From ea3443e7fbd1f864609e9aa023abd441b3a759c5 Mon Sep 17 00:00:00 2001 From: xxm1995 Date: Fri, 14 Jul 2023 17:03:35 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E6=8B=86=E5=88=86=E7=BD=91=E5=85=B3?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81/?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E7=BD=91=E5=85=B3=E5=90=8C=E6=AD=A5=E8=AE=B0?= =?UTF-8?q?=E5=BD=95/=E4=BF=9D=E5=AD=98=E5=90=84=E9=80=9A=E9=81=93?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E4=BB=98=E5=8D=95/=E5=82=A8=E5=80=BC?= =?UTF-8?q?=E5=8D=A1=E5=A4=9A=E5=8D=A1=E9=80=80=E6=AC=BE=E5=88=B0=E5=8D=95?= =?UTF-8?q?=E5=8D=A1,=20=E4=B8=8D=E4=BC=A0=E5=8D=A1=E5=8F=B7=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E8=AE=BE=E7=BD=AE=E4=B8=BA=E9=BB=98=E8=AE=A4=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- _doc/Task.md | 11 ++ .../daxpay/controller/PayController.java | 2 +- ...er.java => PayRefundRecordController.java} | 16 +-- .../controller/PaySyncRecordController.java | 34 +++++ .../daxpay/controller/VoucherController.java | 45 +++++++ .../alipay/service/AliPaymentService.java | 26 ++-- .../alipay/service/AlipaySyncService.java | 5 +- .../voucher/service/VoucherPayService.java | 11 +- .../voucher/service/VoucherService.java | 30 ++++- .../wallet/dao/WalletConfigManager.java | 18 +++ .../wallet/dao/WalletConfigMapper.java | 14 ++ .../channel/wallet/entity/WalletConfig.java | 43 ++++++ .../wallet/service/WalletConfigService.java | 20 +++ .../wechat/service/WeChatPayService.java | 4 +- .../wechat/service/WeChatPaySyncService.java | 4 +- .../wechat/service/WeChatPaymentService.java | 18 ++- .../core/notify/entity/PayNotifyRecord.java | 5 +- .../service/PayNotifyRecordService.java | 2 +- .../daxpay/core/pay/func/AbsPayStrategy.java | 10 -- .../core/pay/strategy/AliPayStrategy.java | 9 -- .../core/pay/strategy/WeChatPayStrategy.java | 11 -- .../refund/record/convert/RefundConvert.java | 6 +- .../record/dao/PayRefundRecordManager.java | 34 +++++ ...Mapper.java => PayRefundRecordMapper.java} | 4 +- .../record/dao/RefundRecordManager.java | 34 ----- ...RefundRecord.java => PayRefundRecord.java} | 6 +- ...rvice.java => PayRefundRecordService.java} | 18 +-- .../core/refund/service/PayRefundService.java | 8 +- .../strategy/WeChatPayRefundStrategy.java | 2 - .../sync/factory/PaySyncStrategyFactory.java | 38 ++++++ .../core/sync/func/AbsPaySyncStrategy.java | 35 +++++ .../record/convert/PaySyncRecordConvert.java | 19 +++ .../sync/record/dao/PaySyncRecordManager.java | 35 +++++ .../sync/record/dao/PaySyncRecordMapper.java | 14 ++ .../sync/record/entity/PaySyncRecord.java | 74 ++++++++++ .../record/service/PaySyncRecordService.java | 59 ++++++++ .../{pay => sync}/result/PaySyncResult.java | 7 +- .../service/PayExpiredTimeService.java | 127 ++++++++---------- .../{pay => sync}/service/PaySyncService.java | 37 +++-- .../sync/strategy/AliPaySyncStrategy.java | 53 ++++++++ .../sync/strategy/WeChatPaySyncStrategy.java | 49 +++++++ ...RecordDto.java => PayRefundRecordDto.java} | 2 +- .../daxpay/dto/sync/PaySyncRecordDto.java | 61 +++++++++ .../daxpay/mq/PaymentMessageListener.java | 2 +- .../channel/voucher/VoucherChangeParam.java | 57 ++++++++ .../channel/voucher/VoucherImportParam.java | 42 +++++- .../template/import/ImportVoucher.xlsx | Bin 0 -> 10060 bytes 48 files changed, 953 insertions(+), 210 deletions(-) create mode 100644 _doc/Task.md rename dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/{RefundRecordController.java => PayRefundRecordController.java} (61%) create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/PaySyncRecordController.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/dao/WalletConfigManager.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/dao/WalletConfigMapper.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/entity/WalletConfig.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/service/WalletConfigService.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/PayRefundRecordManager.java rename dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/{RefundRecordMapper.java => PayRefundRecordMapper.java} (58%) delete mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/RefundRecordManager.java rename dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/entity/{RefundRecord.java => PayRefundRecord.java} (90%) rename dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/service/{RefundRecordService.java => PayRefundRecordService.java} (52%) create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/factory/PaySyncStrategyFactory.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/func/AbsPaySyncStrategy.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/convert/PaySyncRecordConvert.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/dao/PaySyncRecordManager.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/dao/PaySyncRecordMapper.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/entity/PaySyncRecord.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/service/PaySyncRecordService.java rename dax-pay/src/main/java/cn/bootx/platform/daxpay/core/{pay => sync}/result/PaySyncResult.java (74%) rename dax-pay/src/main/java/cn/bootx/platform/daxpay/core/{pay => sync}/service/PayExpiredTimeService.java (55%) rename dax-pay/src/main/java/cn/bootx/platform/daxpay/core/{pay => sync}/service/PaySyncService.java (85%) create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/strategy/AliPaySyncStrategy.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/strategy/WeChatPaySyncStrategy.java rename dax-pay/src/main/java/cn/bootx/platform/daxpay/dto/refund/{RefundRecordDto.java => PayRefundRecordDto.java} (97%) create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/dto/sync/PaySyncRecordDto.java create mode 100644 dax-pay/src/main/java/cn/bootx/platform/daxpay/param/channel/voucher/VoucherChangeParam.java create mode 100644 dax-pay/src/main/resources/template/import/ImportVoucher.xlsx diff --git a/README.md b/README.md index 47ef4d1f7..f95ac8571 100644 --- a/README.md +++ b/README.md @@ -125,4 +125,4 @@ QQ扫码加入QQ交流群 ## 🍷License -Apache License Version 2.0_ +Apache License Version 2.0 diff --git a/_doc/Task.md b/_doc/Task.md new file mode 100644 index 000000000..8c01389fd --- /dev/null +++ b/_doc/Task.md @@ -0,0 +1,11 @@ +- 微信V3支付接口 +- x 拆分网关同步相关代码 +- x 记录网关同步记录 +- 重构支付消息通知结构 +- x 保存各通道的支付单 +- 钱包支持设置开通时的默认金额 +- 储值卡多卡支付和退款演示 +- x 储值卡信息调整 +- x 储值卡多卡退款到单卡, 不传卡号自动设置为默认卡 +- 储值卡批量导入 +- 储值卡支持多卡合一 diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/PayController.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/PayController.java index 2186b5df0..70fd402c9 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/PayController.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/PayController.java @@ -5,7 +5,7 @@ import cn.bootx.platform.common.core.rest.ResResult; import cn.bootx.platform.daxpay.core.pay.service.PayCancelService; import cn.bootx.platform.daxpay.core.refund.service.PayRefundService; import cn.bootx.platform.daxpay.core.pay.service.PayService; -import cn.bootx.platform.daxpay.core.pay.service.PaySyncService; +import cn.bootx.platform.daxpay.core.sync.service.PaySyncService; import cn.bootx.platform.daxpay.dto.pay.PayResult; import cn.bootx.platform.daxpay.param.pay.PayParam; import cn.bootx.platform.daxpay.param.refund.RefundParam; diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/RefundRecordController.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/PayRefundRecordController.java similarity index 61% rename from dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/RefundRecordController.java rename to dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/PayRefundRecordController.java index 8193f0791..a4a241b8f 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/RefundRecordController.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/PayRefundRecordController.java @@ -4,8 +4,8 @@ import cn.bootx.platform.common.core.rest.PageResult; import cn.bootx.platform.common.core.rest.Res; import cn.bootx.platform.common.core.rest.ResResult; import cn.bootx.platform.common.core.rest.param.PageParam; -import cn.bootx.platform.daxpay.core.refund.record.service.RefundRecordService; -import cn.bootx.platform.daxpay.dto.refund.RefundRecordDto; +import cn.bootx.platform.daxpay.core.refund.record.service.PayRefundRecordService; +import cn.bootx.platform.daxpay.dto.refund.PayRefundRecordDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -23,20 +23,20 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/pay/refund") @RequiredArgsConstructor -public class RefundRecordController { +public class PayRefundRecordController { - private final RefundRecordService refundRecordService; + private final PayRefundRecordService payRefundRecordService; @Operation(summary = "分页") @GetMapping("/page") - public ResResult> page(PageParam pageParam, RefundRecordDto param) { - return Res.ok(refundRecordService.page(pageParam, param)); + public ResResult> page(PageParam pageParam, PayRefundRecordDto param) { + return Res.ok(payRefundRecordService.page(pageParam, param)); } @Operation(summary = "根据id查询") @GetMapping("/findById") - public ResResult findById(Long id) { - return Res.ok(refundRecordService.findById(id)); + public ResResult findById(Long id) { + return Res.ok(payRefundRecordService.findById(id)); } } diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/PaySyncRecordController.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/PaySyncRecordController.java new file mode 100644 index 000000000..9593630ff --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/PaySyncRecordController.java @@ -0,0 +1,34 @@ +package cn.bootx.platform.daxpay.controller; + +import cn.bootx.platform.common.core.rest.PageResult; +import cn.bootx.platform.common.core.rest.Res; +import cn.bootx.platform.common.core.rest.ResResult; +import cn.bootx.platform.common.core.rest.param.PageParam; +import cn.bootx.platform.daxpay.core.sync.record.service.PaySyncRecordService; +import cn.bootx.platform.daxpay.dto.sync.PaySyncRecordDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "支付同步记录") +@RestController +@RequestMapping("/") +@RequiredArgsConstructor +public class PaySyncRecordController { + private final PaySyncRecordService syncRecordService; + + @Operation(summary = "分页") + @GetMapping("/page") + public ResResult> page(PageParam pageParam, PaySyncRecordDto param) { + return Res.ok(syncRecordService.page(pageParam, param)); + } + + @Operation(summary = "根据id查询") + @GetMapping("/findById") + public ResResult findById(Long id) { + return Res.ok(syncRecordService.findById(id)); + } +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/VoucherController.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/VoucherController.java index 34bd13e07..aeb652c7c 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/VoucherController.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/controller/VoucherController.java @@ -8,12 +8,24 @@ import cn.bootx.platform.daxpay.core.channel.voucher.service.VoucherQueryService import cn.bootx.platform.daxpay.core.channel.voucher.service.VoucherService; import cn.bootx.platform.daxpay.dto.channel.voucher.VoucherDto; import cn.bootx.platform.daxpay.param.channel.voucher.VoucherGenerationParam; +import cn.bootx.platform.daxpay.param.channel.voucher.VoucherImportParam; import cn.bootx.platform.daxpay.param.channel.voucher.VoucherParam; +import cn.hutool.core.io.IoUtil; +import com.alibaba.excel.EasyExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import java.io.InputStream; import java.util.List; /** @@ -89,4 +101,37 @@ public class VoucherController { return Res.ok(); } + @SneakyThrows + @Operation(summary = "导入已有的储值卡") + @PostMapping("/importBatch") + public ResResult importBatch(Boolean skip, MultipartFile file){ + List voucherImportParams = EasyExcel.read(file.getInputStream()) + // 设置与Excel表映射的类 + .head(VoucherImportParam.class) + // 设置sheet,默认读取第一个 + .sheet() + // 设置标题所在行数 + .headRowNumber(1) + // 异步读取 + .doReadSync(); + voucherService.importBatch(skip,voucherImportParams); + return Res.ok(); + } + + @SneakyThrows + @Operation(summary = "下载导入模板") + @GetMapping("/excelTemplate") + public ResponseEntity excelTemplate(){ + //设置header信息 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + headers.setContentDispositionFormData("attachment", + "ImportVoucher.xlsx"); + + ResourceLoader resourceLoader = new DefaultResourceLoader(); + InputStream inputStream = resourceLoader.getResource("classpath:templates//ImportVoucher.xlsx") + .getInputStream(); + + return new ResponseEntity<>(IoUtil.readBytes(inputStream),headers, HttpStatus.OK); + } } diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AliPaymentService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AliPaymentService.java index 9892220e9..b9fc02088 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AliPaymentService.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AliPaymentService.java @@ -3,11 +3,11 @@ package cn.bootx.platform.daxpay.core.channel.alipay.service; import cn.bootx.platform.common.core.util.BigDecimalUtil; import cn.bootx.platform.daxpay.code.pay.PayChannelEnum; import cn.bootx.platform.daxpay.code.pay.PayStatusCode; +import cn.bootx.platform.daxpay.core.channel.alipay.dao.AliPaymentManager; +import cn.bootx.platform.daxpay.core.channel.alipay.entity.AliPayment; import cn.bootx.platform.daxpay.core.pay.local.AsyncPayInfoLocal; import cn.bootx.platform.daxpay.core.payment.dao.PaymentManager; import cn.bootx.platform.daxpay.core.payment.entity.Payment; -import cn.bootx.platform.daxpay.core.channel.alipay.dao.AliPaymentManager; -import cn.bootx.platform.daxpay.core.channel.alipay.entity.AliPayment; import cn.bootx.platform.daxpay.dto.pay.AsyncPayInfo; import cn.bootx.platform.daxpay.dto.payment.PayChannelInfo; import cn.bootx.platform.daxpay.dto.payment.RefundableInfo; @@ -25,6 +25,9 @@ import java.util.Optional; /** * 支付宝支付记录 + * 1.创建: 支付调起并支付成功后才会创建 + * 2.撤销: 关闭本地支付记录 + * 3.退款: 发起退款时记录 * * @author xxm * @since 2021/2/26 @@ -39,7 +42,7 @@ public class AliPaymentService { private final PaymentManager paymentManager; /** - * 支付调起成功 更新 payment 中 异步支付类型信息 + * 支付调起成功 更新payment中异步支付类型信息, 如果支付完成, 创建支付宝支付单 */ public void updatePaySuccess(Payment payment, PayWayParam payWayParam) { AsyncPayInfo asyncPayInfo = AsyncPayInfoLocal.get(); @@ -71,22 +74,22 @@ public class AliPaymentService { public void updateAsyncSuccess(Long id, PayWayParam payWayParam, String tradeNo) { // 更新支付记录 Payment payment = paymentManager.findById(id).orElseThrow(() -> new PayFailureException("支付记录不存在")); - + this.createAliPayment(payment,payWayParam,tradeNo); } /** - * 创建支付宝支付记录 + * 创建支付宝支付记录(支付调起成功后才会创建) */ private void createAliPayment(Payment payment, PayWayParam payWayParam, String tradeNo) { // 创建支付宝支付记录 AliPayment aliPayment = new AliPayment(); aliPayment.setTradeNo(tradeNo) - .setPaymentId(payment.getId()) - .setAmount(payWayParam.getAmount()) - .setRefundableBalance(payWayParam.getAmount()) - .setBusinessId(payment.getBusinessId()) - .setPayStatus(PayStatusCode.TRADE_SUCCESS) - .setPayTime(LocalDateTime.now()); + .setPaymentId(payment.getId()) + .setAmount(payWayParam.getAmount()) + .setRefundableBalance(payWayParam.getAmount()) + .setBusinessId(payment.getBusinessId()) + .setPayStatus(PayStatusCode.TRADE_SUCCESS) + .setPayTime(LocalDateTime.now()); aliPaymentManager.save(aliPayment); } @@ -118,5 +121,4 @@ public class AliPaymentService { aliPaymentManager.updateById(payment); }); } - } diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AlipaySyncService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AlipaySyncService.java index 10170207c..25c469585 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AlipaySyncService.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AlipaySyncService.java @@ -2,8 +2,9 @@ package cn.bootx.platform.daxpay.core.channel.alipay.service; import cn.bootx.platform.daxpay.code.pay.PaySyncStatus; import cn.bootx.platform.daxpay.code.paymodel.AliPayCode; -import cn.bootx.platform.daxpay.core.pay.result.PaySyncResult; +import cn.bootx.platform.daxpay.core.sync.result.PaySyncResult; import cn.bootx.platform.daxpay.core.payment.entity.Payment; +import cn.hutool.json.JSONUtil; import com.alipay.api.AlipayApiException; import com.alipay.api.domain.AlipayTradeQueryModel; import com.alipay.api.response.AlipayTradeQueryResponse; @@ -39,7 +40,7 @@ public class AlipaySyncService { // 查询退款参数 AlipayTradeQueryResponse response = AliPayApi.tradeQueryToResponse(queryModel); String tradeStatus = response.getTradeStatus(); - + paySyncResult.setJson(JSONUtil.toJsonStr(response)); // 支付完成 if (Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_SUCCESS) || Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_FINISHED)) { diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/voucher/service/VoucherPayService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/voucher/service/VoucherPayService.java index a56a1ffa2..38bc1adb6 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/voucher/service/VoucherPayService.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/voucher/service/VoucherPayService.java @@ -164,7 +164,7 @@ public class VoucherPayService { } /** - * 直接支付 + * 直接支付 (同步支付方式下) */ @Transactional(rollbackFor = Exception.class) public List pay(BigDecimal amount, Payment payment, List vouchers) { @@ -272,9 +272,16 @@ public class VoucherPayService { .map(VoucherRecord::getCardNo) .collect(Collectors.toList()); List vouchers = voucherManager.findByCardNoList(cardNoList); + // 如果未传入卡号, 默认退到最抗用的一张卡上 + if (StrUtil.isBlank(refundVoucherNo)){ + List sort = this.sort(vouchers); + refundVoucherNo = sort.get(sort.size()-1).getCardNo(); + } + // 筛选出来要进行退款的卡 + String finalRefundVoucherNo = refundVoucherNo; Voucher voucher = vouchers.stream() - .filter(vr -> Objects.equals(vr.getCardNo(), refundVoucherNo)) + .filter(vr -> Objects.equals(vr.getCardNo(), finalRefundVoucherNo)) .findFirst() .orElseThrow(() -> new PayFailureException("退款卡号不存在")); // 将金额全部推到指定的卡上 diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/voucher/service/VoucherService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/voucher/service/VoucherService.java index 16b33e615..ab0a3ddec 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/voucher/service/VoucherService.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/voucher/service/VoucherService.java @@ -1,12 +1,16 @@ package cn.bootx.platform.daxpay.core.channel.voucher.service; +import cn.bootx.platform.common.core.exception.BizException; +import cn.bootx.platform.common.core.exception.DataNotExistException; import cn.bootx.platform.daxpay.code.paymodel.VoucherCode; import cn.bootx.platform.daxpay.core.channel.voucher.dao.VoucherLogManager; import cn.bootx.platform.daxpay.core.channel.voucher.dao.VoucherManager; import cn.bootx.platform.daxpay.core.channel.voucher.entity.Voucher; import cn.bootx.platform.daxpay.core.channel.voucher.entity.VoucherLog; +import cn.bootx.platform.daxpay.param.channel.voucher.VoucherChangeParam; import cn.bootx.platform.daxpay.param.channel.voucher.VoucherGenerationParam; import cn.bootx.platform.daxpay.param.channel.voucher.VoucherImportParam; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.RandomUtil; import lombok.RequiredArgsConstructor; @@ -68,8 +72,24 @@ public class VoucherService { /** * 批量导入 + * @param skip 是否跳过已经导入的储值卡,false时将会异常 */ - public void importBatch(VoucherImportParam param) { + public void importBatch(Boolean skip,List voucherImports) { + List cardNoList = voucherImports.stream() + .map(VoucherImportParam::getCardNo) + .distinct() + .collect(Collectors.toList()); + // 卡号不能重复 + if (voucherImports.size()!=cardNoList.size()){ + throw new BizException("卡号不能重复"); + } + // 查询库中是否已经有对应的储值卡号 + List vouchersByDB = voucherManager.findByCardNoList(cardNoList); + // 不跳过已经导入的储值卡且存在数据, 抛出异常 + if (Objects.equals(skip,true)&& CollUtil.isNotEmpty(vouchersByDB)){ + log.warn("数据库中已经存在的卡号:{}",vouchersByDB.stream().map(Voucher::getCardNo).collect(Collectors.toList())); + throw new BizException("要导入的卡号在数据中已经存在"); + } } @@ -102,10 +122,12 @@ public class VoucherService { } /** - * 更改有效期 + * 更改储值卡信息 */ - public void changeEnduring() { - + public void changeInfo(VoucherChangeParam voucherChangeParam) { + // 查询对应的卡 + Voucher voucher = voucherManager.findByCardNo(voucherChangeParam.getCardNo()) + .orElseThrow(DataNotExistException::new); } } diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/dao/WalletConfigManager.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/dao/WalletConfigManager.java new file mode 100644 index 000000000..6c8ff6246 --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/dao/WalletConfigManager.java @@ -0,0 +1,18 @@ +package cn.bootx.platform.daxpay.core.channel.wallet.dao; + +import cn.bootx.platform.common.mybatisplus.impl.BaseManager; +import cn.bootx.platform.daxpay.core.channel.wallet.entity.WalletConfig; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +/** + * 钱包配置 + * @author xxm + * @since 2023/7/14 + */ +@Slf4j +@Repository +@RequiredArgsConstructor +public class WalletConfigManager extends BaseManager { +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/dao/WalletConfigMapper.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/dao/WalletConfigMapper.java new file mode 100644 index 000000000..27ada2c28 --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/dao/WalletConfigMapper.java @@ -0,0 +1,14 @@ +package cn.bootx.platform.daxpay.core.channel.wallet.dao; + +import cn.bootx.platform.daxpay.core.channel.wallet.entity.WalletConfig; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 钱包配置 + * @author xxm + * @since 2023/7/14 + */ +@Mapper +public interface WalletConfigMapper extends BaseMapper { +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/entity/WalletConfig.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/entity/WalletConfig.java new file mode 100644 index 000000000..830a124a6 --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/entity/WalletConfig.java @@ -0,0 +1,43 @@ +package cn.bootx.platform.daxpay.core.channel.wallet.entity; + +import cn.bootx.mybatis.table.modify.annotation.DbColumn; +import cn.bootx.mybatis.table.modify.annotation.DbTable; +import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.DbMySqlIndex; +import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.math.BigDecimal; + +/** + * 钱包配置 + * @author xxm + * @since 2023/7/14 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@DbTable(comment = "钱包配置") +@Accessors(chain = true) +@TableName("pay_wallet") +public class WalletConfig extends MpBaseEntity { + + /** 商户编码 */ + @TableField(updateStrategy = FieldStrategy.NEVER) + @DbColumn(comment = "商户编码") + private String mchCode; + + /** 商户应用编码 */ + @TableField(updateStrategy = FieldStrategy.NEVER) + @DbMySqlIndex(comment = "商户应用编码唯一索引") + @DbColumn(comment = "商户应用编码") + private String mchAppCode; + + /** 默认余额 */ + @DbColumn(comment = "默认余额") + private BigDecimal defaultBalance; + +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/service/WalletConfigService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/service/WalletConfigService.java new file mode 100644 index 000000000..c2e2ccec2 --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wallet/service/WalletConfigService.java @@ -0,0 +1,20 @@ +package cn.bootx.platform.daxpay.core.channel.wallet.service; + +import cn.bootx.platform.daxpay.core.channel.wallet.dao.WalletConfigManager; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 钱包配置 + * @author xxm + * @since 2023/7/14 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class WalletConfigService { + private final WalletConfigManager walletConfigManager; + + +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPayService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPayService.java index 1b3c15b06..e8a59e1b7 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPayService.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPayService.java @@ -7,8 +7,8 @@ import cn.bootx.platform.daxpay.code.pay.PayWayEnum; import cn.bootx.platform.daxpay.code.paymodel.WeChatPayCode; import cn.bootx.platform.daxpay.code.paymodel.WeChatPayWay; import cn.bootx.platform.daxpay.core.pay.local.AsyncPayInfoLocal; -import cn.bootx.platform.daxpay.core.pay.result.PaySyncResult; -import cn.bootx.platform.daxpay.core.pay.service.PaySyncService; +import cn.bootx.platform.daxpay.core.sync.result.PaySyncResult; +import cn.bootx.platform.daxpay.core.sync.service.PaySyncService; import cn.bootx.platform.daxpay.core.payment.entity.Payment; import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig; import cn.bootx.platform.daxpay.dto.pay.AsyncPayInfo; diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPaySyncService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPaySyncService.java index 1b9986137..3b63fac7d 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPaySyncService.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPaySyncService.java @@ -2,8 +2,9 @@ package cn.bootx.platform.daxpay.core.channel.wechat.service; import cn.bootx.platform.daxpay.code.pay.PaySyncStatus; import cn.bootx.platform.daxpay.code.paymodel.WeChatPayCode; -import cn.bootx.platform.daxpay.core.pay.result.PaySyncResult; +import cn.bootx.platform.daxpay.core.sync.result.PaySyncResult; import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig; +import cn.hutool.json.JSONUtil; import com.ijpay.core.enums.SignType; import com.ijpay.core.kit.WxPayKit; import com.ijpay.wxpay.WxPayApi; @@ -41,6 +42,7 @@ public class WeChatPaySyncService { try { String xmlResult = WxPayApi.orderQuery(params); Map result = WxPayKit.xmlToMap(xmlResult); + paySyncResult.setJson(JSONUtil.toJsonStr(result)); // 查询失败 if (!WxPayKit.codeIsOk(result.get(WeChatPayCode.RETURN_CODE))) { log.warn("查询微信订单失败:{}", result); diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPaymentService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPaymentService.java index 0ec224152..5981430d0 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPaymentService.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPaymentService.java @@ -1,6 +1,7 @@ package cn.bootx.platform.daxpay.core.channel.wechat.service; import cn.bootx.platform.common.core.exception.BizException; +import cn.bootx.platform.common.core.util.BigDecimalUtil; import cn.bootx.platform.daxpay.code.pay.PayChannelEnum; import cn.bootx.platform.daxpay.code.pay.PayStatusCode; import cn.bootx.platform.daxpay.core.channel.wechat.dao.WeChatPaymentManager; @@ -38,7 +39,7 @@ public class WeChatPaymentService { private final WeChatPaymentManager weChatPaymentManager; /** - * 支付调起成功 更新 payment 中 异步支付类型信息 + * 支付调起成功 更新payment中异步支付类型信息, 如果支付完成, 创建微信支付单 */ public void updatePaySuccess(Payment payment, PayWayParam payWayParam) { AsyncPayInfo asyncPayInfo = AsyncPayInfoLocal.get(); @@ -74,10 +75,9 @@ public class WeChatPaymentService { } /** - * 更新支付记录成功状态, 并创建微信支付记录 + * 并创建微信支付记录 */ private void createWeChatPayment(Payment payment, PayWayParam payWayParam, String tradeNo) { - // 创建微信支付记录 WeChatPayment wechatPayment = new WeChatPayment(); wechatPayment.setTradeNo(tradeNo) @@ -105,6 +105,18 @@ public class WeChatPaymentService { * 更新退款 */ public void updatePayRefund(Long paymentId, BigDecimal amount) { + Optional weChatPayment = weChatPaymentManager.findByPaymentId(paymentId); + weChatPayment.ifPresent(payment -> { + BigDecimal refundableBalance = payment.getRefundableBalance().subtract(amount); + payment.setRefundableBalance(refundableBalance); + if (BigDecimalUtil.compareTo(refundableBalance, BigDecimal.ZERO) == 0) { + payment.setPayStatus(PayStatusCode.TRADE_REFUNDED); + } + else { + payment.setPayStatus(PayStatusCode.TRADE_REFUNDING); + } + weChatPaymentManager.updateById(payment); + }); } } diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/notify/entity/PayNotifyRecord.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/notify/entity/PayNotifyRecord.java index a216b56cf..97530d4dc 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/notify/entity/PayNotifyRecord.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/notify/entity/PayNotifyRecord.java @@ -1,11 +1,10 @@ package cn.bootx.platform.daxpay.core.notify.entity; import cn.bootx.mybatis.table.modify.annotation.DbComment; -import cn.bootx.mybatis.table.modify.annotation.DbTable; import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.DbMySqlFieldType; import cn.bootx.mybatis.table.modify.mybatis.mysq.constants.MySqlFieldTypeEnum; import cn.bootx.platform.common.core.function.EntityBaseFunction; -import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity; +import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity; import cn.bootx.platform.daxpay.code.pay.PayChannelEnum; import cn.bootx.platform.daxpay.code.pay.PayStatusCode; import cn.bootx.platform.daxpay.core.notify.convert.PayNotifyConvert; @@ -28,7 +27,7 @@ import java.time.LocalDateTime; //@DbTable(comment = "回调记录") @Accessors(chain = true) @TableName("pay_pay_notify_record") -public class PayNotifyRecord extends MpBaseEntity implements EntityBaseFunction { +public class PayNotifyRecord extends MpCreateEntity implements EntityBaseFunction { /** 支付记录id */ @DbComment("支付记录id") diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/notify/service/PayNotifyRecordService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/notify/service/PayNotifyRecordService.java index cf3778179..feb90009d 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/notify/service/PayNotifyRecordService.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/notify/service/PayNotifyRecordService.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; @Slf4j @Service @RequiredArgsConstructor -public class PayNotifyRecordService { +public class PayNotifyRecordService { private final PayNotifyRecordManager payNotifyRecordManager; diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/func/AbsPayStrategy.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/func/AbsPayStrategy.java index b9c26abe6..1c23d19fb 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/func/AbsPayStrategy.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/func/AbsPayStrategy.java @@ -1,9 +1,7 @@ package cn.bootx.platform.daxpay.core.pay.func; import cn.bootx.platform.daxpay.code.pay.PayChannelEnum; -import cn.bootx.platform.daxpay.code.pay.PaySyncStatus; import cn.bootx.platform.daxpay.core.pay.exception.ExceptionInfo; -import cn.bootx.platform.daxpay.core.pay.result.PaySyncResult; import cn.bootx.platform.daxpay.core.payment.entity.Payment; import cn.bootx.platform.daxpay.param.pay.PayParam; import cn.bootx.platform.daxpay.param.pay.PayWayParam; @@ -93,12 +91,4 @@ public abstract class AbsPayStrategy { */ public abstract void doCloseHandler(); - /** - * 异步支付单与支付网关进行状态比对 - * @see PaySyncStatus - */ - public PaySyncResult doSyncPayStatusHandler() { - return new PaySyncResult(); - } - } diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/strategy/AliPayStrategy.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/strategy/AliPayStrategy.java index 5f1c478b0..6528917ea 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/strategy/AliPayStrategy.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/strategy/AliPayStrategy.java @@ -8,7 +8,6 @@ import cn.bootx.platform.daxpay.core.channel.alipay.entity.AlipayConfig; import cn.bootx.platform.daxpay.core.channel.alipay.service.*; import cn.bootx.platform.daxpay.core.pay.exception.ExceptionInfo; import cn.bootx.platform.daxpay.core.pay.func.AbsPayStrategy; -import cn.bootx.platform.daxpay.core.pay.result.PaySyncResult; import cn.bootx.platform.daxpay.exception.payment.PayAmountAbnormalException; import cn.bootx.platform.daxpay.exception.payment.PayFailureException; import cn.bootx.platform.daxpay.param.channel.alipay.AliPayParam; @@ -153,14 +152,6 @@ public class AliPayStrategy extends AbsPayStrategy { aliPaymentService.updateClose(this.getPayment().getId()); } - /** - * 异步支付单与支付网关进行状态比对 - */ - @Override - public PaySyncResult doSyncPayStatusHandler() { - this.initAlipayConfig(this.getPayParam().getMchAppCode()); - return alipaySyncService.syncPayStatus(this.getPayment()); - } /** * 初始化支付宝配置信息 diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/strategy/WeChatPayStrategy.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/strategy/WeChatPayStrategy.java index 438546de7..aceecc979 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/strategy/WeChatPayStrategy.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/strategy/WeChatPayStrategy.java @@ -11,7 +11,6 @@ import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPaySyncService import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPaymentService; import cn.bootx.platform.daxpay.core.pay.exception.ExceptionInfo; import cn.bootx.platform.daxpay.core.pay.func.AbsPayStrategy; -import cn.bootx.platform.daxpay.core.pay.result.PaySyncResult; import cn.bootx.platform.daxpay.exception.payment.PayAmountAbnormalException; import cn.bootx.platform.daxpay.exception.payment.PayFailureException; import cn.bootx.platform.daxpay.param.channel.wechat.WeChatPayParam; @@ -155,16 +154,6 @@ public class WeChatPayStrategy extends AbsPayStrategy { weChatPaymentService.updateClose(this.getPayment().getId()); } - /** - * 异步支付单与支付网关进行状态比对 - */ - @Override - public PaySyncResult doSyncPayStatusHandler() { - // 检查并获取微信支付配置 - this.initWeChatPayConfig(this.getPayParam().getMchAppCode()); - return weChatPaySyncService.syncPayStatus(this.getPayment().getId(), this.weChatPayConfig); - } - /** * 初始化微信支付 */ diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/convert/RefundConvert.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/convert/RefundConvert.java index 14d72fd71..b62f968eb 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/convert/RefundConvert.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/convert/RefundConvert.java @@ -1,7 +1,7 @@ package cn.bootx.platform.daxpay.core.refund.record.convert; -import cn.bootx.platform.daxpay.core.refund.record.entity.RefundRecord; -import cn.bootx.platform.daxpay.dto.refund.RefundRecordDto; +import cn.bootx.platform.daxpay.core.refund.record.entity.PayRefundRecord; +import cn.bootx.platform.daxpay.dto.refund.PayRefundRecordDto; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @@ -14,6 +14,6 @@ public interface RefundConvert { RefundConvert CONVERT = Mappers.getMapper(RefundConvert.class); - RefundRecordDto convert(RefundRecord in); + PayRefundRecordDto convert(PayRefundRecord in); } diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/PayRefundRecordManager.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/PayRefundRecordManager.java new file mode 100644 index 000000000..9c68bdaab --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/PayRefundRecordManager.java @@ -0,0 +1,34 @@ +package cn.bootx.platform.daxpay.core.refund.record.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.daxpay.core.refund.record.entity.PayRefundRecord; +import cn.bootx.platform.daxpay.dto.refund.PayRefundRecordDto; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.Objects; + +/** + * @author xxm + * @since 2022/3/2 + */ +@Slf4j +@Repository +@RequiredArgsConstructor +public class PayRefundRecordManager extends BaseManager { + + public Page page(PageParam pageParam, PayRefundRecordDto param) { + Page mpPage = MpUtil.getMpPage(pageParam, PayRefundRecord.class); + return lambdaQuery().orderByDesc(MpIdEntity::getId) + .like(Objects.nonNull(param.getPaymentId()), PayRefundRecord::getPaymentId, param.getPaymentId()) + .like(Objects.nonNull(param.getBusinessId()), PayRefundRecord::getBusinessId, param.getBusinessId()) + .like(Objects.nonNull(param.getTitle()), PayRefundRecord::getTitle, param.getTitle()) + .page(mpPage); + } + +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/RefundRecordMapper.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/PayRefundRecordMapper.java similarity index 58% rename from dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/RefundRecordMapper.java rename to dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/PayRefundRecordMapper.java index 55a4611e4..db718ce7c 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/RefundRecordMapper.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/PayRefundRecordMapper.java @@ -1,6 +1,6 @@ package cn.bootx.platform.daxpay.core.refund.record.dao; -import cn.bootx.platform.daxpay.core.refund.record.entity.RefundRecord; +import cn.bootx.platform.daxpay.core.refund.record.entity.PayRefundRecord; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @@ -9,6 +9,6 @@ import org.apache.ibatis.annotations.Mapper; * @since 2022/3/2 */ @Mapper -public interface RefundRecordMapper extends BaseMapper { +public interface PayRefundRecordMapper extends BaseMapper { } diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/RefundRecordManager.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/RefundRecordManager.java deleted file mode 100644 index 7adaa8017..000000000 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/dao/RefundRecordManager.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.bootx.platform.daxpay.core.refund.record.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.daxpay.core.refund.record.entity.RefundRecord; -import cn.bootx.platform.daxpay.dto.refund.RefundRecordDto; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Repository; - -import java.util.Objects; - -/** - * @author xxm - * @since 2022/3/2 - */ -@Slf4j -@Repository -@RequiredArgsConstructor -public class RefundRecordManager extends BaseManager { - - public Page page(PageParam pageParam, RefundRecordDto param) { - Page mpPage = MpUtil.getMpPage(pageParam, RefundRecord.class); - return lambdaQuery().orderByDesc(MpIdEntity::getId) - .like(Objects.nonNull(param.getPaymentId()), RefundRecord::getPaymentId, param.getPaymentId()) - .like(Objects.nonNull(param.getBusinessId()), RefundRecord::getBusinessId, param.getBusinessId()) - .like(Objects.nonNull(param.getTitle()), RefundRecord::getTitle, param.getTitle()) - .page(mpPage); - } - -} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/entity/RefundRecord.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/entity/PayRefundRecord.java similarity index 90% rename from dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/entity/RefundRecord.java rename to dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/entity/PayRefundRecord.java index d47f4e8a4..1db4012fc 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/entity/RefundRecord.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/record/entity/PayRefundRecord.java @@ -6,7 +6,7 @@ import cn.bootx.platform.common.mybatisplus.handler.JacksonRawTypeHandler; import cn.bootx.platform.daxpay.code.pay.PayStatusCode; import cn.bootx.platform.daxpay.core.refund.record.convert.RefundConvert; import cn.bootx.platform.daxpay.dto.payment.RefundableInfo; -import cn.bootx.platform.daxpay.dto.refund.RefundRecordDto; +import cn.bootx.platform.daxpay.dto.refund.PayRefundRecordDto; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -27,7 +27,7 @@ import java.util.List; @Data @Accessors(chain = true) @TableName(value = "pay_refund_record", autoResultMap = true) -public class RefundRecord extends MpBaseEntity implements EntityBaseFunction { +public class PayRefundRecord extends MpBaseEntity implements EntityBaseFunction { /** 支付单号 */ private Long paymentId; @@ -75,7 +75,7 @@ public class RefundRecord extends MpBaseEntity implements EntityBaseFunction page(PageParam pageParam, RefundRecordDto param) { - Page page = refundRecordManager.page(pageParam, param); + public PageResult page(PageParam pageParam, PayRefundRecordDto param) { + Page page = refundRecordManager.page(pageParam, param); return MpUtil.convert2DtoPageResult(page); } /** * 根据id查询 */ - public RefundRecordDto findById(Long id) { - return refundRecordManager.findById(id).map(RefundRecord::toDto).orElseThrow(DataNotExistException::new); + public PayRefundRecordDto findById(Long id) { + return refundRecordManager.findById(id).map(PayRefundRecord::toDto).orElseThrow(DataNotExistException::new); } } diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/service/PayRefundService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/service/PayRefundService.java index 6991fd441..b77f1386e 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/service/PayRefundService.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/service/PayRefundService.java @@ -10,8 +10,8 @@ import cn.bootx.platform.daxpay.core.refund.factory.PayRefundStrategyFactory; import cn.bootx.platform.daxpay.core.refund.func.AbsPayRefundStrategy; import cn.bootx.platform.daxpay.core.refund.func.PayRefundStrategyConsumer; import cn.bootx.platform.daxpay.core.refund.local.AsyncRefundLocal; -import cn.bootx.platform.daxpay.core.refund.record.dao.RefundRecordManager; -import cn.bootx.platform.daxpay.core.refund.record.entity.RefundRecord; +import cn.bootx.platform.daxpay.core.refund.record.dao.PayRefundRecordManager; +import cn.bootx.platform.daxpay.core.refund.record.entity.PayRefundRecord; import cn.bootx.platform.daxpay.dto.payment.RefundableInfo; import cn.bootx.platform.daxpay.exception.payment.PayAmountAbnormalException; import cn.bootx.platform.daxpay.exception.payment.PayFailureException; @@ -54,7 +54,7 @@ public class PayRefundService { private final PaymentManager paymentManager; - private final RefundRecordManager refundRecordManager; + private final PayRefundRecordManager refundRecordManager; /** * 退款 @@ -229,7 +229,7 @@ public class PayRefundService { .collect(Collectors.toList()); HttpServletRequest request = WebServletUtil.getRequest(); String ip = ServletUtil.getClientIP(request); - RefundRecord refundRecord = new RefundRecord().setRefundRequestNo(AsyncRefundLocal.get()) + PayRefundRecord refundRecord = new PayRefundRecord().setRefundRequestNo(AsyncRefundLocal.get()) .setRefundableInfo(refundableInfos) .setAmount(amount) .setRefundableBalance(payment.getRefundableBalance()) diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/strategy/WeChatPayRefundStrategy.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/strategy/WeChatPayRefundStrategy.java index a0de464ef..57ce0bc75 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/strategy/WeChatPayRefundStrategy.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/refund/strategy/WeChatPayRefundStrategy.java @@ -2,7 +2,6 @@ package cn.bootx.platform.daxpay.core.refund.strategy; import cn.bootx.platform.daxpay.code.pay.PayChannelEnum; import cn.bootx.platform.daxpay.core.channel.wechat.dao.WeChatPayConfigManager; -import cn.bootx.platform.daxpay.core.channel.wechat.dao.WeChatPaymentManager; import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig; import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPayCancelService; import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPaymentService; @@ -25,7 +24,6 @@ import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROT @RequiredArgsConstructor public class WeChatPayRefundStrategy extends AbsPayRefundStrategy { - private final WeChatPaymentManager weChatPaymentManager; private final WeChatPayConfigManager weChatPayConfigManager; private final WeChatPayCancelService weChatPayCancelService; diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/factory/PaySyncStrategyFactory.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/factory/PaySyncStrategyFactory.java new file mode 100644 index 000000000..755fb48cc --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/factory/PaySyncStrategyFactory.java @@ -0,0 +1,38 @@ +package cn.bootx.platform.daxpay.core.sync.factory; + + +import cn.bootx.platform.daxpay.code.pay.PayChannelEnum; +import cn.bootx.platform.daxpay.core.sync.func.AbsPaySyncStrategy; +import cn.bootx.platform.daxpay.core.sync.strategy.AliPaySyncStrategy; +import cn.bootx.platform.daxpay.core.sync.strategy.WeChatPaySyncStrategy; +import cn.bootx.platform.daxpay.exception.payment.PayUnsupportedMethodException; +import cn.hutool.extra.spring.SpringUtil; + +/** + * 支付同步策略工厂类 + * @author xxm + * @since 2023/7/14 + */ +public class PaySyncStrategyFactory { + /** + * 获取支付同步策略 + * @param payChannelCode + * @return + */ + public static AbsPaySyncStrategy create(String payChannelCode) { + AbsPaySyncStrategy strategy; + PayChannelEnum channelEnum = PayChannelEnum.findByCode(payChannelCode); + switch (channelEnum) { + case ALI: + strategy = SpringUtil.getBean(AliPaySyncStrategy.class); + break; + case WECHAT: + strategy = SpringUtil.getBean(WeChatPaySyncStrategy.class); + break; + default: + throw new PayUnsupportedMethodException(); + } + // noinspection ConstantConditions + return strategy; + } +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/func/AbsPaySyncStrategy.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/func/AbsPaySyncStrategy.java new file mode 100644 index 000000000..10764ecd5 --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/func/AbsPaySyncStrategy.java @@ -0,0 +1,35 @@ +package cn.bootx.platform.daxpay.core.sync.func; + +import cn.bootx.platform.daxpay.code.pay.PaySyncStatus; +import cn.bootx.platform.daxpay.core.payment.entity.Payment; +import cn.bootx.platform.daxpay.core.sync.result.PaySyncResult; +import lombok.Getter; +import lombok.Setter; + +/** + * 支付同步抽象类 + * @author xxm + * @since 2023/7/14 + */ +@Getter +@Setter +public abstract class AbsPaySyncStrategy { + + /** 支付对象 */ + private Payment payment = null; + + /** + * 初始化支付的参数 + */ + public void initPayParam(Payment payment) { + this.payment = payment; + } + + + /** + * 异步支付单与支付网关进行状态比对 + * @see PaySyncStatus + */ + public abstract PaySyncResult doSyncPayStatusHandler(); + +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/convert/PaySyncRecordConvert.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/convert/PaySyncRecordConvert.java new file mode 100644 index 000000000..94c96b170 --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/convert/PaySyncRecordConvert.java @@ -0,0 +1,19 @@ +package cn.bootx.platform.daxpay.core.sync.record.convert; + +import cn.bootx.platform.daxpay.core.sync.record.entity.PaySyncRecord; +import cn.bootx.platform.daxpay.dto.sync.PaySyncRecordDto; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * 支付同步记录同步 + * @author xxm + * @since 2023/7/14 + */ +@Mapper +public interface PaySyncRecordConvert { + PaySyncRecordConvert CONVERT = Mappers.getMapper(PaySyncRecordConvert.class); + + PaySyncRecordDto convert(PaySyncRecord in); + +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/dao/PaySyncRecordManager.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/dao/PaySyncRecordManager.java new file mode 100644 index 000000000..e6b3bc8bb --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/dao/PaySyncRecordManager.java @@ -0,0 +1,35 @@ +package cn.bootx.platform.daxpay.core.sync.record.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.daxpay.core.sync.record.entity.PaySyncRecord; +import cn.bootx.platform.daxpay.dto.sync.PaySyncRecordDto; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.Objects; + +/** + * + * @author xxm + * @since 2023/7/14 + */ +@Slf4j +@Repository +@RequiredArgsConstructor +public class PaySyncRecordManager extends BaseManager { + + public Page page(PageParam pageParam, PaySyncRecordDto param) { + Page mpPage = MpUtil.getMpPage(pageParam, PaySyncRecord.class); + return lambdaQuery().orderByDesc(MpIdEntity::getId) + .like(Objects.nonNull(param.getPaymentId()), PaySyncRecord::getPaymentId, param.getPaymentId()) + .eq(Objects.nonNull(param.getPayChannel()), PaySyncRecord::getPayChannel, param.getPayChannel()) + .eq(Objects.nonNull(param.getStatus()), PaySyncRecord::getStatus, param.getStatus()) + .page(mpPage); + } + +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/dao/PaySyncRecordMapper.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/dao/PaySyncRecordMapper.java new file mode 100644 index 000000000..60fc9f961 --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/dao/PaySyncRecordMapper.java @@ -0,0 +1,14 @@ +package cn.bootx.platform.daxpay.core.sync.record.dao; + +import cn.bootx.platform.daxpay.core.sync.record.entity.PaySyncRecord; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 支付同步记录 + * @author xxm + * @since 2023/7/14 + */ +@Mapper +public interface PaySyncRecordMapper extends BaseMapper { +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/entity/PaySyncRecord.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/entity/PaySyncRecord.java new file mode 100644 index 000000000..d833a75f5 --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/entity/PaySyncRecord.java @@ -0,0 +1,74 @@ +package cn.bootx.platform.daxpay.core.sync.record.entity; + +import cn.bootx.mybatis.table.modify.annotation.DbComment; +import cn.bootx.mybatis.table.modify.annotation.DbTable; +import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.DbMySqlFieldType; +import cn.bootx.mybatis.table.modify.mybatis.mysq.constants.MySqlFieldTypeEnum; +import cn.bootx.platform.common.core.function.EntityBaseFunction; +import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity; +import cn.bootx.platform.daxpay.code.pay.PayChannelEnum; +import cn.bootx.platform.daxpay.code.pay.PaySyncStatus; +import cn.bootx.platform.daxpay.core.sync.record.convert.PaySyncRecordConvert; +import cn.bootx.platform.daxpay.dto.sync.PaySyncRecordDto; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +/** + * 支付同步记录 + * @author xxm + * @since 2023/7/14 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@DbTable(comment = "支付同步记录") +@Accessors(chain = true) +@TableName("pay_sync_record") +public class PaySyncRecord extends MpCreateEntity implements EntityBaseFunction { + + /** 支付记录id */ + @DbComment("支付记录id") + private Long paymentId; + + /** 商户编码 */ + @DbComment("商户编码") + private String mchCode; + + /** 商户应用编码 */ + @DbComment("商户应用编码") + private String mchAppCode; + + /** + * 支付通道 + * @see PayChannelEnum#getCode() + */ + @DbComment("支付通道") + private String payChannel; + + /** 通知消息 */ + @DbMySqlFieldType(MySqlFieldTypeEnum.LONGTEXT) + @DbComment("通知消息") + private String syncInfo; + + /** + * 同步状态 + * @see PaySyncStatus#WAIT_BUYER_PAY + */ + @DbComment("同步状态") + private String status; + + /** 同步时间 */ + @DbComment("同步时间") + private LocalDateTime syncTime; + + /** + * 转换 + */ + @Override + public PaySyncRecordDto toDto() { + return PaySyncRecordConvert.CONVERT.convert(this); + } +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/service/PaySyncRecordService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/service/PaySyncRecordService.java new file mode 100644 index 000000000..5b222557a --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/record/service/PaySyncRecordService.java @@ -0,0 +1,59 @@ +package cn.bootx.platform.daxpay.core.sync.record.service; + +import cn.bootx.platform.common.core.exception.DataNotExistException; +import cn.bootx.platform.common.core.rest.PageResult; +import cn.bootx.platform.common.core.rest.param.PageParam; +import cn.bootx.platform.common.mybatisplus.util.MpUtil; +import cn.bootx.platform.daxpay.core.payment.entity.Payment; +import cn.bootx.platform.daxpay.core.sync.record.dao.PaySyncRecordManager; +import cn.bootx.platform.daxpay.core.sync.record.entity.PaySyncRecord; +import cn.bootx.platform.daxpay.core.sync.result.PaySyncResult; +import cn.bootx.platform.daxpay.dto.sync.PaySyncRecordDto; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +/** + * 支付同步记录 + * @author xxm + * @since 2023/7/14 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class PaySyncRecordService { + private final PaySyncRecordManager syncRecordManager; + + /** + * 记录同步记录 + */ + public void saveRecord(PaySyncResult paySyncResult, Payment payment){ + PaySyncRecord paySyncRecord = new PaySyncRecord() + .setPaymentId(payment.getId()) + .setMchCode(payment.getMchCode()) + .setMchAppCode(payment.getMchAppCode()) + .setPayChannel(payment.getAsyncPayChannel()) + .setSyncInfo(paySyncResult.getJson()) + .setStatus(paySyncResult.getPaySyncStatus()) + .setSyncTime(LocalDateTime.now()); + syncRecordManager.save(paySyncRecord); + } + + /** + * 分页查询 + */ + public PageResult page(PageParam pageParam, PaySyncRecordDto param) { + Page page = syncRecordManager.page(pageParam, param); + return MpUtil.convert2DtoPageResult(page); + } + + /** + * 根据id查询 + */ + public PaySyncRecordDto findById(Long id) { + return syncRecordManager.findById(id).map(PaySyncRecord::toDto).orElseThrow(DataNotExistException::new); + } +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/result/PaySyncResult.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/result/PaySyncResult.java similarity index 74% rename from dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/result/PaySyncResult.java rename to dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/result/PaySyncResult.java index fa0bd5d4a..ad282d1f2 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/result/PaySyncResult.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/result/PaySyncResult.java @@ -1,4 +1,4 @@ -package cn.bootx.platform.daxpay.core.pay.result; +package cn.bootx.platform.daxpay.core.sync.result; import cn.bootx.platform.daxpay.code.pay.PaySyncStatus; import lombok.Data; @@ -24,7 +24,10 @@ public class PaySyncResult { */ private String paySyncStatus = NOT_SYNC; - /** 网关返回参数 */ + /** 网关返回参数(会被用到的参数) */ private Map map; + /** 网关返回对象的json字符串 */ + private String json; + } diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/service/PayExpiredTimeService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/service/PayExpiredTimeService.java similarity index 55% rename from dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/service/PayExpiredTimeService.java rename to dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/service/PayExpiredTimeService.java index bda102f3d..36a614ce0 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/service/PayExpiredTimeService.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/service/PayExpiredTimeService.java @@ -1,20 +1,11 @@ -package cn.bootx.platform.daxpay.core.pay.service; +package cn.bootx.platform.daxpay.core.sync.service; -import cn.bootx.platform.daxpay.code.pay.PaySyncStatus; import cn.bootx.platform.daxpay.core.pay.builder.PayEventBuilder; -import cn.bootx.platform.daxpay.core.pay.builder.PaymentBuilder; -import cn.bootx.platform.daxpay.core.pay.factory.PayStrategyFactory; import cn.bootx.platform.daxpay.core.pay.func.AbsPayStrategy; -import cn.bootx.platform.daxpay.core.pay.result.PaySyncResult; import cn.bootx.platform.daxpay.core.payment.entity.Payment; import cn.bootx.platform.daxpay.core.payment.service.PaymentService; -import cn.bootx.platform.daxpay.exception.payment.PayFailureException; -import cn.bootx.platform.daxpay.exception.payment.PayUnsupportedMethodException; +import cn.bootx.platform.daxpay.core.sync.result.PaySyncResult; import cn.bootx.platform.daxpay.mq.PaymentEventSender; -import cn.bootx.platform.daxpay.param.pay.PayWayParam; -import cn.bootx.platform.daxpay.param.pay.PayParam; -import cn.bootx.platform.daxpay.util.PayWaylUtil; -import cn.hutool.core.collection.CollUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; @@ -49,63 +40,63 @@ public class PayExpiredTimeService { @Transactional(rollbackFor = Exception.class) public void expiredTime(Long paymentId) { - Payment payment = paymentService.findById(paymentId).orElseThrow(() -> new PayFailureException("支付单未找到")); - // 只处理支付中 - if (!Objects.equals(payment.getPayStatus(), TRADE_PROGRESS)) { - return; - } - // 获取支付网关状态 - PayParam payParam = PaymentBuilder.buildPayParamByPayment(payment); - // 1.获取支付方式,通过工厂生成对应的策略组 - List paymentStrategyList = PayStrategyFactory.create(payParam.getPayWayList()); - if (CollUtil.isEmpty(paymentStrategyList)) { - throw new PayUnsupportedMethodException(); - } - - // 2.初始化支付的参数 - for (AbsPayStrategy paymentStrategy : paymentStrategyList) { - paymentStrategy.initPayParam(payment, payParam); - } - - // 3 拿到异步支付方法, 与支付网关进行同步 - PayWayParam asyncPayMode = PayWaylUtil.getAsyncPayModeParam(payParam); - AbsPayStrategy syncPayStrategy = PayStrategyFactory.create(asyncPayMode); - syncPayStrategy.initPayParam(payment, payParam); - PaySyncResult paySyncResult = syncPayStrategy.doSyncPayStatusHandler(); - - // 4 对返回的支付网关各种状态进行处理 - String paySyncStatus = paySyncResult.getPaySyncStatus(); - switch (paySyncStatus) { - // 成功状态 - case PaySyncStatus.TRADE_SUCCESS: { - this.paySuccess(payment, syncPayStrategy, paySyncResult); - break; - } - // 待付款/ 支付中 - case PaySyncStatus.WAIT_BUYER_PAY: { - this.payCancel(payment, paymentStrategyList); - break; - } - // 超时关闭 和 网关没找到记录 - case PaySyncStatus.TRADE_CLOSED: - case PaySyncStatus.NOT_FOUND: { - this.payClose(payment, paymentStrategyList); - break; - } - // 交易退款 - case PaySyncStatus.TRADE_REFUND: { - log.info("交易退款不需要关闭: {}", payment.getId()); - break; - } - // 调用出错 进行重试 - case PaySyncStatus.FAIL: { - log.warn("支付状态同步接口调用出错"); - } - case PaySyncStatus.NOT_SYNC: - default: { - log.error("支付超时代码有问题"); - } - } +// Payment payment = paymentService.findById(paymentId).orElseThrow(() -> new PayFailureException("支付单未找到")); +// // 只处理支付中 +// if (!Objects.equals(payment.getPayStatus(), TRADE_PROGRESS)) { +// return; +// } +// // 获取支付网关状态 +// PayParam payParam = PaymentBuilder.buildPayParamByPayment(payment); +// // 1.获取支付方式,通过工厂生成对应的策略组 +// List paymentStrategyList = PayStrategyFactory.create(payParam.getPayWayList()); +// if (CollUtil.isEmpty(paymentStrategyList)) { +// throw new PayUnsupportedMethodException(); +// } +// +// // 2.初始化支付的参数 +// for (AbsPayStrategy paymentStrategy : paymentStrategyList) { +// paymentStrategy.initPayParam(payment, payParam); +// } +// +// // 3 拿到异步支付方法, 与支付网关进行同步 +// PayWayParam asyncPayMode = PayWaylUtil.getAsyncPayModeParam(payParam); +// AbsPayStrategy syncPayStrategy = PayStrategyFactory.create(asyncPayMode); +// syncPayStrategy.initPayParam(payment, payParam); +// PaySyncResult paySyncResult = syncPayStrategy.doSyncPayStatusHandler(); +// +// // 4 对返回的支付网关各种状态进行处理 +// String paySyncStatus = paySyncResult.getPaySyncStatus(); +// switch (paySyncStatus) { +// // 成功状态 +// case PaySyncStatus.TRADE_SUCCESS: { +// this.paySuccess(payment, syncPayStrategy, paySyncResult); +// break; +// } +// // 待付款/ 支付中 +// case PaySyncStatus.WAIT_BUYER_PAY: { +// this.payCancel(payment, paymentStrategyList); +// break; +// } +// // 超时关闭 和 网关没找到记录 +// case PaySyncStatus.TRADE_CLOSED: +// case PaySyncStatus.NOT_FOUND: { +// this.payClose(payment, paymentStrategyList); +// break; +// } +// // 交易退款 +// case PaySyncStatus.TRADE_REFUND: { +// log.info("交易退款不需要关闭: {}", payment.getId()); +// break; +// } +// // 调用出错 进行重试 +// case PaySyncStatus.FAIL: { +// log.warn("支付状态同步接口调用出错"); +// } +// case PaySyncStatus.NOT_SYNC: +// default: { +// log.error("支付超时代码有问题"); +// } +// } } /** diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/service/PaySyncService.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/service/PaySyncService.java similarity index 85% rename from dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/service/PaySyncService.java rename to dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/service/PaySyncService.java index b746b8348..0bb478723 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/pay/service/PaySyncService.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/service/PaySyncService.java @@ -1,4 +1,4 @@ -package cn.bootx.platform.daxpay.core.pay.service; +package cn.bootx.platform.daxpay.core.sync.service; import cn.bootx.platform.common.core.exception.BizException; import cn.bootx.platform.daxpay.code.pay.PaySyncStatus; @@ -6,14 +6,17 @@ import cn.bootx.platform.daxpay.core.pay.builder.PayEventBuilder; import cn.bootx.platform.daxpay.core.pay.builder.PaymentBuilder; import cn.bootx.platform.daxpay.core.pay.factory.PayStrategyFactory; import cn.bootx.platform.daxpay.core.pay.func.AbsPayStrategy; -import cn.bootx.platform.daxpay.core.pay.result.PaySyncResult; import cn.bootx.platform.daxpay.core.payment.entity.Payment; import cn.bootx.platform.daxpay.core.payment.service.PaymentService; +import cn.bootx.platform.daxpay.core.sync.factory.PaySyncStrategyFactory; +import cn.bootx.platform.daxpay.core.sync.func.AbsPaySyncStrategy; +import cn.bootx.platform.daxpay.core.sync.record.service.PaySyncRecordService; +import cn.bootx.platform.daxpay.core.sync.result.PaySyncResult; import cn.bootx.platform.daxpay.exception.payment.PayFailureException; import cn.bootx.platform.daxpay.exception.payment.PayUnsupportedMethodException; import cn.bootx.platform.daxpay.mq.PaymentEventSender; -import cn.bootx.platform.daxpay.param.pay.PayWayParam; import cn.bootx.platform.daxpay.param.pay.PayParam; +import cn.bootx.platform.daxpay.param.pay.PayWayParam; import cn.bootx.platform.daxpay.util.PayWaylUtil; import cn.hutool.core.collection.CollUtil; import lombok.RequiredArgsConstructor; @@ -39,6 +42,8 @@ public class PaySyncService { private final PaymentService paymentService; + private final PaySyncRecordService paySyncRecordService; + private final PaymentEventSender eventSender; /** @@ -67,25 +72,39 @@ public class PaySyncService { * 同步支付状态 传入 payment 对象 */ public void syncPayment(Payment payment) { + // 获取同步策略类 + AbsPaySyncStrategy syncPayStrategy = PaySyncStrategyFactory.create(payment.getAsyncPayChannel()); + syncPayStrategy.initPayParam(payment); + // 同步 + PaySyncResult paySyncResult = syncPayStrategy.doSyncPayStatusHandler(); + // 处理 + this.resultHandler(paySyncResult,payment); + // 记录 + paySyncRecordService.saveRecord(paySyncResult,payment); + } + + /** + * 对同步结果进行处理 + */ + public void resultHandler(PaySyncResult paySyncResult, Payment payment){ + String paySyncStatus = paySyncResult.getPaySyncStatus(); PayParam payParam = PaymentBuilder.buildPayParamByPayment(payment); - // 1.获取支付方式,通过工厂生成对应的策略组 + // 获取支付方式,通过工厂生成对应的策略组 List paymentStrategyList = PayStrategyFactory.create(payParam.getPayWayList()); if (CollUtil.isEmpty(paymentStrategyList)) { throw new PayUnsupportedMethodException(); } - // 2.初始化支付的参数 + // 初始化支付的参数 for (AbsPayStrategy paymentStrategy : paymentStrategyList) { paymentStrategy.initPayParam(payment, payParam); } - // 3 拿到异步支付方法, 与支付网关进行同步 + // 拿到对应的支付方式 PayWayParam asyncPayMode = PayWaylUtil.getAsyncPayModeParam(payParam); AbsPayStrategy syncPayStrategy = PayStrategyFactory.create(asyncPayMode); - syncPayStrategy.initPayParam(payment, payParam); - PaySyncResult paySyncResult = syncPayStrategy.doSyncPayStatusHandler(); - String paySyncStatus = paySyncResult.getPaySyncStatus(); + // 对同步结果处理 switch (paySyncStatus) { // 支付成功 支付宝退款时也是支付成功状态, 除非支付完成 case PaySyncStatus.TRADE_SUCCESS: { diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/strategy/AliPaySyncStrategy.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/strategy/AliPaySyncStrategy.java new file mode 100644 index 000000000..46756a3b0 --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/strategy/AliPaySyncStrategy.java @@ -0,0 +1,53 @@ +package cn.bootx.platform.daxpay.core.sync.strategy; + + +import cn.bootx.platform.daxpay.core.channel.alipay.dao.AlipayConfigManager; +import cn.bootx.platform.daxpay.core.channel.alipay.entity.AlipayConfig; +import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipayConfigService; +import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipaySyncService; +import cn.bootx.platform.daxpay.core.sync.func.AbsPaySyncStrategy; +import cn.bootx.platform.daxpay.core.sync.result.PaySyncResult; +import cn.bootx.platform.daxpay.exception.payment.PayFailureException; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +/** + * 支付宝支付同步 + * @author xxm + * @since 2023/7/14 + */ +@Scope(SCOPE_PROTOTYPE) +@Component +@RequiredArgsConstructor +public class AliPaySyncStrategy extends AbsPaySyncStrategy { + + private final AlipayConfigManager alipayConfigManager; + + private final AlipaySyncService alipaySyncService; + + private final AlipayConfigService alipayConfigService; + + private AlipayConfig alipayConfig; + + /** + * 异步支付单与支付网关进行状态比对 + */ + @Override + public PaySyncResult doSyncPayStatusHandler() { + this.initAlipayConfig(this.getPayment().getMchAppCode()); + return alipaySyncService.syncPayStatus(this.getPayment()); + } + + /** + * 初始化支付宝配置信息 + */ + private void initAlipayConfig(String mchAppCode) { + // 检查并获取支付宝支付配置 + this.alipayConfig = alipayConfigManager.findByMchAppCode(mchAppCode) + .orElseThrow(() -> new PayFailureException("支付配置不存在")); + alipayConfigService.initApiConfig(this.alipayConfig); + } +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/strategy/WeChatPaySyncStrategy.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/strategy/WeChatPaySyncStrategy.java new file mode 100644 index 000000000..08735d733 --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/core/sync/strategy/WeChatPaySyncStrategy.java @@ -0,0 +1,49 @@ +package cn.bootx.platform.daxpay.core.sync.strategy; + +import cn.bootx.platform.daxpay.core.channel.wechat.dao.WeChatPayConfigManager; +import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig; +import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPaySyncService; +import cn.bootx.platform.daxpay.core.sync.func.AbsPaySyncStrategy; +import cn.bootx.platform.daxpay.core.sync.result.PaySyncResult; +import cn.bootx.platform.daxpay.exception.payment.PayFailureException; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +/** + * 微信支付同步 + * @author xxm + * @since 2023/7/14 + */ +@Scope(SCOPE_PROTOTYPE) +@Component +@RequiredArgsConstructor +public class WeChatPaySyncStrategy extends AbsPaySyncStrategy { + + private final WeChatPayConfigManager weChatPayConfigManager; + + private final WeChatPaySyncService weChatPaySyncService; + + private WeChatPayConfig weChatPayConfig; + + /** + * 异步支付单与支付网关进行状态比对 + */ + @Override + public PaySyncResult doSyncPayStatusHandler() { + // 检查并获取微信支付配置 + this.initWeChatPayConfig(this.getPayment().getMchAppCode()); + return weChatPaySyncService.syncPayStatus(this.getPayment().getId(), this.weChatPayConfig); + } + + /** + * 初始化微信支付 + */ + private void initWeChatPayConfig(String appCode) { + // 检查并获取微信支付配置 + this.weChatPayConfig = weChatPayConfigManager.findByMchAppCode(appCode) + .orElseThrow(() -> new PayFailureException("支付配置不存在")); + } +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/dto/refund/RefundRecordDto.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/dto/refund/PayRefundRecordDto.java similarity index 97% rename from dax-pay/src/main/java/cn/bootx/platform/daxpay/dto/refund/RefundRecordDto.java rename to dax-pay/src/main/java/cn/bootx/platform/daxpay/dto/refund/PayRefundRecordDto.java index 42d678b84..145d0c50e 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/dto/refund/RefundRecordDto.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/dto/refund/PayRefundRecordDto.java @@ -22,7 +22,7 @@ import java.util.List; @Data @Accessors(chain = true) @Schema(title = "退款记录") -public class RefundRecordDto extends BaseDto { +public class PayRefundRecordDto extends BaseDto { @Schema(description = "关联的业务id") private String businessId; diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/dto/sync/PaySyncRecordDto.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/dto/sync/PaySyncRecordDto.java new file mode 100644 index 000000000..0cac84906 --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/dto/sync/PaySyncRecordDto.java @@ -0,0 +1,61 @@ +package cn.bootx.platform.daxpay.dto.sync; + +import cn.bootx.mybatis.table.modify.annotation.DbComment; +import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.DbMySqlFieldType; +import cn.bootx.mybatis.table.modify.mybatis.mysq.constants.MySqlFieldTypeEnum; +import cn.bootx.platform.common.core.rest.dto.BaseDto; +import cn.bootx.platform.daxpay.code.pay.PayChannelEnum; +import cn.bootx.platform.daxpay.code.pay.PaySyncStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +/** + * 支付同步记录 + * @author xxm + * @since 2023/7/14 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@Schema(title = "支付同步记录") +public class PaySyncRecordDto extends BaseDto { + + /** 支付记录id */ + @DbComment("支付记录id") + private Long paymentId; + + /** 商户编码 */ + @DbComment("商户编码") + private String mchCode; + + /** 商户应用编码 */ + @DbComment("商户应用编码") + private String mchAppCode; + + /** + * 支付通道 + * @see PayChannelEnum#getCode() + */ + @DbComment("支付通道") + private String payChannel; + + /** 通知消息 */ + @DbMySqlFieldType(MySqlFieldTypeEnum.LONGTEXT) + @DbComment("通知消息") + private String syncInfo; + + /** + * 同步状态 + * @see PaySyncStatus#WAIT_BUYER_PAY + */ + @DbComment("同步状态") + private String status; + + /** 同步时间 */ + @DbComment("同步时间") + private LocalDateTime syncTime; +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/mq/PaymentMessageListener.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/mq/PaymentMessageListener.java index 65452e1cb..846177d1b 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/mq/PaymentMessageListener.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/mq/PaymentMessageListener.java @@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.mq; import cn.bootx.platform.common.rabbit.conditional.ConditionalOnRabbit; import cn.bootx.platform.daxpay.code.PaymentEventCode; -import cn.bootx.platform.daxpay.core.pay.service.PayExpiredTimeService; +import cn.bootx.platform.daxpay.core.sync.service.PayExpiredTimeService; import cn.bootx.platform.daxpay.event.PayCancelEvent; import cn.bootx.platform.daxpay.event.PayCompleteEvent; import cn.bootx.platform.daxpay.event.PayRefundEvent; diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/param/channel/voucher/VoucherChangeParam.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/param/channel/voucher/VoucherChangeParam.java new file mode 100644 index 000000000..5e2191f8a --- /dev/null +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/param/channel/voucher/VoucherChangeParam.java @@ -0,0 +1,57 @@ +package cn.bootx.platform.daxpay.param.channel.voucher; + +import cn.bootx.platform.daxpay.code.paymodel.VoucherCode; +import cn.hutool.core.date.DatePattern; +import com.alibaba.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * + * @author xxm + * @since 2023/7/13 + */ +@Data +@Accessors(chain = true) +@Schema(title = "储值卡信息更改参数") +public class VoucherChangeParam { + + @ExcelProperty("卡号") + @Schema(description = "卡号") + private String cardNo; + + @ExcelProperty("卡号") + @Schema(description = "面值") + private BigDecimal faceValue; + + @ExcelProperty("卡号") + @Schema(description = "余额") + private BigDecimal balance; + + @ExcelProperty("卡号") + @Schema(description = "是否长期有效") + private Boolean enduring; + + @ExcelProperty("开始时间") + @Schema(description = "开始时间") + @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + private LocalDateTime startTime; + + @ExcelProperty("结束时间") + @Schema(description = "结束时间") + @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + private LocalDateTime endTime; + + /** + * @see VoucherCode#STATUS_NORMAL + */ + @ExcelProperty("默认状态") + @Schema(description = "默认状态") + private String status; + +} diff --git a/dax-pay/src/main/java/cn/bootx/platform/daxpay/param/channel/voucher/VoucherImportParam.java b/dax-pay/src/main/java/cn/bootx/platform/daxpay/param/channel/voucher/VoucherImportParam.java index 9dcac1aef..ac50888b9 100644 --- a/dax-pay/src/main/java/cn/bootx/platform/daxpay/param/channel/voucher/VoucherImportParam.java +++ b/dax-pay/src/main/java/cn/bootx/platform/daxpay/param/channel/voucher/VoucherImportParam.java @@ -1,16 +1,54 @@ package cn.bootx.platform.daxpay.param.channel.voucher; +import cn.bootx.platform.daxpay.code.paymodel.VoucherCode; +import cn.hutool.core.date.DatePattern; +import com.alibaba.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import lombok.experimental.Accessors; + +import java.math.BigDecimal; +import java.time.LocalDateTime; /** * @author xxm * @since 2022/3/14 */ @Data -@Accessors(chain = true) @Schema(title = "储值卡导入参数") public class VoucherImportParam { + @ExcelProperty("卡号") + @Schema(description = "卡号") + private String cardNo; + + @ExcelProperty("面值") + @Schema(description = "面值") + private BigDecimal faceValue; + + @ExcelProperty("余额") + @Schema(description = "余额") + private BigDecimal balance; + + @ExcelProperty("是否长期有效") + @Schema(description = "是否长期有效") + private Boolean enduring; + + @ExcelProperty("开始时间") + @Schema(description = "开始时间") + @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + private LocalDateTime startTime; + + @ExcelProperty("结束时间") + @Schema(description = "结束时间") + @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + private LocalDateTime endTime; + + /** + * @see VoucherCode#STATUS_NORMAL + */ + @ExcelProperty("默认状态") + @Schema(description = "默认状态") + private String status; + } diff --git a/dax-pay/src/main/resources/template/import/ImportVoucher.xlsx b/dax-pay/src/main/resources/template/import/ImportVoucher.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2707d64f8b169c5c83e985a5a9dd79384b43ae0b GIT binary patch literal 10060 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 Rz?TFjL^OEMS#KV{{T~MdOfLWc literal 0 HcmV?d00001