From 862279391f359f5ab40281f37e78388342466694 Mon Sep 17 00:00:00 2001 From: jkcdev Date: Sun, 27 Apr 2025 17:50:45 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E6=95=B4=E4=BD=93=E6=A1=86=E6=9E=B6=E5=B7=B2=E7=BB=8F=E5=AE=8C?= =?UTF-8?q?=E6=88=90=EF=BC=8C=E7=9B=B4=E6=8E=A5=E4=BD=BF=E7=94=A8JavaSDK-v?= =?UTF-8?q?3=E5=AE=9E=E7=8E=B0=EF=BC=8C=E5=8C=85=E6=8B=AC=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=9A=84=E5=B0=81=E8=A3=85=E3=80=81=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E7=9A=84=E4=BA=A4=E4=BA=92=EF=BC=9B=E8=BF=98=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E5=AE=8C=E5=96=84=E4=BB=98=E6=AC=BE=E3=80=81=E9=80=80?= =?UTF-8?q?=E6=AC=BE=E5=9B=9E=E8=B0=83=E7=9A=84=E5=AE=9E=E7=8E=B0=EF=BC=8C?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E8=BD=AE=E8=AE=AD=E7=9A=84=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ebike-payment/pom.xml | 172 ++++++++++++ .../cdzy/payment/EbikePaymentApplication.java | 23 ++ .../config/MyBatisFlexConfiguration.java | 41 +++ .../payment/config/PermissionDialect.java | 24 ++ .../com/cdzy/payment/config/WxPayConfig.java | 90 +++++++ .../controller/EbikeWxPaymentController.java | 90 +++++++ .../payment/mapper/EbikePaymentMapper.java | 14 + .../payment/mapper/EbikeRefundMapper.java | 14 + .../com/cdzy/payment/model/dto/AmountDto.java | 28 ++ .../payment/model/dto/AmountRefundDto.java | 32 +++ .../com/cdzy/payment/model/dto/DetailDto.java | 33 +++ .../payment/model/dto/EbikePaymentDto.java | 46 ++++ .../payment/model/dto/EbikeRefundDto.java | 38 +++ .../cdzy/payment/model/dto/PayDetailDto.java | 36 +++ .../payment/model/dto/WxJsapiPromptDto.java | 69 +++++ .../payment/model/entity/EbikePayment.java | 83 ++++++ .../payment/model/entity/EbikeRefund.java | 89 +++++++ .../cdzy/payment/model/enums/PayMethod.java | 15 ++ .../payment/service/EbikePaymentService.java | 32 +++ .../payment/service/EbikeRefundService.java | 41 +++ .../cdzy/payment/service/WxPayService.java | 73 ++++++ .../service/impl/EbikePaymentServiceImpl.java | 63 +++++ .../service/impl/EbikeRefundServiceImpl.java | 60 +++++ .../service/impl/WxPayServiceImpl.java | 245 ++++++++++++++++++ .../java/com/cdzy/payment/task/WsPayTask.java | 79 ++++++ .../com/cdzy/payment/utils/StringUtil.java | 46 ++++ .../src/main/resources/application-dev.yml | 43 +++ .../src/main/resources/application-prod.yml | 10 + .../src/main/resources/application-test.yml | 39 +++ .../src/main/resources/application.yml | 3 + pom.xml | 8 + 31 files changed, 1679 insertions(+) create mode 100644 ebike-payment/pom.xml create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/EbikePaymentApplication.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/config/MyBatisFlexConfiguration.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/config/PermissionDialect.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/config/WxPayConfig.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/controller/EbikeWxPaymentController.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/mapper/EbikePaymentMapper.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/mapper/EbikeRefundMapper.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/model/dto/AmountDto.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/model/dto/AmountRefundDto.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/model/dto/DetailDto.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/model/dto/EbikePaymentDto.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/model/dto/EbikeRefundDto.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/model/dto/PayDetailDto.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/model/dto/WxJsapiPromptDto.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/model/entity/EbikePayment.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/model/entity/EbikeRefund.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/model/enums/PayMethod.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/service/EbikePaymentService.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/service/EbikeRefundService.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/service/WxPayService.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/service/impl/EbikePaymentServiceImpl.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/service/impl/EbikeRefundServiceImpl.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/service/impl/WxPayServiceImpl.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/task/WsPayTask.java create mode 100644 ebike-payment/src/main/java/com/cdzy/payment/utils/StringUtil.java create mode 100644 ebike-payment/src/main/resources/application-dev.yml create mode 100644 ebike-payment/src/main/resources/application-prod.yml create mode 100644 ebike-payment/src/main/resources/application-test.yml create mode 100644 ebike-payment/src/main/resources/application.yml diff --git a/ebike-payment/pom.xml b/ebike-payment/pom.xml new file mode 100644 index 00000000..2bc30f88 --- /dev/null +++ b/ebike-payment/pom.xml @@ -0,0 +1,172 @@ + + + 4.0.0 + + com.cdzy + ebike-share + 0.0.1-SNAPSHOT + + ebike-payment + 0.0.1-SNAPSHOT + ebike-payment + ebike-payment-process + jar + + + 17 + 17 + UTF-8 + + + + + + com.cdzy + ebike-common + 0.0.1-SNAPSHOT + + + com.cdzy + ebike-feign + 0.0.1-SNAPSHOT + + + + com.github.wechatpay-apiv3 + wechatpay-java + 0.2.17 + + + + com.mybatis-flex + mybatis-flex-spring-boot3-starter + ${mybatis-flex.version} + + + com.mybatis-flex + mybatis-flex-processor + ${mybatis-flex.version} + provided + + + org.projectlombok + lombok + ${org.projectlombok.version} + + + + com.zaxxer + HikariCP + ${HikariCP.version} + + + + org.mybatis + mybatis-spring + ${mybatis.version} + + + + cn.hutool + hutool-core + ${hutool.version} + + + + org.springframework.boot + spring-boot-starter-validation + ${boot.version} + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + com.squareup.okhttp3 + okhttp + ${okhttp.version} + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson.version} + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + ${nacos.version} + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + com.mysql + mysql-connector-j + ${mysql.version} + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + com.mybatis-flex + mybatis-flex-codegen + 1.10.8 + test + + + + + + + dev + + dev + + true + + + + + + + prod + + prod + + + + + test + + test + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${boot.version} + + + + repackage + + + + + + + \ No newline at end of file diff --git a/ebike-payment/src/main/java/com/cdzy/payment/EbikePaymentApplication.java b/ebike-payment/src/main/java/com/cdzy/payment/EbikePaymentApplication.java new file mode 100644 index 00000000..feceec6a --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/EbikePaymentApplication.java @@ -0,0 +1,23 @@ +package com.cdzy.payment; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * 支付模块 + * + * @author dingchao + * @date 2025/4/24 + * @modified by: + */ +@SpringBootApplication +@MapperScan("com.cdzy.payment.mapper") +//引入Spring Task +@EnableScheduling +public class EbikePaymentApplication { + public static void main(String[] args) { + SpringApplication.run(EbikePaymentApplication.class, args); + } +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/config/MyBatisFlexConfiguration.java b/ebike-payment/src/main/java/com/cdzy/payment/config/MyBatisFlexConfiguration.java new file mode 100644 index 00000000..50c5e58d --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/config/MyBatisFlexConfiguration.java @@ -0,0 +1,41 @@ +package com.cdzy.payment.config; + +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.core.FlexGlobalConfig; +import com.mybatisflex.core.audit.AuditManager; +import com.mybatisflex.core.dialect.DbType; +import com.mybatisflex.core.dialect.DialectFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; + +/** + * @author attiya + */ +@Configuration +public class MyBatisFlexConfiguration { + + private static final Logger logger = LoggerFactory + .getLogger("mybatis-flex-sql:"); + + + public MyBatisFlexConfiguration() { + //开启审计功能 + AuditManager.setAuditEnable(true); + + //设置 SQL 审计收集器 + AuditManager.setMessageCollector(auditMessage -> + logger.info("{},{}ms", auditMessage.getFullSql() + , auditMessage.getElapsedTime()) + ); + + //全局ID生成策略配置 + FlexGlobalConfig.KeyConfig keyConfig = new FlexGlobalConfig.KeyConfig(); + keyConfig.setKeyType(KeyType.Generator); + keyConfig.setValue("snowFlakeId"); + keyConfig.setBefore(true); + FlexGlobalConfig.getDefaultConfig().setKeyConfig(keyConfig); + + DialectFactory.registerDialect(DbType.MYSQL,new PermissionDialect()); + } +} \ No newline at end of file diff --git a/ebike-payment/src/main/java/com/cdzy/payment/config/PermissionDialect.java b/ebike-payment/src/main/java/com/cdzy/payment/config/PermissionDialect.java new file mode 100644 index 00000000..d3d8246b --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/config/PermissionDialect.java @@ -0,0 +1,24 @@ +package com.cdzy.payment.config; + +import com.mybatisflex.core.dialect.impl.CommonsDialectImpl; +import com.mybatisflex.core.query.CPI; +import com.mybatisflex.core.query.QueryTable; +import com.mybatisflex.core.query.QueryWrapper; + +import java.util.List; + +/** + * @author attiya + * @since 2025-03-14 + */ +public class PermissionDialect extends CommonsDialectImpl { + + @Override + public String forSelectByQuery(QueryWrapper queryWrapper) { + + //用于严重table是否需要添加数据权限查询条件 + List tables = CPI.getQueryTables(queryWrapper); + //获取当前用户信息,为 queryWrapper 添加额外的条件 + return super.buildSelectSql(queryWrapper); + } +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/config/WxPayConfig.java b/ebike-payment/src/main/java/com/cdzy/payment/config/WxPayConfig.java new file mode 100644 index 00000000..1187afc6 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/config/WxPayConfig.java @@ -0,0 +1,90 @@ +package com.cdzy.payment.config; + +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.RSAAutoCertificateConfig; +import com.wechat.pay.java.core.cipher.RSASigner; +import com.wechat.pay.java.core.util.PemUtil; +import com.wechat.pay.java.service.payments.jsapi.JsapiService; +import com.wechat.pay.java.service.refund.RefundService; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.security.PrivateKey; + +/** + * 微信支付配置参数 + * + * @author dingchao + * @date 2025/4/24 + * @modified by: + */ +@Slf4j +@Data +@Configuration +@ConfigurationProperties(prefix = "payment.wx-pay") +public class WxPayConfig { + /** + * 微信小程序 appId + */ + private String appId; + /** + * 微信支付商户号 + */ + private String merchantId; + /** + * 微信支付商户密钥文件路径 + */ + private String privateKeyPath; + /** + * 微信支付商户证书序列号 + */ + private String merchantSerialNumber; + /** + * 微信支付商户 APIv3 密钥 + */ + private String apiV3Key; + /** + * 微信支付回调地址 + */ + private String payNotifyUrl; + /** + * 微信支付退款回调地址 + */ + private String refundNotifyUrl; + /** + * 支付、退款过期时间(分钟) + * 默认30分钟,超过30分钟订单会自动关闭 + */ + private Integer expireMinute = 30; + + @Bean("certConfig") + private Config certificateConfig() { + return new RSAAutoCertificateConfig.Builder() + .merchantId(merchantId) + // 使用 com.wechat.pay.java.core.util 中的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名 + .privateKeyFromPath(privateKeyPath) + .merchantSerialNumber(merchantSerialNumber) + .apiV3Key(apiV3Key) + .build(); + } + + @Bean + public JsapiService wxJsapiService(@Qualifier("certConfig") Config certificateConfig) { + return new JsapiService.Builder().config(certificateConfig).build(); + } + + @Bean + public RefundService wxRefundService(@Qualifier("certConfig") Config certificateConfig) { + return new RefundService.Builder().config(certificateConfig).build(); + } + + @Bean + public RSASigner wxRsaSigner(){ + PrivateKey privateKey = PemUtil.loadPrivateKeyFromPath(privateKeyPath); + return new RSASigner(merchantSerialNumber, privateKey); + } +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/controller/EbikeWxPaymentController.java b/ebike-payment/src/main/java/com/cdzy/payment/controller/EbikeWxPaymentController.java new file mode 100644 index 00000000..b091125f --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/controller/EbikeWxPaymentController.java @@ -0,0 +1,90 @@ +package com.cdzy.payment.controller; + +import com.alibaba.fastjson2.JSONObject; +import com.cdzy.common.model.JsonResult; +import com.cdzy.payment.model.dto.EbikePaymentDto; +import com.cdzy.payment.model.dto.EbikeRefundDto; +import com.cdzy.payment.service.WxPayService; +import com.wechat.pay.java.service.payments.model.Transaction; +import com.wechat.pay.java.service.refund.model.Refund; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 用户订单微信支付 控制层。 + * + * @author dingchao + * @since 2025-04-24 + */ +@RestController +@RequestMapping("/wxPayment") +public class EbikeWxPaymentController { + + @Resource + private WxPayService wxPayService; + + /** + * 微信支付下单 + * + * @param paymentDto 支付信息 + * @return 下单成功返回true,否则返回false + */ + @PostMapping("/prepay") + public JsonResult prepay(@RequestBody EbikePaymentDto paymentDto) { + JSONObject r = wxPayService.prepay(paymentDto.getOrderId(), paymentDto.getDescription(), paymentDto.getGoodsTag(), paymentDto.getOpenId(), paymentDto.getAmount(), paymentDto.getDetail(), paymentDto.getClientIp()); + return r == null?JsonResult.failed("下单失败"):JsonResult.success(r); + } + + /** + * 通过交易订单号查询支付订单 + * + * @param transactionId 微信支付订单号 + * @return 支付订单信息 + */ + @GetMapping("/queryOrderById/{transactionId}") + public JsonResult queryOrderById(@PathVariable String transactionId) { + Transaction r = wxPayService.queryOrderById(transactionId); + return r == null?JsonResult.failed(String.format("交易订单号{%s}查询支付订单失败", transactionId)):JsonResult.success(r); + } + + /** + * 通过商户(骑行)订单号查询支付订单 + * + * @param outTradeNo 商户(骑行)订单号 + * @return 支付订单信息 + */ + @GetMapping("/queryOrderByOutTradeNo/{outTradeNo}") + public JsonResult queryOrderByOutTradeNo(@PathVariable String outTradeNo) { + Transaction r = wxPayService.queryOrderByOutTradeNo(outTradeNo); + return r == null?JsonResult.failed(String.format("骑行订单号{%s}查询支付订单失败", outTradeNo)):JsonResult.success(r); + } + + /** + * 退款申请 + * + * @param refundDto 退款信息 + * @return 退款成功返回true,否则返回false + */ + @PostMapping("/refund") + public JsonResult refund(@RequestBody EbikeRefundDto refundDto) { + String r = wxPayService.refund(refundDto.getPaymentId(), refundDto.getOrderId(), refundDto.getReason(), refundDto.getAmount()); + return r == null?JsonResult.failed("退款失败"):JsonResult.success(r); + } + + /** + * 通过退款单号查询退款信息 + * + * @param outRefundNo 商户(骑行)退款单号 + * @return 退款信息 + */ + @GetMapping("/refundQuery/{outRefundNo}") + public JsonResult refundQuery(@PathVariable String outRefundNo) { + Refund r = wxPayService.queryRefundByOutNo(outRefundNo); + return r == null?JsonResult.failed(String.format("退款单号{%s}查询退款失败", outRefundNo)):JsonResult.success(r); + } +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/mapper/EbikePaymentMapper.java b/ebike-payment/src/main/java/com/cdzy/payment/mapper/EbikePaymentMapper.java new file mode 100644 index 00000000..b904328f --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/mapper/EbikePaymentMapper.java @@ -0,0 +1,14 @@ +package com.cdzy.payment.mapper; + +import com.mybatisflex.core.BaseMapper; +import com.cdzy.payment.model.entity.EbikePayment; + +/** + * 用户订单支付记录 映射层。 + * + * @author dingchao + * @since 2025-04-24 + */ +public interface EbikePaymentMapper extends BaseMapper { + +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/mapper/EbikeRefundMapper.java b/ebike-payment/src/main/java/com/cdzy/payment/mapper/EbikeRefundMapper.java new file mode 100644 index 00000000..91c79b85 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/mapper/EbikeRefundMapper.java @@ -0,0 +1,14 @@ +package com.cdzy.payment.mapper; + +import com.mybatisflex.core.BaseMapper; +import com.cdzy.payment.model.entity.EbikeRefund; + +/** + * 用户订单退款记录 映射层。 + * + * @author dingchao + * @since 2025-04-25 + */ +public interface EbikeRefundMapper extends BaseMapper { + +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/model/dto/AmountDto.java b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/AmountDto.java new file mode 100644 index 00000000..6a322432 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/AmountDto.java @@ -0,0 +1,28 @@ +package com.cdzy.payment.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 支付金额信息 + * + * @author dingchao + * @date 2025/4/26 + * @modified by: + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AmountDto implements Serializable { + /** + * 订单金额(单位:元) + */ + private Double total; + /** + * 货币类型(默认:CNY) + */ + private String currency="CNY"; +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/model/dto/AmountRefundDto.java b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/AmountRefundDto.java new file mode 100644 index 00000000..b2c501d8 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/AmountRefundDto.java @@ -0,0 +1,32 @@ +package com.cdzy.payment.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 退款信息 (忽略来源明细) + * + * @author dingchao + * @date 2025/4/26 + * @modified by: + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AmountRefundDto implements Serializable{ + /** + * 退款金额,单位为元 + */ + private Double refund; + /** + * 原订单金额,单位为元 + */ + private Double total; + /** + * 退款币种 (默认CNY) + */ + private String currency = "CNY"; +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/model/dto/DetailDto.java b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/DetailDto.java new file mode 100644 index 00000000..90172c84 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/DetailDto.java @@ -0,0 +1,33 @@ +package com.cdzy.payment.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 支付明细信息 + * + * @author dingchao + * @date 2025/4/26 + * @modified by: + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DetailDto implements Serializable { + /** + * 订单原价(单位:元) + */ + private Double costPrice; + /** + * 商品小票ID(可选) + */ + private String invoiceId; + /** + * 收费明显列表 + */ + private List goodsDetail; +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/model/dto/EbikePaymentDto.java b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/EbikePaymentDto.java new file mode 100644 index 00000000..180114b5 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/EbikePaymentDto.java @@ -0,0 +1,46 @@ +package com.cdzy.payment.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户订单微信支付 请求类。 + * + * @author dingchao + * @date 2025/4/25 + * @modified by: + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EbikePaymentDto { + /** + * 商户(骑行)订单号 + */ + private String orderId; + /** + * 商品描述 + */ + private String description; + /** + * 商品标记 + */ + private String goodsTag; + /** + * 用户标识 + */ + private String openId; + /** + * 金额 + */ + private AmountDto amount; + /** + * 费用详情 + */ + private DetailDto detail; + /** + * 客户端IP + */ + private String clientIp; +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/model/dto/EbikeRefundDto.java b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/EbikeRefundDto.java new file mode 100644 index 00000000..bf3208c6 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/EbikeRefundDto.java @@ -0,0 +1,38 @@ +package com.cdzy.payment.model.dto; + +import com.wechat.pay.java.service.payments.jsapi.model.Amount; +import com.wechat.pay.java.service.payments.jsapi.model.Detail; +import com.wechat.pay.java.service.refund.model.AmountReq; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户订单退款 请求类。 + * + * @author dingchao + * @date 2025/4/25 + * @modified by: + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EbikeRefundDto { + /** + * 商户(骑行)订单号 + */ + private String orderId; + /** + * 支付交易会话标识 + */ + private String paymentId; + + /** + * 退款原因 + */; + private String reason; + /** + * 金额 + */ + private AmountRefundDto amount; +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/model/dto/PayDetailDto.java b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/PayDetailDto.java new file mode 100644 index 00000000..00151b91 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/PayDetailDto.java @@ -0,0 +1,36 @@ +package com.cdzy.payment.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 支付费用明细信息 + * + * @author dingchao + * @date 2025/4/26 + * @modified by: + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PayDetailDto implements Serializable { + /** + * 费用项目ID + */ + private String itemId; + /** + * 费用名称 + */ + private String itemName; + /** + * 数量 + */ + private Integer quantity; + /** + * 单价(单位:元) + */ + private Double unitPrice; +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/model/dto/WxJsapiPromptDto.java b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/WxJsapiPromptDto.java new file mode 100644 index 00000000..93896d5b --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/WxJsapiPromptDto.java @@ -0,0 +1,69 @@ +package com.cdzy.payment.model.dto; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.annotation.JSONField; +import com.wechat.pay.java.core.cipher.RSASigner; +import com.wechat.pay.java.core.cipher.SignatureResult; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.StringJoiner; + +/** + * JSAPI 调起微信支付参数 + * + * @author dingchao + * @date 2025/4/25 + * @modified by: + */ +@Slf4j +@Data +@NoArgsConstructor +public class WxJsapiPromptDto { + + /** + * 下单时传入的appid + */ + private String appId; + /** + * 传秒级时间戳 + */ + private String timeStamp; + /** + * 随机字符串 + */ + private String nonceStr; + /** + * 订单详情扩展,提交格式如:prepay_id=*** + */ + // json中转为 "package" + @JSONField(name = "package") + private String extension; + /** + * 签名类型,固定填RSA + */ + private String signType = "RSA"; + /** + * 签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值 + */ + private String paySign; + + public JSONObject toJson(RSASigner rsaSigner) { + try{ + // 计算签名 + StringJoiner signMsg = new StringJoiner("\n"); + signMsg.add(appId); + signMsg.add(timeStamp); + signMsg.add(nonceStr); + signMsg.add(extension); + SignatureResult sign = rsaSigner.sign(signMsg.toString()); + this.paySign = sign.getSign(); + //result.put("package", result.remove("extension")); + return JSONObject.parseObject(JSONObject.toJSONString(this)); + }catch (Exception e){ + log.error("计算签名失败", e); + throw new RuntimeException("计算签名失败", e); + } + } +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/model/entity/EbikePayment.java b/ebike-payment/src/main/java/com/cdzy/payment/model/entity/EbikePayment.java new file mode 100644 index 00000000..60b7aa64 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/model/entity/EbikePayment.java @@ -0,0 +1,83 @@ +package com.cdzy.payment.model.entity; + +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.Table; +import java.io.Serializable; +import java.time.LocalDateTime; + +import java.io.Serial; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户订单支付记录 实体类。 + * + * @author dingchao + * @since 2025-04-24 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table("ebike_payment") +public class EbikePayment implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @Id + private String recordId; + + /** + * 骑行订单号 + */ + @Id + private String orderId; + + /** + * 支付交易会话标识;有效期为2小时 + */ + private String paymentId; + + /** + * 提交时间 + */ + private LocalDateTime createTime; + + /** + * 支付成功时间 + */ + private LocalDateTime paymentTime; + + /** + * 支付方式;wechat/alipay/balance + */ + private String paymentMethod; + + /** + * 支付状态;0支付成功 1退款 2未支付 3关闭 4取消 5支付中 6支付错误 7接受 + */ + private String tradeState; + + + /** + * 订单原价 + */ + private Double costPrice; + /** + * 货币 + */ + private String currency; + + /** + * 总金额 + */ + private Double total; + +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/model/entity/EbikeRefund.java b/ebike-payment/src/main/java/com/cdzy/payment/model/entity/EbikeRefund.java new file mode 100644 index 00000000..9d22b73a --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/model/entity/EbikeRefund.java @@ -0,0 +1,89 @@ +package com.cdzy.payment.model.entity; + +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.Table; +import java.io.Serializable; +import java.math.BigDecimal; + +import java.io.Serial; +import java.time.LocalDateTime; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户订单退款记录 实体类。 + * + * @author dingchao + * @since 2025-04-25 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table("ebike_refund") +public class EbikeRefund implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @Id + private String refundId; + + /** + * 骑行订单号 + */ + private String orderId; + + /** + * 支付记录id + */ + private String recordId; + + /** + * 支付交易会话标识;有效期为2小时 + */ + private String paymentId; + + /** + * 提交时间 + */ + private LocalDateTime createTime; + + /** + * 退款成功时间 + */ + private LocalDateTime refundTime; + + + /** + * 支付状态;0退款成功 1关闭 2退款中 3异常 + */ + private String status; + + /** + * 货币 + */ + private String currency; + + /** + * 退款金额 + */ + private Double refund; + + /** + * 退款总金额 + */ + private Double total; + + /** + * 退款原因 + */ + private String reason; + +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/model/enums/PayMethod.java b/ebike-payment/src/main/java/com/cdzy/payment/model/enums/PayMethod.java new file mode 100644 index 00000000..4a5feb2e --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/model/enums/PayMethod.java @@ -0,0 +1,15 @@ +package com.cdzy.payment.model.enums; + +import com.google.gson.annotations.SerializedName; + +public enum PayMethod { + @SerializedName("wechat") + wechat, + @SerializedName("alipay") + alipay, + @SerializedName("balance") + balance; + + private PayMethod() { + } +} \ No newline at end of file diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/EbikePaymentService.java b/ebike-payment/src/main/java/com/cdzy/payment/service/EbikePaymentService.java new file mode 100644 index 00000000..330393ac --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/EbikePaymentService.java @@ -0,0 +1,32 @@ +package com.cdzy.payment.service; + +import com.mybatisflex.core.service.IService; +import com.cdzy.payment.model.entity.EbikePayment; +import com.wechat.pay.java.service.payments.model.Transaction; + +import java.util.List; + +/** + * 用户订单支付记录 服务层。 + * + * @author dingchao + * @since 2025-04-24 + */ +public interface EbikePaymentService extends IService { + /** + * 查询未支付订单 + * + * @param duration 订单创建时间超过duration分钟,单位分钟 + * @return 未支付订单列表 + */ + List getNoPayOrderByDuration(int duration); + + /** + * 更新支付状态 + * + * @param recordId 记录ID + * @param transaction 支付结果 + * @return 更新成功返回true,否则返回false + */ + Boolean updatePaymentStatus(String recordId, Transaction transaction); +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/EbikeRefundService.java b/ebike-payment/src/main/java/com/cdzy/payment/service/EbikeRefundService.java new file mode 100644 index 00000000..1e0b10b1 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/EbikeRefundService.java @@ -0,0 +1,41 @@ +package com.cdzy.payment.service; + +import com.mybatisflex.core.service.IService; +import com.cdzy.payment.model.entity.EbikeRefund; +import com.wechat.pay.java.service.payments.model.Transaction; +import com.wechat.pay.java.service.refund.model.Refund; + +import java.util.List; + +/** + * 用户订单退款记录 服务层。 + * + * @author dingchao + * @since 2025-04-25 + */ +public interface EbikeRefundService extends IService { + /** + * 保存退款记录 + * + * @param ebikeRefund 退款结果 + * @return 保存成功返回主键id,否则返回null + */ + Boolean saveRefundResult(EbikeRefund ebikeRefund); + + /** + * 查询未成功退款订单 + * + * @param duration 订单创建时间超过duration分钟,单位分钟 + * @return 未成功退款订单列表 + */ + List getNoSuccessRefundOrderByDuration(int duration); + + /** + * 更新退款状态 + * + * @param refundId 退款ID + * @param refund 退款结果 + * @return 更新成功返回true,否则返回false + */ + Boolean updateRefundStatus(String refundId, Refund refund); +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/WxPayService.java b/ebike-payment/src/main/java/com/cdzy/payment/service/WxPayService.java new file mode 100644 index 00000000..e2426ff6 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/WxPayService.java @@ -0,0 +1,73 @@ +package com.cdzy.payment.service; + + +import com.alibaba.fastjson2.JSONObject; +import com.cdzy.payment.model.dto.AmountDto; +import com.cdzy.payment.model.dto.AmountRefundDto; +import com.cdzy.payment.model.dto.DetailDto; +import com.wechat.pay.java.service.payments.model.Transaction; +import com.wechat.pay.java.service.refund.model.Refund; + +/** + * 微信支付服务类(JSAPI支付),小程序 + * + * @author dingchao + * @date 2025/4/25 + * @modified by: + */ +public interface WxPayService { + /** + * 关闭订单 + * + * @param outTradeNo 商户(骑行)订单号 + * @return + */ + boolean closeOrder(String outTradeNo); + /** + * JSAPI支付下单 + * + * @param outTradeNo 商户(骑行)订单号 + * @param description 商品描述 + * @param goodsTag 商品标记 + * @param openId 用户标识 + * @param amount 金额 + * @param detail 商品详情 + * @param clientIp 客户端IP + * @return 下单成功返回true,否则返回false + */ + JSONObject prepay(String outTradeNo, String description, String goodsTag, String openId, AmountDto amount, DetailDto detail, String clientIp); + /** + * 通过交易订单号查询支付订单 + * + * @param transactionId 微信支付订单号 + * @return 支付订单信息 + */ + Transaction queryOrderById(String transactionId); + /** + * 通过商户(骑行)订单号查询支付订单 + * + * @param outTradeNo 商户(骑行)订单号 + * @return 支付订单信息 + */ + Transaction queryOrderByOutTradeNo(String outTradeNo); + /** + * 退款申请 + * + * @param transactionId 微信支付订单号 + * @param outTradeNo 商户(骑行)订单号 + * @param reason 退款原因 + * @param amount 退款金额 + * @return 退款信息id + */ + String refund(String transactionId, String outTradeNo, String reason, AmountRefundDto amount); + + + /** + * 通过商户退款单号查询退款信息 + * + * @param outRefundNo 商户退款订单号 + * @return 退款信息 + */ + Refund queryRefundByOutNo(String outRefundNo); + +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/impl/EbikePaymentServiceImpl.java b/ebike-payment/src/main/java/com/cdzy/payment/service/impl/EbikePaymentServiceImpl.java new file mode 100644 index 00000000..7a615229 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/impl/EbikePaymentServiceImpl.java @@ -0,0 +1,63 @@ +package com.cdzy.payment.service.impl; + +import com.cdzy.payment.model.enums.PayMethod; +import com.ebike.feign.clients.OrdersFeignClient; +import com.ebike.feign.model.res.ResFeignOrderPaymentDto; +import com.mybatisflex.core.query.QueryColumn; +import com.mybatisflex.core.query.QueryMethods; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import com.cdzy.payment.model.entity.EbikePayment; +import com.cdzy.payment.mapper.EbikePaymentMapper; +import com.cdzy.payment.service.EbikePaymentService; +import com.wechat.pay.java.service.payments.model.Transaction; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +import static com.cdzy.payment.model.entity.table.EbikePaymentTableDef.EBIKE_PAYMENT; + +/** + * 用户订单支付记录 服务层实现。 + * + * @author dingchao + * @since 2025-04-24 + */ +@Service +public class EbikePaymentServiceImpl extends ServiceImpl implements EbikePaymentService{ + + @Resource + private OrdersFeignClient ordersFeignClient; + + @Override + public List getNoPayOrderByDuration(int duration) { + QueryColumn time = QueryMethods.secToTime(String.valueOf(duration * 60)); + // trade_state 等于 NOTPAY未支付的, 并且创建时间超过duration分钟的订单 + QueryWrapper query = QueryWrapper.create() + .where(EBIKE_PAYMENT.TRADE_STATE.eq(Transaction.TradeStateEnum.NOTPAY.ordinal())) + .and(QueryMethods.addTime(EBIKE_PAYMENT.CREATE_TIME, time).ge(QueryMethods.now())); + return list(query); + } + + @Override + public Boolean updatePaymentStatus(String recordId, Transaction transaction) { + EbikePayment ebikePayment = new EbikePayment(); + ebikePayment.setRecordId(recordId); + ebikePayment.setTradeState(String.valueOf(transaction.getTradeState().ordinal())); + if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) { + // 支付成功, 更新订单状态 + double v = transaction.getAmount().getTotal().doubleValue() / 100.0; + ebikePayment.setTotal(v); + ebikePayment.setPaymentTime(LocalDateTime.now()); + // 同步支付状态 + ResFeignOrderPaymentDto paymentParam = new ResFeignOrderPaymentDto(); + paymentParam.setOrderId(Long.valueOf(transaction.getOutTradeNo())); + paymentParam.setPaymentTime(ebikePayment.getPaymentTime()); + paymentParam.setPaymentMethod(PayMethod.wechat.name()); + ordersFeignClient.payment(paymentParam); + } + return updateById(ebikePayment); + } +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/impl/EbikeRefundServiceImpl.java b/ebike-payment/src/main/java/com/cdzy/payment/service/impl/EbikeRefundServiceImpl.java new file mode 100644 index 00000000..d08f348a --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/impl/EbikeRefundServiceImpl.java @@ -0,0 +1,60 @@ +package com.cdzy.payment.service.impl; + +import com.ebike.feign.clients.OrdersFeignClient; +import com.mybatisflex.core.query.QueryMethods; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.spring.service.impl.ServiceImpl; +import com.cdzy.payment.model.entity.EbikeRefund; +import com.cdzy.payment.mapper.EbikeRefundMapper; +import com.cdzy.payment.service.EbikeRefundService; +import com.wechat.pay.java.service.payments.model.Transaction; +import com.wechat.pay.java.service.refund.model.Refund; +import com.wechat.pay.java.service.refund.model.Status; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +import static com.cdzy.payment.model.entity.table.EbikeRefundTableDef.EBIKE_REFUND; + +/** + * 用户订单退款记录 服务层实现。 + * + * @author dingchao + * @since 2025-04-25 + */ +@Service +public class EbikeRefundServiceImpl extends ServiceImpl implements EbikeRefundService{ + + @Resource + private OrdersFeignClient ordersFeignClient; + + @Override + public Boolean saveRefundResult(EbikeRefund ebikeRefund) { + // 同步发起退款 + ordersFeignClient.refund(Long.valueOf(ebikeRefund.getOrderId())); + return save(ebikeRefund); + } + + @Override + public List getNoSuccessRefundOrderByDuration(int duration) { + // trade_state 不等于 SUCCESS退款成功的, 并且创建时间超过duration分钟的订单 + QueryWrapper query = QueryWrapper.create() + .where(EBIKE_REFUND.STATUS.ne(Status.SUCCESS.ordinal())) + .and(QueryMethods.addTime(EBIKE_REFUND.CREATE_TIME, QueryMethods.secToTime(String.valueOf(duration * 60))).ge(QueryMethods.now())); + return list(query); + } + + @Override + public Boolean updateRefundStatus(String refundId, Refund refund) { + EbikeRefund ebikeRefund = new EbikeRefund(); + ebikeRefund.setRefundId(refundId); + ebikeRefund.setStatus(String.valueOf(refund.getStatus().ordinal())); + if (Status.SUCCESS.equals(refund.getStatus())) { + ebikeRefund.setRefundTime(LocalDateTime.now()); + ebikeRefund.setRefund(refund.getAmount().getRefund().doubleValue() / 100.0); + } + return updateById(ebikeRefund); + } +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/impl/WxPayServiceImpl.java b/ebike-payment/src/main/java/com/cdzy/payment/service/impl/WxPayServiceImpl.java new file mode 100644 index 00000000..3ab79f41 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/impl/WxPayServiceImpl.java @@ -0,0 +1,245 @@ +package com.cdzy.payment.service.impl; + +import cn.hutool.core.util.RandomUtil; +import com.alibaba.fastjson2.JSONObject; +import com.cdzy.payment.config.WxPayConfig; +import com.cdzy.payment.model.dto.AmountDto; +import com.cdzy.payment.model.dto.AmountRefundDto; +import com.cdzy.payment.model.dto.DetailDto; +import com.cdzy.payment.model.dto.WxJsapiPromptDto; +import com.cdzy.payment.model.entity.EbikePayment; +import com.cdzy.payment.model.entity.EbikeRefund; +import com.cdzy.payment.model.enums.PayMethod; +import com.cdzy.payment.service.EbikePaymentService; +import com.cdzy.payment.service.EbikeRefundService; +import com.cdzy.payment.service.WxPayService; +import com.cdzy.payment.utils.StringUtil; +import com.wechat.pay.java.core.cipher.RSASigner; +import com.wechat.pay.java.core.exception.HttpException; +import com.wechat.pay.java.core.exception.MalformedMessageException; +import com.wechat.pay.java.core.exception.ServiceException; +import com.wechat.pay.java.service.payments.jsapi.model.*; +import com.wechat.pay.java.service.payments.jsapi.model.Amount; +import com.wechat.pay.java.service.payments.jsapi.model.GoodsDetail; +import com.wechat.pay.java.service.payments.model.Transaction; +import com.wechat.pay.java.service.refund.RefundService; +import com.wechat.pay.java.service.refund.model.*; +import com.wechat.pay.java.service.payments.jsapi.JsapiService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * 微信支付服务类(JSAPI支付),小程序 + * + * @author dingchao + * @date 2025/4/25 + * @modified by: + */ +@Slf4j +@Service +public class WxPayServiceImpl implements WxPayService { + + @Resource + private JsapiService wxJsapiService; + @Resource + private RefundService wxRefundService; + @Resource + private WxPayConfig wxPayConfig; + @Resource + private RSASigner wxRsaSigner; + @Resource + private EbikePaymentService ebikePaymentService; + @Resource + private EbikeRefundService ebikeRefundService; + + + @Override + public boolean closeOrder(String outTradeNo) { + try { + CloseOrderRequest request = new CloseOrderRequest(); + request.setOutTradeNo(outTradeNo); + wxJsapiService.closeOrder(request); + log.info("关闭订单成功,订单号:{}", outTradeNo); + //ebikePaymentService.updatePaymentStatus(outTradeNo, 2); + return true; + } catch (Exception e) { + logError("关闭订单closeOrder", e); + return false; + } + } + + @Override + public JSONObject prepay(String outTradeNo, String description, String goodsTag, String openId, AmountDto amount, DetailDto detail, String clientIp) { + try { + PrepayRequest request = new PrepayRequest(); + request.setAppid(wxPayConfig.getAppId()); + request.setMchid(wxPayConfig.getMerchantId()); + request.setDescription(description); + request.setOutTradeNo(outTradeNo); + request.setNotifyUrl(wxPayConfig.getPayNotifyUrl()); + LocalDateTime expireTime = LocalDateTime.now().plusMinutes(wxPayConfig.getExpireMinute()); + request.setTimeExpire(StringUtil.formatLocalDatetime(expireTime)); + request.setGoodsTag(goodsTag); + Amount amountReq = new Amount(); + amountReq.setTotal(BigDecimal.valueOf(amount.getTotal() * 100.0).intValue()); + amountReq.setCurrency(amount.getCurrency()); + request.setAmount(amountReq); + Payer payer = new Payer(); + payer.setOpenid(openId); + request.setPayer(payer); + Detail detailReq = new Detail(); + detailReq.setGoodsDetail(detail.getGoodsDetail().stream().map(goodsDetail -> { + GoodsDetail goodsDetailReq = new GoodsDetail(); + goodsDetailReq.setGoodsName(goodsDetail.getItemName()); + goodsDetailReq.setMerchantGoodsId(goodsDetail.getItemId()); + goodsDetailReq.setQuantity(goodsDetail.getQuantity()); + goodsDetailReq.setUnitPrice(BigDecimal.valueOf(goodsDetail.getUnitPrice()*100.0).intValue()); + return goodsDetailReq; + }).toList()); + detailReq.setCostPrice(BigDecimal.valueOf(detail.getCostPrice()*100.0).intValue()); + detailReq.setInvoiceId(detail.getInvoiceId()); + request.setDetail(detailReq); + SceneInfo sceneInfo = new SceneInfo(); + sceneInfo.setPayerClientIp(clientIp); + request.setSceneInfo(sceneInfo); + + PrepayResponse response = wxJsapiService.prepay(request); + String payId = response.getPrepayId(); + //入库 + if (payId != null) { + EbikePayment ebikePayment = new EbikePayment(); + ebikePayment.setOrderId(outTradeNo); + ebikePayment.setPaymentId(payId); + ebikePayment.setCreateTime(LocalDateTime.now()); + ebikePayment.setPaymentMethod(PayMethod.wechat.name()); + ebikePayment.setCostPrice(amount.getTotal()); + int state = Transaction.TradeStateEnum.NOTPAY.ordinal(); + ebikePayment.setTradeState(String.valueOf(state)); + ebikePaymentService.save(ebikePayment); + // 返回给小程序的参数(调起微信支付) + WxJsapiPromptDto wxJsapiPromptDto = new WxJsapiPromptDto(); + wxJsapiPromptDto.setAppId(wxPayConfig.getAppId()); + wxJsapiPromptDto.setTimeStamp(String.valueOf(Instant.now().getEpochSecond())); + wxJsapiPromptDto.setNonceStr(RandomUtil.randomStringUpper(32)); + wxJsapiPromptDto.setExtension("prepay_id=" + payId); + return wxJsapiPromptDto.toJson(wxRsaSigner); + } + log.error("微信支付下单prepay失败,订单号:{}", outTradeNo); + return null; + }catch (Exception e) { + logError("微信支付下单prepay", e); + return null; + } + } + + @Override + public Transaction queryOrderById(String transactionId) { + try { + QueryOrderByIdRequest request = new QueryOrderByIdRequest(); + request.setTransactionId(transactionId); + return wxJsapiService.queryOrderById(request); + }catch (Exception e) { + logError("通过交易订单号查询支付订单queryOrderById", e); + } + return null; + } + + @Override + public Transaction queryOrderByOutTradeNo(String outTradeNo) { + try { + QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest(); + request.setOutTradeNo(outTradeNo); + return wxJsapiService.queryOrderByOutTradeNo(request); + }catch (Exception e) { + logError("通过商户(骑行)订单号查询支付订单queryOrderByOutTradeNo", e); + return null; + } + } + + @Override + public String refund(String transactionId, String outTradeNo, String reason, AmountRefundDto amount) { + try { + // 雪花算法生成退款单号 + String outRefundNo = StringUtil.generateSnowflakeId("refundId"); + EbikeRefund ebikeRefund = new EbikeRefund(); + ebikeRefund.setCreateTime(LocalDateTime.now()); + // 发起退款 + CreateRequest request = new CreateRequest(); + request.setTransactionId(transactionId); + request.setOutTradeNo(outTradeNo); + request.setOutRefundNo(outRefundNo); + request.setReason(reason); + request.setNotifyUrl(wxPayConfig.getRefundNotifyUrl()); + AmountReq amountReq = new AmountReq(); + amountReq.setRefund(BigDecimal.valueOf(amount.getRefund() * 100.0).longValue()); + amountReq.setTotal(BigDecimal.valueOf(amount.getTotal() * 100.0).longValue()); + amountReq.setCurrency(amount.getCurrency()); + request.setAmount(amountReq); + Refund result = wxRefundService.create(request); + // 入库 + if (result != null) { + ebikeRefund.setRefundId(outRefundNo); + ebikeRefund.setOrderId(outTradeNo); + ebikeRefund.setPaymentId(transactionId); + ebikeRefund.setTotal(amount.getTotal()); + ebikeRefund.setReason(reason); + ebikeRefund.setStatus(String.valueOf(result.getStatus().ordinal())); + ebikeRefund.setCurrency(amount.getCurrency()); + ebikeRefundService.saveRefundResult(ebikeRefund); + if(Status.SUCCESS.equals(result.getStatus())){ + ebikeRefund.setRefund(result.getAmount().getRefund().doubleValue() / 100.0); + ebikeRefund.setRefundTime(LocalDateTime.now()); + } + ebikeRefundService.saveRefundResult(ebikeRefund); + return outRefundNo; + } + log.error("退款申请refund失败,订单号:{}", outTradeNo); + return null; + }catch (Exception e) { + logError("退款申请refund", e); + return null; + } + } + + @Override + public Refund queryRefundByOutNo(String outRefundNo) { + try { + QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest(); + request.setOutRefundNo(outRefundNo); + return wxRefundService.queryByOutRefundNo(request); + }catch (Exception e) { + logError("通过商户退款单号查询退款queryByOutRefundNo", e); + return null; + } + } + + /** + * 打印日志 + * + * @param desc 描述 + * @param e 异常 + */ + private void logError(String desc, Exception e) { + if (e instanceof HttpException httpException) { + log.error("{} 发送HTTP请求失败, {}", desc, httpException.getHttpRequest()); + } + else if (e instanceof ServiceException serviceException) { + log.error("{} 服务返回状态不正常, {}", desc, serviceException.getResponseBody()); + } + else if (e instanceof MalformedMessageException malformedMessageException) { + log.error("{} 返回体类型不合法或者解析返回体失败, {}", desc, malformedMessageException.getMessage()); + } + else { + log.error("{} 执行异常, {}", desc, e.getMessage()); + } + + } +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/task/WsPayTask.java b/ebike-payment/src/main/java/com/cdzy/payment/task/WsPayTask.java new file mode 100644 index 00000000..047e0921 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/task/WsPayTask.java @@ -0,0 +1,79 @@ +package com.cdzy.payment.task; + +import com.cdzy.payment.config.WxPayConfig; +import com.cdzy.payment.model.entity.EbikePayment; +import com.cdzy.payment.model.entity.EbikeRefund; +import com.cdzy.payment.service.EbikePaymentService; +import com.cdzy.payment.service.EbikeRefundService; +import com.cdzy.payment.service.WxPayService; +import com.wechat.pay.java.service.payments.model.Transaction; +import com.wechat.pay.java.service.refund.model.Refund; +import com.wechat.pay.java.service.refund.model.Status; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 用户订单支付任务。检测支付状态,更新订单状态。 + * + * @author dingchao + * @date 2025/4/25 + * @modified by: + */ +@Slf4j +@Component +public class WsPayTask { + + @Resource + private WxPayConfig wxPayConfig; + @Resource + private WxPayService wxPayService; + @Resource + private EbikePaymentService ebikePaymentService; + @Resource + private EbikeRefundService ebikeRefundService; + + /** + * 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未支付的订单 + */ + @Scheduled(cron = "0/30 * * * * ?") + public void orderConfirm() throws Exception { + log.info("orderConfirm 执行......"); + // 1. 查询未支付的订单 + List ebikePaymentList = ebikePaymentService.getNoPayOrderByDuration(wxPayConfig.getExpireMinute()); + // 2. 遍历订单,查询支付状态 + for (EbikePayment ebikePayment : ebikePaymentList) { + log.warn("超时未支付的订单号 ===> {}", ebikePayment.getOrderId()); + // 调用微信支付查询接口,查询支付状态 + Transaction transaction = wxPayService.queryOrderByOutTradeNo(ebikePayment.getOrderId()); + // 3. 更新订单状态 + if (transaction != null) + ebikePaymentService.updatePaymentStatus(ebikePayment.getRecordId(), transaction); + } + } + + /** + * 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未成功的退款单 + */ + @Scheduled(cron = "0/30 * * * * ?") + public void refundConfirm() throws Exception { + log.info("refundConfirm 执行......"); + + // 1. 查询未成功的退款单 + List ebikeRefundList = ebikeRefundService.getNoSuccessRefundOrderByDuration(wxPayConfig.getExpireMinute()); + // 2. 遍历退款单,查询退款状态 + for (EbikeRefund ebikeRefund : ebikeRefundList) { + log.warn("超时未退款的退款单号 ===> {}", ebikeRefund.getRefundId()); + // TODO: 2025/4/25 调用微信退款查询接口,查询退款状态 + Refund refund = wxPayService.queryRefundByOutNo(ebikeRefund.getRefundId()); + if (refund!= null&& Status.SUCCESS.equals(refund.getStatus())){ + // 3. 更新退款单状态 + //ebikeRefundService.updateRefundStatus(ebikeRefund.getRefundId(), 2); + } + } + } + +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/utils/StringUtil.java b/ebike-payment/src/main/java/com/cdzy/payment/utils/StringUtil.java new file mode 100644 index 00000000..2069e6f9 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/utils/StringUtil.java @@ -0,0 +1,46 @@ +package com.cdzy.payment.utils; + +import com.mybatisflex.core.keygen.IKeyGenerator; +import com.mybatisflex.core.keygen.KeyGeneratorFactory; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * 字符串处理工具类 + * + * @author dingchao + * @date 2025/4/27 + * @modified by: + */ +public class StringUtil { + + + /** + * 格式化时间 + * 格式:yyyy-MM-dd'T'HH:mm:ss.SSSX + * 示例:2025-04-25T10:10:10.100+08:00 + * 注意:时区为+8 + * + * @param localDateTime 本地时间 + * @return + */ + public static String formatLocalDatetime(LocalDateTime localDateTime) { + ZonedDateTime localZoned = localDateTime.atZone(ZoneId.of("+8")); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"); + return localZoned.format(formatter); + } + + public static String generateSnowflakeId(String idFieldName) { + // 获取名为 "snowFlakeId" 的生成器 + IKeyGenerator generator = KeyGeneratorFactory.getKeyGenerator("snowFlakeId"); + Long key = (Long)generator.generate(null, idFieldName); + return String.valueOf(key); + } + + //public static void main(String[] args) { + // System.out.println(formatLocalDatetime(LocalDateTime.now())); + // System.out.println(generateSnowflakeId("refundId")); + //} +} diff --git a/ebike-payment/src/main/resources/application-dev.yml b/ebike-payment/src/main/resources/application-dev.yml new file mode 100644 index 00000000..5d484368 --- /dev/null +++ b/ebike-payment/src/main/resources/application-dev.yml @@ -0,0 +1,43 @@ +server: + port: 10017 +spring: + application: + name: ebike-pay + cloud: + nacos: + server-addr: 127.0.0.1:8848 # nacos + username: nacos + password: nacos + jackson: + serialization: + write-dates-as-timestamps: false + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + datasource: + url: jdbc:mysql://192.168.2.226:3306/ebike_orders?serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf8 + username: root + password: 970529 + hikari: + ## 最小空闲连接数量 + minimum-idle: 5 + ## 空闲连接存活最大时间,默认600000(10分钟) + idle-timeout: 180000 + ## 连接池最大连接数,默认是10 + maximum-pool-size: 10 + ## 数据库连接超时时间,默认30秒,即30000 + connection-timeout: 30000 + connection-test-query: SELECT 1 + ##此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟 + max-lifetime: 1800000 +mybatis-flex: + mapper-locations: classpath:mapper/*.xml +payment: + wx-pay: + app-id: wx097b305458b0757c + merchant-id: 1668000369 + private-key-path: D:\Projects\eBIKE\docs\wechatpay\apiclient_key.pem + merchant-serial-number: 5157F09EFDC096DE15EBE81A47057A72******** + api-v3-key: 5157F09EFDC096DE15EBE81A47057A72******** + pay-notify_url: http://192.168.2.156:10017/wxPayment/pay-notify + refund-notify_url: http://192.168.2.156:10017/wxPayment/refund-notify + expire-minutes: 5 diff --git a/ebike-payment/src/main/resources/application-prod.yml b/ebike-payment/src/main/resources/application-prod.yml new file mode 100644 index 00000000..eb9eea7a --- /dev/null +++ b/ebike-payment/src/main/resources/application-prod.yml @@ -0,0 +1,10 @@ +server: + port: 10017 +spring: + application: + name: ebike-pay + cloud: + nacos: + server-addr: 192.168.2.226:8848 # nacos + username: nacos + password: nacos \ No newline at end of file diff --git a/ebike-payment/src/main/resources/application-test.yml b/ebike-payment/src/main/resources/application-test.yml new file mode 100644 index 00000000..4b724d4d --- /dev/null +++ b/ebike-payment/src/main/resources/application-test.yml @@ -0,0 +1,39 @@ +server: + port: 10017 +spring: + application: + name: ebike-pay + cloud: + nacos: + server-addr: 192.168.2.226:8848 # nacos + username: nacos + password: nacos + jackson: + serialization: + write-dates-as-timestamps: false + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + datasource: + url: jdbc:mysql://192.168.2.226:3306/ebike_user?useSSL=false + username: root + password: 970529 + hikari: + ## 最小空闲连接数量 + minimum-idle: 5 + ## 空闲连接存活最大时间,默认600000(10分钟) + idle-timeout: 180000 + ## 连接池最大连接数,默认是10 + maximum-pool-size: 10 + ## 数据库连接超时时间,默认30秒,即30000 + connection-timeout: 30000 + connection-test-query: SELECT 1 + ##此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟 + max-lifetime: 1800000 + sql: + init: + platform: mysql + mode: always + schema-locations: classpath:db/init.sql + data-locations: classpath:db/data.sql +mybatis-flex: + mapper-locations: classpath:mapper/*.xml \ No newline at end of file diff --git a/ebike-payment/src/main/resources/application.yml b/ebike-payment/src/main/resources/application.yml new file mode 100644 index 00000000..db116c9d --- /dev/null +++ b/ebike-payment/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + profiles: + active: @profile.active@ diff --git a/pom.xml b/pom.xml index 91448586..dbe4f6f2 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,7 @@ ebike-operate ebike-orders ebike-report + ebike-payment @@ -209,6 +210,13 @@ import pom + + + org.junit.platform + junit-platform-launcher + test + + From 54f2d49dead33a81fbbfb6baee4d06bf89a0f973 Mon Sep 17 00:00:00 2001 From: jkcdev Date: Sun, 27 Apr 2025 17:51:54 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E6=95=B4=E4=BD=93=E6=A1=86=E6=9E=B6=E5=B7=B2=E7=BB=8F=E5=AE=8C?= =?UTF-8?q?=E6=88=90=EF=BC=8C=E7=9B=B4=E6=8E=A5=E4=BD=BF=E7=94=A8JavaSDK-v?= =?UTF-8?q?3=E5=AE=9E=E7=8E=B0=EF=BC=8C=E5=8C=85=E6=8B=AC=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=9A=84=E5=B0=81=E8=A3=85=E3=80=81=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E7=9A=84=E4=BA=A4=E4=BA=92=EF=BC=9B=E8=BF=98=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E5=AE=8C=E5=96=84=E4=BB=98=E6=AC=BE=E3=80=81=E9=80=80?= =?UTF-8?q?=E6=AC=BE=E5=9B=9E=E8=B0=83=E7=9A=84=E5=AE=9E=E7=8E=B0=EF=BC=8C?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E8=BD=AE=E8=AE=AD=E7=9A=84=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cdzy/payment/task/WsPayTask.java | 2 +- .../payment/EbikePaymentApplicationTest.java | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 ebike-payment/src/test/java/com/cdzy/payment/EbikePaymentApplicationTest.java diff --git a/ebike-payment/src/main/java/com/cdzy/payment/task/WsPayTask.java b/ebike-payment/src/main/java/com/cdzy/payment/task/WsPayTask.java index 047e0921..89ce4fc2 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/task/WsPayTask.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/task/WsPayTask.java @@ -67,7 +67,7 @@ public class WsPayTask { // 2. 遍历退款单,查询退款状态 for (EbikeRefund ebikeRefund : ebikeRefundList) { log.warn("超时未退款的退款单号 ===> {}", ebikeRefund.getRefundId()); - // TODO: 2025/4/25 调用微信退款查询接口,查询退款状态 + // 调用微信退款查询接口,查询退款状态 Refund refund = wxPayService.queryRefundByOutNo(ebikeRefund.getRefundId()); if (refund!= null&& Status.SUCCESS.equals(refund.getStatus())){ // 3. 更新退款单状态 diff --git a/ebike-payment/src/test/java/com/cdzy/payment/EbikePaymentApplicationTest.java b/ebike-payment/src/test/java/com/cdzy/payment/EbikePaymentApplicationTest.java new file mode 100644 index 00000000..f09c5530 --- /dev/null +++ b/ebike-payment/src/test/java/com/cdzy/payment/EbikePaymentApplicationTest.java @@ -0,0 +1,98 @@ +package com.cdzy.payment; + +import com.mybatisflex.codegen.Generator; +import com.mybatisflex.codegen.config.GlobalConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.junit.jupiter.api.Test; + +/** + * 功能描述 + * + * @author dingchao + * @date 2025/4/24 + * @modified by: + */ +public class EbikePaymentApplicationTest { + + private static final String model_path ="D:/Projects/eBIKE/mybatis-flex/ebike-pay"; + private static final String mapperPath="D:/Projects/eBIKE/mybatis-flex/ebike-pay/resources/mapper"; + private static final String packageName ="com.cdzy.payment"; + private static final String[] tables= new String[]{ + "ebike_payment", + "ebike_refund", + "ebike_order_details" + }; + + @Test + public void pay_mybatis_code() { + //配置数据源 + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setJdbcUrl("jdbc:mysql://192.168.2.226:3306/ebike_orders?characterEncoding=utf-8"); + dataSource.setUsername("root"); + dataSource.setPassword("970529"); + //生成全库的 + GlobalConfig globalConfig = createGlobalConfigUseStyle2(); + //单表的 + //GlobalConfig globalConfig = createGlobalConfigUseStyle2(); + Generator generator = new Generator(dataSource, globalConfig); + //生成代码 + generator.generate(); + } + private GlobalConfig createGlobalConfigUseStyle1() { + // 创建配置内容 + GlobalConfig globalConfig = new GlobalConfig(); + // 设置项目源目录和基础包 + globalConfig.getPackageConfig() + .setSourceDir(model_path) + .setBasePackage(packageName); + // 启用生成 entity,并启用 Lombok + globalConfig.setEntityGenerateEnable(true); + globalConfig.setEntityWithLombok(true); + // 设置项目的JDK版本 + globalConfig.setEntityJdkVersion(17); + // 启用生成 mapper、service、controller + globalConfig.enableEntity(); + globalConfig.enableMapper(); + globalConfig.enableService(); + globalConfig.enableServiceImpl(); + globalConfig.enableController(); + globalConfig.enableMapperXml(); + globalConfig.setMapperXmlPath(mapperPath); + // 配置 Mapper XML 生成路径和文件名 + globalConfig.getMapperXmlConfig() + .setFilePrefix("") // 设置合适的前缀 + .setFileSuffix("Mapper"); // 确保设置正确的后缀名 + return globalConfig; + } + private GlobalConfig createGlobalConfigUseStyle2() { + // 创建配置内容 + GlobalConfig globalConfig = new GlobalConfig(); + // 设置项目源目录和基础包 + globalConfig.getPackageConfig() + .setSourceDir(model_path) + .setBasePackage(packageName); + // 启用生成 entity,并启用 Lombok + globalConfig.setEntityGenerateEnable(true); + globalConfig.setEntityWithLombok(true); + // 设置项目的JDK版本 + globalConfig.setEntityJdkVersion(17); + // 启用生成 mapper、service、controller + globalConfig.enableEntity(); + globalConfig.enableMapper(); + globalConfig.enableService(); + globalConfig.enableServiceImpl(); + globalConfig.enableController(); + globalConfig.enableMapperXml(); + globalConfig.setMapperXmlPath(mapperPath); + // 配置 Mapper XML 生成路径和文件名 + globalConfig.getMapperXmlConfig() + .setFilePrefix("") // 设置合适的前缀 + .setFileSuffix("Mapper"); // 确保设置正确的后缀名 + //设置表前缀和只生成哪些表 + // globalConfig.setTablePrefix("tb_"); + globalConfig.setGenerateTable(tables); + // 返回配置 + return globalConfig; + } + +} From 62724b62d703bd1d03cf901a6017f946416b8659 Mon Sep 17 00:00:00 2001 From: 863620989 <863620989@qq.com> Date: Mon, 28 Apr 2025 08:49:47 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ebikeoperate/controller/EbikeSysRoperatesetController.java | 1 + .../cdzy/ebikeoperate/service/EbikeSysRoperatesetService.java | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ebike-operate/src/main/java/com/cdzy/ebikeoperate/controller/EbikeSysRoperatesetController.java b/ebike-operate/src/main/java/com/cdzy/ebikeoperate/controller/EbikeSysRoperatesetController.java index 074b6ecc..bfeec0f3 100644 --- a/ebike-operate/src/main/java/com/cdzy/ebikeoperate/controller/EbikeSysRoperatesetController.java +++ b/ebike-operate/src/main/java/com/cdzy/ebikeoperate/controller/EbikeSysRoperatesetController.java @@ -85,4 +85,5 @@ public class EbikeSysRoperatesetController { return JsonResult.success(dto); } + } diff --git a/ebike-operate/src/main/java/com/cdzy/ebikeoperate/service/EbikeSysRoperatesetService.java b/ebike-operate/src/main/java/com/cdzy/ebikeoperate/service/EbikeSysRoperatesetService.java index 60a1dc6e..f4a5b533 100644 --- a/ebike-operate/src/main/java/com/cdzy/ebikeoperate/service/EbikeSysRoperatesetService.java +++ b/ebike-operate/src/main/java/com/cdzy/ebikeoperate/service/EbikeSysRoperatesetService.java @@ -24,4 +24,6 @@ public interface EbikeSysRoperatesetService extends IService