From d814831822445439a1fcd679475e8086a9c334e6 Mon Sep 17 00:00:00 2001 From: eric <465889110@qq.com> Date: Mon, 8 Sep 2025 03:04:04 +0800 Subject: [PATCH] no message --- .gitignore | 35 + Dockerfile | 10 + Dockerfile.buildagent | 15 + Jenkinsfile | 88 +++ pom.xml | 320 ++++++++ settings.xml | 12 + src/main/java/com/sczx/pay/Application.java | 34 + .../pay/alipay/controller/ItemController.java | 131 ++++ .../controller/ItemOrderController.java | 32 + .../pay/alipay/dto/RentItemCreateRequest.java | 291 +++++++ .../pay/alipay/po/RentCommodityConfig.java | 163 ++++ .../com/sczx/pay/alipay/po/RentRuleItem.java | 314 ++++++++ .../alipay/service/ImageUploadService.java | 47 ++ .../sczx/pay/alipay/service/ItemService.java | 44 ++ .../pay/alipay/service/RentOrderService.java | 11 + .../alipay/service/RentRuleItemService.java | 8 + .../service/impl/ImageUploadServiceImpl.java | 348 +++++++++ .../alipay/service/impl/ItemServiceImpl.java | 735 ++++++++++++++++++ .../service/impl/RentOrderServiceImpl.java | 146 ++++ .../service/impl/RentRuleItemServiceImpl.java | 64 ++ .../sczx/pay/alipay/vo/AppItemSkuAttrVo.java | 39 + .../pay/alipay/vo/ImageDirectoryRequest.java | 83 ++ .../sczx/pay/alipay/vo/ItemCreateRequest.java | 164 ++++ .../com/sczx/pay/alipay/vo/OpenResponse.java | 136 ++++ .../pay/alipay/vo/RentOrderCreateRequest.java | 15 + .../com/sczx/pay/aspect/FacadeAspect.java | 54 ++ .../com/sczx/pay/aspect/WebLogAspect.java | 127 +++ .../java/com/sczx/pay/common/IApiCode.java | 12 + src/main/java/com/sczx/pay/common/Result.java | 121 +++ .../pay/common/constant/SystemConstants.java | 11 + .../sczx/pay/common/enums/ApiErrorCode.java | 55 ++ .../java/com/sczx/pay/config/AlipayConf.java | 114 +++ .../sczx/pay/config/DynamicWXPayConfig.java | 83 ++ .../com/sczx/pay/config/SwaggerConfig.java | 47 ++ .../com/sczx/pay/config/WXPayConfigImpl.java | 85 ++ .../controller/AliPayNotifyController.java | 144 ++++ .../AliPayOrderCloseController.java | 38 + .../AliPayOrderFulfillmentController.java | 49 ++ .../pay/controller/AliPaymentController.java | 70 ++ .../pay/controller/AlipayRentController.java | 29 + .../AlipayRentNotifyController.java | 84 ++ .../pay/controller/PaymentController.java | 244 ++++++ .../com/sczx/pay/dto/AlipayCloseRequest.java | 14 + .../com/sczx/pay/dto/AlipayCreateRequest.java | 14 + .../sczx/pay/dto/AlipayCreateResponse.java | 14 + .../com/sczx/pay/dto/AlipayQueryRequest.java | 11 + .../com/sczx/pay/dto/AlipayQueryResponse.java | 14 + .../com/sczx/pay/dto/AlipayRefundRequest.java | 14 + .../java/com/sczx/pay/dto/AlipayResponse.java | 10 + .../java/com/sczx/pay/dto/PaymentRequest.java | 74 ++ .../com/sczx/pay/dto/PaymentResponse.java | 38 + .../java/com/sczx/pay/dto/RefundRequest.java | 65 ++ .../pay/dto/ali/rent/AlipayRentPayReq.java | 13 + .../dto/ali/rent/AlipayRentPayResponse.java | 17 + .../sczx/pay/entity/CompanyAlipayConfig.java | 44 ++ .../sczx/pay/entity/CompanyWechatConfig.java | 44 ++ .../java/com/sczx/pay/entity/OrderMain.java | 48 ++ .../java/com/sczx/pay/entity/OrderSub.java | 29 + .../java/com/sczx/pay/entity/PayStatus.java | 113 +++ .../sczx/pay/entity/PaymentNotifyRecord.java | 169 ++++ .../com/sczx/pay/entity/PaymentRecord.java | 177 +++++ .../java/com/sczx/pay/entity/ReasonCodes.java | 165 ++++ .../com/sczx/pay/entity/RefundRecord.java | 158 ++++ .../com/sczx/pay/entity/RefundStatus.java | 110 +++ .../sczx/pay/exception/InnerException.java | 86 ++ .../pay/mapper/CompanyAlipayConfigMapper.java | 22 + .../pay/mapper/CompanyWechatConfigMapper.java | 22 + .../com/sczx/pay/mapper/OrderPayMapper.java | 73 ++ .../pay/mapper/PaymentNotifyRecordMapper.java | 28 + .../sczx/pay/mapper/PaymentRecordMapper.java | 62 ++ .../sczx/pay/mapper/RefundRecordMapper.java | 39 + .../sczx/pay/mapper/RentRuleItemMapper.java | 30 + src/main/java/com/sczx/pay/sdk/WXPay.java | 315 ++++++++ .../java/com/sczx/pay/sdk/WXPayConfig.java | 54 ++ .../pay/service/AliPayOrderCloseService.java | 15 + .../AliPayOrderFulfillmentService.java | 25 + .../sczx/pay/service/AlipayRentService.java | 23 + .../com/sczx/pay/service/AlipayService.java | 47 ++ .../sczx/pay/service/WechatPayService.java | 564 ++++++++++++++ .../impl/AliPayOrderCloseServiceImpl.java | 66 ++ .../AliPayOrderFulfillmentServiceImpl.java | 82 ++ .../service/impl/AlipayRentServiceImpl.java | 196 +++++ .../pay/service/impl/AlipayServiceImpl.java | 469 +++++++++++ .../sczx/pay/thirdpart/dto/req/CarDTO.java | 132 ++++ .../thirdpart/dto/req/CarModelSimpleDTO.java | 40 + .../thirdpart/dto/req/CompanyStoreDTO.java | 68 ++ .../pay/thirdpart/dto/req/OrderCarImgDTO.java | 16 + .../sczx/pay/thirdpart/dto/req/OrderDTO.java | 144 ++++ .../pay/thirdpart/dto/req/OrderDetailDTO.java | 176 +++++ .../pay/thirdpart/dto/req/OrderSubDTO.java | 49 ++ .../pay/thirdpart/dto/req/PayOrderDTO.java | 30 + .../thirdpart/dto/req/RentBatteyRuleDTO.java | 91 +++ .../pay/thirdpart/facade/OrderFacade.java | 18 + .../pay/thirdpart/integration/OrderInteg.java | 35 + .../com/sczx/pay/utils/AlipayApiCallback.java | 21 + .../com/sczx/pay/utils/AlipayApiTemplate.java | 50 ++ .../com/sczx/pay/utils/AlipaySdkUtil.java | 242 ++++++ .../java/com/sczx/pay/utils/ComputerInfo.java | 159 ++++ src/main/java/com/sczx/pay/utils/IPUtils.java | 135 ++++ .../java/com/sczx/pay/utils/WXPayUtil.java | 243 ++++++ src/main/resources/application-dev.yml | 67 ++ src/main/resources/application-local.yml | 67 ++ src/main/resources/application.yml | 117 +++ src/main/resources/logback.xml | 53 ++ .../resources/mapper/RentRuleItemMapper.xml | 341 ++++++++ 105 files changed, 10519 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Dockerfile.buildagent create mode 100644 Jenkinsfile create mode 100644 pom.xml create mode 100644 settings.xml create mode 100644 src/main/java/com/sczx/pay/Application.java create mode 100644 src/main/java/com/sczx/pay/alipay/controller/ItemController.java create mode 100644 src/main/java/com/sczx/pay/alipay/controller/ItemOrderController.java create mode 100644 src/main/java/com/sczx/pay/alipay/dto/RentItemCreateRequest.java create mode 100644 src/main/java/com/sczx/pay/alipay/po/RentCommodityConfig.java create mode 100644 src/main/java/com/sczx/pay/alipay/po/RentRuleItem.java create mode 100644 src/main/java/com/sczx/pay/alipay/service/ImageUploadService.java create mode 100644 src/main/java/com/sczx/pay/alipay/service/ItemService.java create mode 100644 src/main/java/com/sczx/pay/alipay/service/RentOrderService.java create mode 100644 src/main/java/com/sczx/pay/alipay/service/RentRuleItemService.java create mode 100644 src/main/java/com/sczx/pay/alipay/service/impl/ImageUploadServiceImpl.java create mode 100644 src/main/java/com/sczx/pay/alipay/service/impl/ItemServiceImpl.java create mode 100644 src/main/java/com/sczx/pay/alipay/service/impl/RentOrderServiceImpl.java create mode 100644 src/main/java/com/sczx/pay/alipay/service/impl/RentRuleItemServiceImpl.java create mode 100644 src/main/java/com/sczx/pay/alipay/vo/AppItemSkuAttrVo.java create mode 100644 src/main/java/com/sczx/pay/alipay/vo/ImageDirectoryRequest.java create mode 100644 src/main/java/com/sczx/pay/alipay/vo/ItemCreateRequest.java create mode 100644 src/main/java/com/sczx/pay/alipay/vo/OpenResponse.java create mode 100644 src/main/java/com/sczx/pay/alipay/vo/RentOrderCreateRequest.java create mode 100644 src/main/java/com/sczx/pay/aspect/FacadeAspect.java create mode 100644 src/main/java/com/sczx/pay/aspect/WebLogAspect.java create mode 100644 src/main/java/com/sczx/pay/common/IApiCode.java create mode 100644 src/main/java/com/sczx/pay/common/Result.java create mode 100644 src/main/java/com/sczx/pay/common/constant/SystemConstants.java create mode 100644 src/main/java/com/sczx/pay/common/enums/ApiErrorCode.java create mode 100644 src/main/java/com/sczx/pay/config/AlipayConf.java create mode 100644 src/main/java/com/sczx/pay/config/DynamicWXPayConfig.java create mode 100644 src/main/java/com/sczx/pay/config/SwaggerConfig.java create mode 100644 src/main/java/com/sczx/pay/config/WXPayConfigImpl.java create mode 100644 src/main/java/com/sczx/pay/controller/AliPayNotifyController.java create mode 100644 src/main/java/com/sczx/pay/controller/AliPayOrderCloseController.java create mode 100644 src/main/java/com/sczx/pay/controller/AliPayOrderFulfillmentController.java create mode 100644 src/main/java/com/sczx/pay/controller/AliPaymentController.java create mode 100644 src/main/java/com/sczx/pay/controller/AlipayRentController.java create mode 100644 src/main/java/com/sczx/pay/controller/AlipayRentNotifyController.java create mode 100644 src/main/java/com/sczx/pay/controller/PaymentController.java create mode 100644 src/main/java/com/sczx/pay/dto/AlipayCloseRequest.java create mode 100644 src/main/java/com/sczx/pay/dto/AlipayCreateRequest.java create mode 100644 src/main/java/com/sczx/pay/dto/AlipayCreateResponse.java create mode 100644 src/main/java/com/sczx/pay/dto/AlipayQueryRequest.java create mode 100644 src/main/java/com/sczx/pay/dto/AlipayQueryResponse.java create mode 100644 src/main/java/com/sczx/pay/dto/AlipayRefundRequest.java create mode 100644 src/main/java/com/sczx/pay/dto/AlipayResponse.java create mode 100644 src/main/java/com/sczx/pay/dto/PaymentRequest.java create mode 100644 src/main/java/com/sczx/pay/dto/PaymentResponse.java create mode 100644 src/main/java/com/sczx/pay/dto/RefundRequest.java create mode 100644 src/main/java/com/sczx/pay/dto/ali/rent/AlipayRentPayReq.java create mode 100644 src/main/java/com/sczx/pay/dto/ali/rent/AlipayRentPayResponse.java create mode 100644 src/main/java/com/sczx/pay/entity/CompanyAlipayConfig.java create mode 100644 src/main/java/com/sczx/pay/entity/CompanyWechatConfig.java create mode 100644 src/main/java/com/sczx/pay/entity/OrderMain.java create mode 100644 src/main/java/com/sczx/pay/entity/OrderSub.java create mode 100644 src/main/java/com/sczx/pay/entity/PayStatus.java create mode 100644 src/main/java/com/sczx/pay/entity/PaymentNotifyRecord.java create mode 100644 src/main/java/com/sczx/pay/entity/PaymentRecord.java create mode 100644 src/main/java/com/sczx/pay/entity/ReasonCodes.java create mode 100644 src/main/java/com/sczx/pay/entity/RefundRecord.java create mode 100644 src/main/java/com/sczx/pay/entity/RefundStatus.java create mode 100644 src/main/java/com/sczx/pay/exception/InnerException.java create mode 100644 src/main/java/com/sczx/pay/mapper/CompanyAlipayConfigMapper.java create mode 100644 src/main/java/com/sczx/pay/mapper/CompanyWechatConfigMapper.java create mode 100644 src/main/java/com/sczx/pay/mapper/OrderPayMapper.java create mode 100644 src/main/java/com/sczx/pay/mapper/PaymentNotifyRecordMapper.java create mode 100644 src/main/java/com/sczx/pay/mapper/PaymentRecordMapper.java create mode 100644 src/main/java/com/sczx/pay/mapper/RefundRecordMapper.java create mode 100644 src/main/java/com/sczx/pay/mapper/RentRuleItemMapper.java create mode 100644 src/main/java/com/sczx/pay/sdk/WXPay.java create mode 100644 src/main/java/com/sczx/pay/sdk/WXPayConfig.java create mode 100644 src/main/java/com/sczx/pay/service/AliPayOrderCloseService.java create mode 100644 src/main/java/com/sczx/pay/service/AliPayOrderFulfillmentService.java create mode 100644 src/main/java/com/sczx/pay/service/AlipayRentService.java create mode 100644 src/main/java/com/sczx/pay/service/AlipayService.java create mode 100644 src/main/java/com/sczx/pay/service/WechatPayService.java create mode 100644 src/main/java/com/sczx/pay/service/impl/AliPayOrderCloseServiceImpl.java create mode 100644 src/main/java/com/sczx/pay/service/impl/AliPayOrderFulfillmentServiceImpl.java create mode 100644 src/main/java/com/sczx/pay/service/impl/AlipayRentServiceImpl.java create mode 100644 src/main/java/com/sczx/pay/service/impl/AlipayServiceImpl.java create mode 100644 src/main/java/com/sczx/pay/thirdpart/dto/req/CarDTO.java create mode 100644 src/main/java/com/sczx/pay/thirdpart/dto/req/CarModelSimpleDTO.java create mode 100644 src/main/java/com/sczx/pay/thirdpart/dto/req/CompanyStoreDTO.java create mode 100644 src/main/java/com/sczx/pay/thirdpart/dto/req/OrderCarImgDTO.java create mode 100644 src/main/java/com/sczx/pay/thirdpart/dto/req/OrderDTO.java create mode 100644 src/main/java/com/sczx/pay/thirdpart/dto/req/OrderDetailDTO.java create mode 100644 src/main/java/com/sczx/pay/thirdpart/dto/req/OrderSubDTO.java create mode 100644 src/main/java/com/sczx/pay/thirdpart/dto/req/PayOrderDTO.java create mode 100644 src/main/java/com/sczx/pay/thirdpart/dto/req/RentBatteyRuleDTO.java create mode 100644 src/main/java/com/sczx/pay/thirdpart/facade/OrderFacade.java create mode 100644 src/main/java/com/sczx/pay/thirdpart/integration/OrderInteg.java create mode 100644 src/main/java/com/sczx/pay/utils/AlipayApiCallback.java create mode 100644 src/main/java/com/sczx/pay/utils/AlipayApiTemplate.java create mode 100644 src/main/java/com/sczx/pay/utils/AlipaySdkUtil.java create mode 100644 src/main/java/com/sczx/pay/utils/ComputerInfo.java create mode 100644 src/main/java/com/sczx/pay/utils/IPUtils.java create mode 100644 src/main/java/com/sczx/pay/utils/WXPayUtil.java create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application-local.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/logback.xml create mode 100644 src/main/resources/mapper/RentRuleItemMapper.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d7f4db --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +logs/ +src/main/resources/rebel.xml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fd863fa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +# 使用 OpenJDK 8 镜像构建 +FROM openjdk:8-jdk +# 添加作者信息 +LABEL maintainer="123879394@qq.com" + +WORKDIR /app +# 复制 jar 包 +COPY target/*.jar app.jar +# 设置 JVM 参数和启动命令 +ENTRYPOINT ["java", "-jar", "-Xms64m", "-Xmx128m", "app.jar"] \ No newline at end of file diff --git a/Dockerfile.buildagent b/Dockerfile.buildagent new file mode 100644 index 0000000..f893f6c --- /dev/null +++ b/Dockerfile.buildagent @@ -0,0 +1,15 @@ +# 使用 JDK 8 作为构建环境 +FROM openjdk:8-jdk + +# 使用阿里云的 apt 镜像源(Debian 11 bullseye) +RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \ + sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list + +# 更新包列表并安装 Maven 和 Git +RUN apt update && \ + apt install -y maven git && \ + mvn --version && \ + git --version + +# 设置工作目录 +WORKDIR /home/jenkins/workspace \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..8aca357 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,88 @@ +pipeline { + agent any +// tools { +// maven 'M3' // 必须在 Jenkins → Manage Jenkins → Global Tool Configuration 中配置过 +// } + environment { + APP_NAME = "sczx_notify" + DOCKER_IMAGE = "${APP_NAME}:latest" + CONTAINER_NAME = "${APP_NAME}-container" + } + + stages { +// stage('Checkout') { +// steps { +// echo "📦 正在拉取代码..." +// git branch: 'main', url: 'http://115.190.8.52:3000/sczx_group/sczx_order.git' +// } +// } + + stage('Build with Maven in JDK 8') { + agent { + dockerfile { + filename "Dockerfile.buildagent" + } + } + steps { + echo "🛠️ 正在使用 Maven 构建..." + sh 'mvn clean package -s settings.xml' + } + } + + stage('Check Jar File') { + agent any + steps { + sh 'ls -la target/' // 确保 jar 文件存在 + } + } + + stage('Build Docker Image') { + agent any + steps { + echo "🐋 正在构建 Docker 镜像..." + sh 'docker build -t "$DOCKER_IMAGE" .' + } + } + + stage('Stop Old Container') { + agent any + steps { + echo "🛑 正在停止旧的容器(如果存在)..." + sh ''' + if [ "$(docker ps -f 'name=sczx_notify-container' --format '{{.Status}}')" ]; then + docker stop sczx_notify-container + docker rm sczx_notify-container + fi + ''' + } + } + + stage('Run New Container') { + agent any + steps { + echo "🟢 正在运行新的容器..." + sh """ + docker run -d \ + --name \${CONTAINER_NAME} \ + --network sczx-net \ + -p 8019:8019 \ + -e JAVA_OPTS="-Xms256m -Xmx512m -Duser.timezone=Asia/Shanghai" \ + -e TZ=Asia/Shanghai \ + -v /etc/localtime:/etc/localtime:ro \ + -v /root/cert:/root/cert:ro \ + --restart always \ + \${DOCKER_IMAGE} + """ + } + } + } + + post { + success { + echo "🎉 构建成功!" + } + failure { + echo "❌ 构建失败,请检查日志!" + } + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..23578b5 --- /dev/null +++ b/pom.xml @@ -0,0 +1,320 @@ + + + 4.0.0 + + com.sczx + sczx_notify + 1.0.0 + jar + + sczx_notify + sczx_notify service + + + 1.8 + 2.3.12.RELEASE + Hoxton.SR12 + 2.2.9.RELEASE + + + + + org.springframework.boot + spring-boot-starter-parent + 2.3.12.RELEASE + + + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + com.alibaba.cloud + spring-cloud-alibaba-dependencies + ${spring-cloud-alibaba.version} + pom + import + + + + + + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + org.springframework.boot + spring-boot-configuration-processor + + + + org.springframework.cloud + spring-cloud-commons + + + + + org.projectlombok + lombok + 1.18.30 + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + org.springframework.cloud + spring-cloud-starter-netflix-hystrix + + + + + org.springframework.retry + spring-retry + 1.3.1 + + + + org.springframework.boot + spring-boot-starter-aop + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.3.1 + + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.3.1 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + mysql + mysql-connector-java + 8.0.33 + + + + + io.springfox + springfox-swagger2 + 2.9.2 + + + + io.springfox + springfox-swagger-ui + 2.9.2 + + + + + javax.xml.bind + jaxb-api + 2.3.1 + + + + io.projectreactor + reactor-core + 3.2.2.RELEASE + + + + + org.mapstruct + mapstruct + 1.5.5.Final + + + + + org.mapstruct + mapstruct-processor + 1.5.5.Final + provided + + + + + com.alibaba + fastjson + 1.2.83 + + + + + com.github.wechatpay-apiv3 + wechatpay-java + 0.2.14 + + + com.alipay.sdk + alipay-sdk-java + 4.39.79.ALL + + + com.alipay.sdk + alipay-sdk-java-v3 + 2.9.0.ALL + + + + com.github.xiaoymin + knife4j-spring-boot-starter + 2.0.2 + + + + commons-codec + commons-codec + 1.15 + + + + + + org.apache.httpcomponents + httpclient + 4.5.13 + + + + + org.apache.httpcomponents + httpcore + 4.4.14 + + + + + org.apache.httpcomponents + httpmime + 4.5.13 + + + + + + + + + com.alipay.sdk + alipay-sdk-java + 4.40.427.ALL + + + + net.coobird + thumbnailator + 0.4.17 + + + + + + + sczx_singlepay + + + src/main/resources + + *.yml + + true + + + src/main/resources + + *.yml + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.3.12.RELEASE + + + + repackage + + + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + \ No newline at end of file diff --git a/settings.xml b/settings.xml new file mode 100644 index 0000000..06004f8 --- /dev/null +++ b/settings.xml @@ -0,0 +1,12 @@ + + + + aliyun-maven + * + Aliyun Maven + https://maven.aliyun.com/repository/public + + + \ No newline at end of file diff --git a/src/main/java/com/sczx/pay/Application.java b/src/main/java/com/sczx/pay/Application.java new file mode 100644 index 0000000..19037b3 --- /dev/null +++ b/src/main/java/com/sczx/pay/Application.java @@ -0,0 +1,34 @@ +package com.sczx.pay; + +import com.sczx.pay.common.constant.SystemConstants; +import com.sczx.pay.utils.ComputerInfo; +import lombok.extern.slf4j.Slf4j; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.netflix.hystrix.EnableHystrix; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.Environment; +import org.springframework.retry.annotation.EnableRetry; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import java.io.IOException; + +@Slf4j +@SpringBootApplication +@EnableDiscoveryClient // 启用服务注册与发现 +@EnableRetry +@EnableFeignClients(basePackages = SystemConstants.FEIGN_CLIENT_BASE_PACKAGE ) +@EnableTransactionManagement +@EnableHystrix +@MapperScan("com.sczx.pay.mapper") // 扫描 Mapper 接口 +public class Application { + + public static void main(String[] args) throws IOException { + ConfigurableApplicationContext context = SpringApplication.run(Application.class, args); + Environment environment = context.getBean(Environment.class); + log.info("启动成功,后端服务API地址:http://{}:{}/swagger-ui.html", ComputerInfo.getIpAddr(), environment.getProperty("server.port")); + } +} diff --git a/src/main/java/com/sczx/pay/alipay/controller/ItemController.java b/src/main/java/com/sczx/pay/alipay/controller/ItemController.java new file mode 100644 index 0000000..f05c0d4 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/controller/ItemController.java @@ -0,0 +1,131 @@ +package com.sczx.pay.alipay.controller; + +import com.alipay.api.domain.AppxCategoryVO; +import com.alipay.api.internal.util.StringUtils; +import com.alipay.api.response.AlipayOpenAppItemListQueryResponse; +import com.alipay.api.response.AlipayOpenAppItemQueryResponse; +import com.alipay.api.response.AlipayOpenAppItemTemplateQueryResponse; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import com.sczx.pay.alipay.po.RentRuleItem; +import com.sczx.pay.alipay.service.ItemService; +import com.sczx.pay.alipay.service.RentRuleItemService; +import com.sczx.pay.alipay.vo.ItemCreateRequest; +import com.sczx.pay.alipay.vo.OpenResponse; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + + +import java.util.List; +import java.util.Map; + + +@Api(tags = "普通商品模块") +@RestController +@RequestMapping(value = "/item") +public class ItemController { + + @Autowired + private ItemService itemService; + + @Autowired + private RentRuleItemService rentRuleItemService; + + /** + * 类目查询 + * 获取叶子节点商品类目列表 + */ + @ApiOperationSupport(order = 1) + @ApiOperation(value = "类目查询", notes = "获取叶子节点商品类目列表") + @GetMapping(value = "/category/list") + public OpenResponse> categoryList( + @ApiParam(value = "商品类型:1-售卖类(实物),2-租赁类(实物),3-付费充值/兑换类(虚拟)") @RequestParam String itemType, + @ApiParam(value = "类目状态,默认AUDIT_PASSED", example = "AUDIT_PASSED") @RequestParam(required = false) String catStatus) { + return itemService.getCategories(itemType, catStatus); + } + + /** + * 普通商品模板信息查询 + */ + @ApiOperationSupport(order = 2) + @ApiOperation(value = "普通商品模板信息查询") + @GetMapping(value = "/template/query") + public OpenResponse queryTemplate( + @ApiParam(value = "类目ID", required = true) @RequestParam String categoryId, + @ApiParam(value = "商品类型:1-售卖类(实物),2-租赁类(实物),3-付费充值/兑换类(虚拟)") @RequestParam(required = false) String itemType) { + return itemService.queryTemplate(categoryId, itemType); + } + + + + /** + * 租赁类(实物)商品创建 + */ + @ApiOperation(value = "租赁类(实物)商品创建") + @ApiOperationSupport(order = 4) + @PostMapping(value = "/rent/create") + public OpenResponse rentCreate(@RequestBody RentRuleItem request) { + return itemService.createRentItem(request); + } + + + @PostMapping(value = "/rent/modify") + public OpenResponse modifyCreate(@RequestBody RentRuleItem request) { + return itemService.modifyItem(request); + } + + @ApiOperation(value = "商品详情查询") + @ApiOperationSupport(order = 7) + @GetMapping(value = "/query") + public OpenResponse query(@ApiParam(value = "支付平台侧商品ID") @RequestParam(required = false) String itemId, + @ApiParam(value = "商家侧商品ID") @RequestParam(required = false) String outItemId, + @ApiParam(value = "是否查询编辑版本,默认为0", defaultValue = "0") @RequestParam(required = false) String needEditSpu) { + return itemService.queryItemDetail(itemId, outItemId, + StringUtils.isNumeric(needEditSpu) ? Long.parseLong(needEditSpu) : 0); + } + + /** + * 租赁类(实物)履约模板创建 + */ + @ApiOperation(value = "租赁类(实物)履约模板创建") + @ApiOperationSupport(order = 4) + @PostMapping(value = "/template/create") + public OpenResponse templateCreate() { + return itemService.syncDeliveryInfo(); + } + + /** + * 租赁类(实物)履约模板创建 + */ + @ApiOperation(value = "租赁类(实物)履约模板创建") + @ApiOperationSupport(order = 4) + @PostMapping(value = "/delivery/query") + public OpenResponse deliveryQuery() { + return itemService.deliveryInfoQuery(); + } + + @ApiOperation(value = "租赁类(实物)履约模板创建") + @ApiOperationSupport(order = 4) + @PostMapping(value = "/deliverytemplate/query") + public OpenResponse deliveryTemplateQuery() { + return itemService.deliveryTemplateQuery(); + } + + @ApiOperation(value = "租赁类(实物)履约模板创建") + @ApiOperationSupport(order = 4) + @PostMapping(value = "/shop/query") + public OpenResponse shopQuery() { + return itemService.shopQuery(); + } + + + @ApiOperation(value = "接收套餐数据并记录商品") + @GetMapping(value = "/sync/item") + public Map syncItem(@RequestParam(required = false) String brandId,@RequestParam(required = false) String carModelId, + @RequestParam(required = false) String carRuleId,@RequestParam(required = false) String batteryRuleId) { + return rentRuleItemService.syncItem(brandId, carModelId,carRuleId,batteryRuleId); + } + +} diff --git a/src/main/java/com/sczx/pay/alipay/controller/ItemOrderController.java b/src/main/java/com/sczx/pay/alipay/controller/ItemOrderController.java new file mode 100644 index 0000000..48c8f77 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/controller/ItemOrderController.java @@ -0,0 +1,32 @@ +package com.sczx.pay.alipay.controller; + + +import com.alipay.api.AlipayApiException; +import com.sczx.pay.alipay.service.RentOrderService; +import com.sczx.pay.alipay.vo.RentOrderCreateRequest; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +@Api(value = "租赁订单接口", tags = "租赁订单接口") +@Slf4j +@RestController +@RequestMapping("/rentorder") +public class ItemOrderController { + + @Autowired + private RentOrderService rentOrderService; + + @ApiOperation(value = "创建租赁订单") + @PostMapping("/create") + public String rentOrderCreate(RentOrderCreateRequest request) throws AlipayApiException { + return rentOrderService.rentOrderCreate(request).getBody(); + + } +} diff --git a/src/main/java/com/sczx/pay/alipay/dto/RentItemCreateRequest.java b/src/main/java/com/sczx/pay/alipay/dto/RentItemCreateRequest.java new file mode 100644 index 0000000..dbbf111 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/dto/RentItemCreateRequest.java @@ -0,0 +1,291 @@ +package com.sczx.pay.alipay.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.io.Serializable; +import java.util.List; + +/** + * 实物租赁商品创建请求实体类 + */ +public class RentItemCreateRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + // 基本商品信息 + private String outItemId; + + @ApiModelProperty(value = "商品名称,最少不低于3个字,最长不超过60个字", required = true, example = "实物租赁商品创建示例") + private String title; + + @ApiModelProperty(value = "商品主图,图片宽高为750px*750px,宽高比1:1,800kb以内", required = true, + example = "A*pHfaSaEsF_0AAAAAAAAAAAAAATcnAQ") + private String headImg; + + @ApiModelProperty(value = "平台类目ID,多级类目只填最后一级", required = true, example = "C001375054") + private String categoryId; + + @ApiModelProperty(value = "商品类型,租赁类(实物): 2", required = true, example = "2") + private String itemType; + + @ApiModelProperty(value = "商品详情页URL,请填写您的小程序侧商品详情页链接") + private String path; + + @ApiModelProperty(value = "商品子图,作为平台详情页组件的轮播图,最多3张") + private List imageList; + + // SKU信息 + + @ApiModelProperty(value = "SKU数组") + private List skus; + + // 商品属性 + + @ApiModelProperty(value = "商品属性") + private List attrs; + + // Getters and Setters + + public String getOutItemId() { + return outItemId; + } + + public void setOutItemId(String outItemId) { + this.outItemId = outItemId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getHeadImg() { + return headImg; + } + + public void setHeadImg(String headImg) { + this.headImg = headImg; + } + + public String getCategoryId() { + return categoryId; + } + + public void setCategoryId(String categoryId) { + this.categoryId = categoryId; + } + + public String getItemType() { + return itemType; + } + + public void setItemType(String itemType) { + this.itemType = itemType; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public List getImageList() { + return imageList; + } + + public void setImageList(List imageList) { + this.imageList = imageList; + } + + public List getSkus() { + return skus; + } + + public void setSkus(List skus) { + this.skus = skus; + } + + public List getAttrs() { + return attrs; + } + + public void setAttrs(List attrs) { + this.attrs = attrs; + } + + /** + * 租赁SKU请求实体类 + */ + @ApiModel(value = "RentSkuRequest", description = "租赁SKU信息") + public static class RentSkuRequest { + + @ApiModelProperty(value = "商家侧SKU ID,要求APPID下全局唯一", required = true) + private String outSkuId; + + @ApiModelProperty(value = "售卖状态") + private String saleStatus; + + @ApiModelProperty(value = "商品销售属性") + private List skuAttrs; + + @ApiModelProperty(value = "SKU类型,枚举值:RENT(租赁sku)、SALE(售卖sku)", example = "RENT") + private String skuType; + + @ApiModelProperty(value = "SKU售价,如果有起租天数总租金,该字段不用填写") + private Long salePrice; + + @ApiModelProperty(value = "SKU原价,如果有起租天数总租金,该字段不用填写") + private Long originalPrice; + + @ApiModelProperty(value = "SKU库存") + private Long stockNum; + + @ApiModelProperty(value = "价格单元,租赁sku:元/日,售卖sku:元", example = "元/日") + private String priceUnit; + + // Getters and Setters + + public String getOutSkuId() { + return outSkuId; + } + + public void setOutSkuId(String outSkuId) { + this.outSkuId = outSkuId; + } + + public String getSaleStatus() { + return saleStatus; + } + + public void setSaleStatus(String saleStatus) { + this.saleStatus = saleStatus; + } + + public List getSkuAttrs() { + return skuAttrs; + } + + public void setSkuAttrs(List skuAttrs) { + this.skuAttrs = skuAttrs; + } + + public String getSkuType() { + return skuType; + } + + public void setSkuType(String skuType) { + this.skuType = skuType; + } + + public Long getSalePrice() { + return salePrice; + } + + public void setSalePrice(Long salePrice) { + this.salePrice = salePrice; + } + + public Long getOriginalPrice() { + return originalPrice; + } + + public void setOriginalPrice(Long originalPrice) { + this.originalPrice = originalPrice; + } + + public Long getStockNum() { + return stockNum; + } + + public void setStockNum(Long stockNum) { + this.stockNum = stockNum; + } + + public String getPriceUnit() { + return priceUnit; + } + + public void setPriceUnit(String priceUnit) { + this.priceUnit = priceUnit; + } + } + + /** + * SKU属性请求实体类 (sku_attrs 销售属性) + */ + @ApiModel(value = "SkuAttrRequest", description = "SKU销售属性信息") + public static class SkuAttrRequest { + + @ApiModelProperty(value = "销售属性key", required = true) + private String attrKey; + + @ApiModelProperty(value = "销售属性类型,枚举值:spec(规格)、custom(自定义属性)", example = "custom") + private String attrType; + + @ApiModelProperty(value = "销售属性值", required = true) + private String attrValue; + + // Getters and Setters + + public String getAttrKey() { + return attrKey; + } + + public void setAttrKey(String attrKey) { + this.attrKey = attrKey; + } + + public String getAttrType() { + return attrType; + } + + public void setAttrType(String attrType) { + this.attrType = attrType; + } + + public String getAttrValue() { + return attrValue; + } + + public void setAttrValue(String attrValue) { + this.attrValue = attrValue; + } + } + + /** + * 商品属性请求实体类 (attrs 商品属性) + */ + @ApiModel(value = "ItemAttrRequest", description = "商品属性信息") + public static class ItemAttrRequest { + + @ApiModelProperty(value = "商品属性key", required = true) + private String attrKey; + + @ApiModelProperty(value = "商品属性值", required = true) + private String attrValue; + + // Getters and Setters + + public String getAttrKey() { + return attrKey; + } + + public void setAttrKey(String attrKey) { + this.attrKey = attrKey; + } + + public String getAttrValue() { + return attrValue; + } + + public void setAttrValue(String attrValue) { + this.attrValue = attrValue; + } + } +} diff --git a/src/main/java/com/sczx/pay/alipay/po/RentCommodityConfig.java b/src/main/java/com/sczx/pay/alipay/po/RentCommodityConfig.java new file mode 100644 index 0000000..437eb35 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/po/RentCommodityConfig.java @@ -0,0 +1,163 @@ +package com.sczx.pay.alipay.po; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 租赁商品配置实体类 + */ +public class RentCommodityConfig { + + /** + * 商品名称 + */ + private String name; + + /** + * 商品描述 + */ + private String description; + + /** + * 是否自定义租赁 (0:否, 1:是) + */ + private String customLease; + + /** + * 斜率公式 + */ + private String slopeFormula; + + /** + * 时间单位 + */ + private String durationUnit; + + /** + * 买断价格 + */ + private String buyoutPrice; + + /** + * 价格单位 + */ + private String priceUnit; + + /** + * 时段价格列表 + */ + private List durationPriceList; + + // Getters and Setters + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCustomLease() { + return customLease; + } + + public void setCustomLease(String customLease) { + this.customLease = customLease; + } + + public String getSlopeFormula() { + return slopeFormula; + } + + public void setSlopeFormula(String slopeFormula) { + this.slopeFormula = slopeFormula; + } + + public String getDurationUnit() { + return durationUnit; + } + + public void setDurationUnit(String durationUnit) { + this.durationUnit = durationUnit; + } + + public String getBuyoutPrice() { + return buyoutPrice; + } + + public void setBuyoutPrice(String buyoutPrice) { + this.buyoutPrice = buyoutPrice; + } + + public String getPriceUnit() { + return priceUnit; + } + + public void setPriceUnit(String priceUnit) { + this.priceUnit = priceUnit; + } + + public List getDurationPriceList() { + return durationPriceList; + } + + public void setDurationPriceList(List durationPriceList) { + this.durationPriceList = durationPriceList; + } + + /** + * 时段价格实体类 + */ + public static class DurationPrice { + + /** + * 时段 + */ + private String duration; + + /** + * 总原价 + */ + private String totalOriginalPrice; + + /** + * 总售价 + */ + private String totalSalePrice; + + // Getters and Setters + + public String getDuration() { + return duration; + } + + public void setDuration(String duration) { + this.duration = duration; + } + + public String getTotalOriginalPrice() { + return totalOriginalPrice; + } + + public void setTotalOriginalPrice(String totalOriginalPrice) { + this.totalOriginalPrice = totalOriginalPrice; + } + + public String getTotalSalePrice() { + return totalSalePrice; + } + + public void setTotalSalePrice(String totalSalePrice) { + this.totalSalePrice = totalSalePrice; + } + } +} diff --git a/src/main/java/com/sczx/pay/alipay/po/RentRuleItem.java b/src/main/java/com/sczx/pay/alipay/po/RentRuleItem.java new file mode 100644 index 0000000..e41cfcb --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/po/RentRuleItem.java @@ -0,0 +1,314 @@ +package com.sczx.pay.alipay.po; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 车型租赁规则项实体类 + */ +@Data +public class RentRuleItem implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 车型ID + */ + private Long id; + + /** + * 外部商品ID + */ + private String outItemId; + + /** + * 阿里商品ID + */ + private String aliItemId; + + /** + * 外部SKU ID + */ + private String outSkuId; + + /** + * 商品描述 + */ + private String itemDesc; + + /** + * 品牌名称 + */ + private String brandName; + + /** + * 车型名称 + */ + private String modelName; + + /** + * 电池类别名称 + */ + private String categoryName; + + /** + * 租赁天数(当类型为"按天数"时使用) + */ + private Integer rentalDays; + + /** + * 套餐类型 + */ + private String rentType; + + /** + * 套餐名称 + */ + private String ruleName; + + + private String pagePath; + /** + * 车型照片 + */ + private String image; + + /** + * 车型照片列表1 + */ + private String imageList1; + + /** + * 车型照片列表2 + */ + private String imageList2; + + /** + * 车型照片列表3 + */ + private String imageList3; + + /** + * 是否支持免押(0不支持 1支持) + */ + private String depositFree; + + /** + * 押金价格(元) + */ + private BigDecimal depositPrice; + + /** + * 租车价格(元) + */ + private BigDecimal rentalPrice; + + /** + * 电池规则ID + */ + private Long batteryRuleId; + + /** + * 车辆规则ID + */ + private Long carRuleId; + + /** + * 车型ID + */ + private Long carModelId; + + /** + * 品牌ID + */ + private Long brandId; + + // Getters and Setters + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getOutItemId() { + return outItemId; + } + + public void setOutItemId(String outItemId) { + this.outItemId = outItemId; + } + + public String getAliItemId() { + return aliItemId; + } + + public void setAliItemId(String aliItemId) { + this.aliItemId = aliItemId; + } + + public String getOutSkuId() { + return outSkuId; + } + + public void setOutSkuId(String outSkuId) { + this.outSkuId = outSkuId; + } + + public String getItemDesc() { + return itemDesc; + } + + public void setItemDesc(String itemDesc) { + this.itemDesc = itemDesc; + } + + public String getBrandName() { + return brandName; + } + + public void setBrandName(String brandName) { + this.brandName = brandName; + } + + public String getModelName() { + return modelName; + } + + public void setModelName(String modelName) { + this.modelName = modelName; + } + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(String categoryName) { + this.categoryName = categoryName; + } + + public Integer getRentalDays() { + return rentalDays; + } + + public void setRentalDays(Integer rentalDays) { + this.rentalDays = rentalDays; + } + + public String getRentType() { + return rentType; + } + + public void setRentType(String rentType) { + this.rentType = rentType; + } + + public String getRuleName() { + return ruleName; + } + + public void setRuleName(String ruleName) { + this.ruleName = ruleName; + } + + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + + public String getImageList1() { + return imageList1; + } + + public void setImageList1(String imageList1) { + this.imageList1 = imageList1; + } + + public String getImageList2() { + return imageList2; + } + + public void setImageList2(String imageList2) { + this.imageList2 = imageList2; + } + + public String getImageList3() { + return imageList3; + } + + public void setImageList3(String imageList3) { + this.imageList3 = imageList3; + } + + public String getDepositFree() { + return depositFree; + } + + public void setDepositFree(String depositFree) { + this.depositFree = depositFree; + } + + public BigDecimal getDepositPrice() { + return depositPrice; + } + + public void setDepositPrice(BigDecimal depositPrice) { + this.depositPrice = depositPrice; + } + + public BigDecimal getRentalPrice() { + return rentalPrice; + } + + public void setRentalPrice(BigDecimal rentalPrice) { + this.rentalPrice = rentalPrice; + } + + public Long getBatteryRuleId() { + return batteryRuleId; + } + + public void setBatteryRuleId(Long batteryRuleId) { + this.batteryRuleId = batteryRuleId; + } + + public Long getCarRuleId() { + return carRuleId; + } + + public void setCarRuleId(Long carRuleId) { + this.carRuleId = carRuleId; + } + + public Long getCarModelId() { + return carModelId; + } + + public void setCarModelId(Long carModelId) { + this.carModelId = carModelId; + } + + public Long getBrandId() { + return brandId; + } + + public void setBrandId(Long brandId) { + this.brandId = brandId; + } + + public String getPagePath() { + return pagePath; + } + + public void setPagePath(String pagePath) { + this.pagePath = pagePath; + } + + +} diff --git a/src/main/java/com/sczx/pay/alipay/service/ImageUploadService.java b/src/main/java/com/sczx/pay/alipay/service/ImageUploadService.java new file mode 100644 index 0000000..d24973e --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/service/ImageUploadService.java @@ -0,0 +1,47 @@ +package com.sczx.pay.alipay.service; + +import com.alipay.api.AlipayApiException; +import com.alipay.api.response.AlipayMarketingImageListQueryResponse; +import com.alipay.api.response.AlipayMarketingImagedirectoryListQueryResponse; +import com.sczx.pay.alipay.vo.ImageDirectoryRequest; +import com.sczx.pay.alipay.vo.OpenResponse; + +import java.util.List; + + +public interface ImageUploadService { + /** + * 上传图片,返回imageId + */ + String uploadAndReturnId(String imagePath, String uploadScene) throws AlipayApiException; + + /** + * 创建图片空间目录 + */ + OpenResponse createImageDirectory(ImageDirectoryRequest createRequest); + + /** + * 修改图片空间目录 + */ + OpenResponse updateImageDirectory(ImageDirectoryRequest updateRequest); + + /** + * 分页查询图片空间目录 + */ + OpenResponse queryImageDirectoryList(Long pageNum, Long pageSize, ImageDirectoryRequest queryRequest); + + /** + * 修改图片信息 + */ + OpenResponse updateImage(String imageIndexId, String fileName, String imageDirectoryId); + + /** + * 分页查询图片 + */ + OpenResponse queryImageList(Long pageNum, Long pageSize, String imageIndexId, String fileName, String imageDirectoryId); + + /** + * 删除图片 + */ + OpenResponse deleteImage(List imageIndexIdList); +} diff --git a/src/main/java/com/sczx/pay/alipay/service/ItemService.java b/src/main/java/com/sczx/pay/alipay/service/ItemService.java new file mode 100644 index 0000000..4a55768 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/service/ItemService.java @@ -0,0 +1,44 @@ +package com.sczx.pay.alipay.service; + +import com.alipay.api.domain.AppxCategoryVO; +import com.alipay.api.response.AlipayOpenAppItemListQueryResponse; +import com.alipay.api.response.AlipayOpenAppItemQueryResponse; +import com.alipay.api.response.AlipayOpenAppItemTemplateQueryResponse; +import com.sczx.pay.alipay.po.RentRuleItem; +import com.sczx.pay.alipay.vo.ItemCreateRequest; +import com.sczx.pay.alipay.vo.OpenResponse; + +import java.util.List; + +public interface ItemService { + /** + * 获取叶子节点商品类目列表(默认获取类目状态为资质已开通的) + */ + OpenResponse> getCategories(String itemType, String catStatus); + + /** + * 查询普通商品模板信息 + */ + OpenResponse queryTemplate(String categoryId, String itemType); + + /** + * 创建租赁类(实物)商品 + */ + OpenResponse createRentItem(RentRuleItem param); + + OpenResponse modifyItem(RentRuleItem param); + + /** + * 根据业务属性写死自提履约信息 + */ + OpenResponse syncDeliveryInfo(); + + OpenResponse deliveryInfoQuery(); + + OpenResponse deliveryTemplateQuery(); + + OpenResponse shopQuery(); + + OpenResponse queryItemDetail(String itemId, String outItemId, long needEditSpu); + +} diff --git a/src/main/java/com/sczx/pay/alipay/service/RentOrderService.java b/src/main/java/com/sczx/pay/alipay/service/RentOrderService.java new file mode 100644 index 0000000..d5c2e7a --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/service/RentOrderService.java @@ -0,0 +1,11 @@ +package com.sczx.pay.alipay.service; + +import com.alipay.api.AlipayApiException; +import com.alipay.api.request.AlipayCommerceRentOrderCreateRequest; +import com.alipay.api.response.AlipayCommerceRentOrderCreateResponse; +import com.sczx.pay.alipay.vo.RentOrderCreateRequest; + +public interface RentOrderService { + + AlipayCommerceRentOrderCreateResponse rentOrderCreate(RentOrderCreateRequest request) throws AlipayApiException; +} diff --git a/src/main/java/com/sczx/pay/alipay/service/RentRuleItemService.java b/src/main/java/com/sczx/pay/alipay/service/RentRuleItemService.java new file mode 100644 index 0000000..4c28450 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/service/RentRuleItemService.java @@ -0,0 +1,8 @@ +package com.sczx.pay.alipay.service; + +import java.util.Map; + +public interface RentRuleItemService { + + Map syncItem(String brandId,String carModelId,String carRuleId,String batteryRuleId); +} diff --git a/src/main/java/com/sczx/pay/alipay/service/impl/ImageUploadServiceImpl.java b/src/main/java/com/sczx/pay/alipay/service/impl/ImageUploadServiceImpl.java new file mode 100644 index 0000000..011d791 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/service/impl/ImageUploadServiceImpl.java @@ -0,0 +1,348 @@ +package com.sczx.pay.alipay.service.impl; + +import com.alipay.api.AlipayApiException; +import com.alipay.api.FileItem; +import com.alipay.api.domain.*; +import com.alipay.api.internal.util.file.IOUtils; +import com.alipay.api.request.*; +import com.alipay.api.response.*; +import com.sczx.pay.alipay.service.ImageUploadService; +import com.sczx.pay.alipay.vo.ImageDirectoryRequest; +import com.sczx.pay.alipay.vo.OpenResponse; +import com.sczx.pay.utils.AlipayApiCallback; +import com.sczx.pay.utils.AlipayApiTemplate; +import com.sczx.pay.utils.AlipaySdkUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Base64; +import java.util.List; +import net.coobird.thumbnailator.Thumbnails; + + +@Service +public class ImageUploadServiceImpl implements ImageUploadService { + private static final Logger logger = LogManager.getLogger(ImageUploadServiceImpl.class); + + @Autowired + private AlipaySdkUtil alipaySdkUtil; + + @Override + public String uploadAndReturnId(String imagePath, String uploadScene) throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayMarketingImageEnhanceUploadRequest request = new AlipayMarketingImageEnhanceUploadRequest(); + + // 设置图片是否需要优化 + request.setNeedEnhance(true); + + // 设置图片上传场景 + request.setUploadScene(uploadScene); + + // 设置图片内容 + try { + //String imageBase64 = getImageFromUrlAndConvertToBase64(imagePath); + String imageBase64 = getImageFromUrlAndConvertToBase64WithResize(imagePath, 750, 750); + FileItem imageContent = new FileItem("imageContent.jpg",Base64.getDecoder().decode(imageBase64)); + request.setImageContent(imageContent); + } catch (IOException e) { + throw new AlipayApiException("alipay.marketing.image.enhance.upload调用失败:" + e); + } + + try { + AlipayMarketingImageEnhanceUploadResponse response = alipaySdkUtil.execute(request); + logger.info(response.getBody()); + + if (response.isSuccess()) { + logger.info("alipay.marketing.image.enhance.upload调用成功"); + return response.getImageId(); + } else { + logger.error("alipay.marketing.image.enhance.upload调用失败"); + throw new AlipayApiException("alipay.marketing.image.enhance.upload调用失败:" + response.getBody()); + } + } catch (Exception e) { + logger.error("alipay.marketing.image.enhance.upload调用失败", e); + throw new AlipayApiException("alipay.marketing.image.enhance.upload调用失败:" + e); + } + } + + @Override + public OpenResponse createImageDirectory(ImageDirectoryRequest createRequest) { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + @Override + public AlipayMarketingImagedirectoryCreateResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayMarketingImagedirectoryCreateRequest request = new AlipayMarketingImagedirectoryCreateRequest(); + AlipayMarketingImagedirectoryCreateModel model = new AlipayMarketingImagedirectoryCreateModel(); + + // 设置目录名 + model.setImageDirectoryName(createRequest.getImageDirectoryName()); + + // 设置归属父目录id + model.setParentDirectoryId(createRequest.getParentDirectoryId()); + + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public String getData(AlipayMarketingImagedirectoryCreateResponse response) { + return response.getImageDirectoryId(); + } + + @Override + public String getApiName() { + return "alipay.marketing.imagedirectory.create"; + } + }); + } + + @Override + public OpenResponse updateImageDirectory(ImageDirectoryRequest updateRequest) { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + @Override + public AlipayMarketingImagedirectoryModifyResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayMarketingImagedirectoryModifyRequest request = new AlipayMarketingImagedirectoryModifyRequest(); + AlipayMarketingImagedirectoryModifyModel model = new AlipayMarketingImagedirectoryModifyModel(); + + // 设置目录id + model.setImageDirectoryId(updateRequest.getImageDirectoryId()); + + if (StringUtils.isNotBlank(updateRequest.getImageDirectoryName())) { + // 设置目录名 + model.setImageDirectoryName(updateRequest.getImageDirectoryName()); + } + + if (StringUtils.isNotBlank(updateRequest.getParentDirectoryId())) { + // 设置父目录id + model.setParentDirectoryId(updateRequest.getParentDirectoryId()); + } + + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public Void getData(AlipayMarketingImagedirectoryModifyResponse response) { + return null; + } + + @Override + public String getApiName() { + return "alipay.marketing.imagedirectory.modify"; + } + }); + } + + @Override + public OpenResponse queryImageDirectoryList(Long pageNum, Long pageSize, ImageDirectoryRequest queryRequest) { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + @Override + public AlipayMarketingImagedirectoryListQueryResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayMarketingImagedirectoryListQueryRequest request = new AlipayMarketingImagedirectoryListQueryRequest(); + AlipayMarketingImagedirectoryListQueryModel model = new AlipayMarketingImagedirectoryListQueryModel(); + + // 设置页码 + model.setPageNum(pageNum == null ? 1L : pageNum); + + // 设置单页条数 + model.setPageSize(pageSize == null ? 10L : pageSize); + + if (StringUtils.isNotBlank(queryRequest.getImageDirectoryName())) { + // 设置目录名 + model.setImageDirectoryName(queryRequest.getImageDirectoryName()); + } + + if (StringUtils.isNotBlank(queryRequest.getImageDirectoryId())) { + // 设置目录id + model.setImageDirectoryId(queryRequest.getImageDirectoryId()); + } + + if (StringUtils.isNotBlank(queryRequest.getParentDirectoryId())) { + // 设置父目录id + model.setParentDirectoryId(queryRequest.getParentDirectoryId()); + } + + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public AlipayMarketingImagedirectoryListQueryResponse getData(AlipayMarketingImagedirectoryListQueryResponse response) { + return response; + } + + @Override + public String getApiName() { + return "alipay.marketing.imagedirectory.list.query"; + } + }); + } + + @Override + public OpenResponse updateImage(String imageIndexId, String fileName, String imageDirectoryId) { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + @Override + public AlipayMarketingImageModifyResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayMarketingImageModifyRequest request = new AlipayMarketingImageModifyRequest(); + AlipayMarketingImageModifyModel model = new AlipayMarketingImageModifyModel(); + + // 设置图索引id + model.setImageIndexId(imageIndexId); + + if (StringUtils.isNotBlank(fileName)) { + // 设置修改后新文件名 + model.setFileName(fileName); + } + + if (StringUtils.isNotBlank(imageDirectoryId)) { + // 设置修改后新目录id + model.setImageDirectoryId(imageDirectoryId); + } + + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public Void getData(AlipayMarketingImageModifyResponse response) { + return null; + } + + @Override + public String getApiName() { + return "alipay.marketing.image.modify"; + } + }); + } + + @Override + public OpenResponse queryImageList(Long pageNum, Long pageSize, String imageIndexId, String fileName, String imageDirectoryId) { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + @Override + public AlipayMarketingImageListQueryResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayMarketingImageListQueryRequest request = new AlipayMarketingImageListQueryRequest(); + AlipayMarketingImageListQueryModel model = new AlipayMarketingImageListQueryModel(); + + // 设置页码 + model.setPageNum(pageNum == null ? 1L : pageNum); + + // 设置单页条数 + model.setPageSize(pageSize == null ? 10L : pageSize); + + if (StringUtils.isNotBlank(imageIndexId)) { + // 设置图索引id + model.setImageIndexId(imageIndexId); + } + + if (StringUtils.isNotBlank(fileName)) { + // 设置图文件名 + model.setFileName(fileName); + } + + if (StringUtils.isNotBlank(imageDirectoryId)) { + // 设置图归属目录id + model.setImageDirectoryId(imageDirectoryId); + } + + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public AlipayMarketingImageListQueryResponse getData(AlipayMarketingImageListQueryResponse response) { + return response; + } + + @Override + public String getApiName() { + return "alipay.marketing.image.list.query"; + } + }); + } + + @Override + public OpenResponse deleteImage(List imageIndexIdList) { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + @Override + public AlipayMarketingImageDeleteResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayMarketingImageDeleteRequest request = new AlipayMarketingImageDeleteRequest(); + AlipayMarketingImageDeleteModel model = new AlipayMarketingImageDeleteModel(); + + model.setImageIndexIdList(imageIndexIdList); + + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public Void getData(AlipayMarketingImageDeleteResponse response) { + return null; + } + + @Override + public String getApiName() { + return "alipay.marketing.image.delete"; + } + }); + } + + public String getImageFromUrlAndConvertToBase64(String imageUrl) throws IOException { + if (StringUtils.isBlank(imageUrl)) { + throw new IllegalArgumentException("图片URL不能为空"); + } + + URL url = new URL(imageUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + + try (InputStream inputStream = connection.getInputStream()) { + byte[] imageBytes = IOUtils.toByteArray(inputStream); + return Base64.getEncoder().encodeToString(imageBytes); + } finally { + connection.disconnect(); + } + } + + public String getImageFromUrlAndConvertToBase64WithResize(String imageUrl, int width, int height) throws IOException { + if (StringUtils.isBlank(imageUrl)) { + throw new IllegalArgumentException("图片URL不能为空"); + } + + URL url = new URL(imageUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + + try (InputStream inputStream = connection.getInputStream()) { + // 使用Thumbnailator调整图片尺寸 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + Thumbnails.of(inputStream) + .size(width, height) + .outputFormat("jpg") + .toOutputStream(outputStream); + + byte[] imageBytes = outputStream.toByteArray(); + return Base64.getEncoder().encodeToString(imageBytes); + } finally { + connection.disconnect(); + } + } + + +} diff --git a/src/main/java/com/sczx/pay/alipay/service/impl/ItemServiceImpl.java b/src/main/java/com/sczx/pay/alipay/service/impl/ItemServiceImpl.java new file mode 100644 index 0000000..8571411 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/service/impl/ItemServiceImpl.java @@ -0,0 +1,735 @@ +package com.sczx.pay.alipay.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.*; +import com.alipay.api.internal.util.StringUtils; +import com.alipay.api.request.*; +import com.alipay.api.response.*; +import com.sczx.pay.alipay.po.RentCommodityConfig; +import com.sczx.pay.alipay.po.RentRuleItem; +import com.sczx.pay.alipay.service.ImageUploadService; +import com.sczx.pay.alipay.service.ItemService; +import com.sczx.pay.alipay.vo.OpenResponse; +import com.sczx.pay.mapper.RentRuleItemMapper; +import com.sczx.pay.utils.AlipayApiCallback; +import com.sczx.pay.utils.AlipayApiTemplate; +import com.sczx.pay.utils.AlipaySdkUtil; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.CollectionUtils; +import org.springframework.stereotype.Service; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.ArrayList; + + +@Service +public class ItemServiceImpl implements ItemService { + + private static final Logger logger = LogManager.getLogger(ItemServiceImpl.class); + + @Autowired + private AlipaySdkUtil alipaySdkUtil; + + @Autowired + private ImageUploadService imageUploadService; + + @Autowired + private RentRuleItemMapper rentRuleItemMapper; + + @Override + public OpenResponse> getCategories(String itemType, String catStatus) { + return AlipayApiTemplate.execute(new AlipayApiCallback, AlipayOpenAppItemAllcategoryQueryResponse>() { + @Override + public AlipayOpenAppItemAllcategoryQueryResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayOpenAppItemAllcategoryQueryRequest request = new AlipayOpenAppItemAllcategoryQueryRequest(); + AlipayOpenAppItemAllcategoryQueryModel model = new AlipayOpenAppItemAllcategoryQueryModel(); + + // 设置商品类型 + model.setItemType(itemType); + + model.setCatStatus(StringUtils.isEmpty(catStatus) ? "AUDIT_PASSED" : catStatus); + + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public List getData(AlipayOpenAppItemAllcategoryQueryResponse response) { + List cats = response.getCats(); + if (CollectionUtils.isEmpty(cats)) { + return null; + } + List childs = new ArrayList<>(); + for (CategoryAndParentVO cat : cats) { + if (CollectionUtils.isEmpty(cat.getCatAndParent())) { + continue; + } + // 获取叶子节点(cat_status不为空的是叶子结点) + Optional categoryVO = cat.getCatAndParent().stream().filter(Objects::nonNull) + .filter(child -> StringUtils.areNotEmpty(child.getCatStatus())).findFirst(); + categoryVO.ifPresent(childs::add); + } + return childs; + } + + @Override + public String getApiName() { + return "alipay.open.app.item.allcategory.query"; + } + }); + } + + @Override + public OpenResponse queryTemplate(String categoryId, String itemType) { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + @Override + public AlipayOpenAppItemTemplateQueryResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayOpenAppItemTemplateQueryRequest request = new AlipayOpenAppItemTemplateQueryRequest(); + AlipayOpenAppItemTemplateQueryModel model = new AlipayOpenAppItemTemplateQueryModel(); + + // 设置类目id + model.setCategoryId(categoryId); + + model.setItemType(itemType); + + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public AlipayOpenAppItemTemplateQueryResponse getData(AlipayOpenAppItemTemplateQueryResponse response) { + return response; + } + + @Override + public String getApiName() { + return "alipay.open.app.item.template.query"; + } + }); + } + + + @Override + public OpenResponse createRentItem(RentRuleItem rentRuleItem) { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + + @Override + public AlipayOpenAppItemCreateResponse process() throws AlipayApiException { + + //RentRuleItem rentRuleItem = rentRuleItemMapper.selectByIds(param.getBatteryRuleId(), param.getCarRuleId(), param.getCarModelId(), param.getBrandId()); + RentRuleItem rentRuleItemvo = rentRuleItem; + // 构造请求参数以调用接口 + AlipayOpenAppItemCreateRequest request = new AlipayOpenAppItemCreateRequest(); + AlipayOpenAppItemCreateModel model = new AlipayOpenAppItemCreateModel(); + + + // 设置平台类目 + model.setCategoryId("C001684401"); + // 设置商品名称由品牌、型号、电池规格、租赁类型、套餐名称拼接 + String title = rentRuleItem.getBrandName() + rentRuleItem.getModelName() +"_"+ rentRuleItem.getCategoryName() + "_" + rentRuleItem.getRentType()+rentRuleItem.getRuleName(); + rentRuleItemvo.setItemDesc(title); + // 设置商品名称 + model.setTitle(title); + // 设置导购描述 + model.setDesc(title); + // 设置平台类目 + model.setCategoryId("C001684401"); + // 设置商品主图 + String imgStr = imageUploadService.uploadAndReturnId(rentRuleItem.getImage(), "ITEM_HEAD_IMG"); + model.setHeadImg(imgStr); + rentRuleItemvo.setImage(imgStr); + // 设置商品模版类型 + model.setItemType("2"); + //sku_attrs + + // 设置价格单元 + model.setPriceUnit("元/日"); + + //设置商品ID + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + String uuidSuffix = UUID.randomUUID().toString().replace("-", "").substring(0, 6).toUpperCase(); + String outItemId = "OI"+timestamp + uuidSuffix; + model.setOutItemId(outItemId); + rentRuleItemvo.setOutItemId(outItemId); + rentRuleItemvo.setOutSkuId(outItemId+"_sku"); + //设置商品详情页路径 + model.setPath("alipays://platformapi/startapp?appId=2021004169641281&page=pages/rentGoodsDetail/index?outItemId="+outItemId); + rentRuleItemvo.setPagePath("alipays://platformapi/startapp?appId=2021004169641281&page=pages/rentGoodsDetail/index?outItemId="+outItemId); + + //设置商品状态 + model.setSaleStatus("AVAILABLE"); + //sku_attrs + model.setSkus(getItemSkus(rentRuleItem, model.getImageList(), model.getItemType())); + + // 设置属性列表 + model.setAttrs(getItemAttrs(rentRuleItem, model.getImageList(), model.getItemType())); + + RentRuleItem res = rentRuleItemMapper.selectItemByFourIds(rentRuleItem.getBatteryRuleId(), rentRuleItem.getCarRuleId(), rentRuleItem.getCarModelId(), rentRuleItem.getBrandId()); + + if(res != null ){ + rentRuleItemMapper.updateItemIdByOutItemId(rentRuleItemvo); + }else { + rentRuleItemMapper.insertByFourId(rentRuleItemvo); + } + logger.info("属性列表:" + JSON.toJSONString(model)); + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public String getData(AlipayOpenAppItemCreateResponse response) { + rentRuleItemMapper.updateByOutItemId(response.getOutItemId(), response.getItemId()); + return response.getItemId(); + } + + @Override + public String getApiName() { + return "alipay.open.app.item.create"; + } + }); + } + + + + @Override + public OpenResponse modifyItem(RentRuleItem rentRuleItem) { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + + @Override + public AlipayOpenAppItemModifyResponse process() throws AlipayApiException { + + // 构造请求参数以调用接口 + AlipayOpenAppItemModifyRequest request = new AlipayOpenAppItemModifyRequest(); + AlipayOpenAppItemModifyModel model = new AlipayOpenAppItemModifyModel(); + + RentRuleItem rentRuleItemvo = rentRuleItem; + // 设置平台类目 + model.setCategoryId("C001684401"); + // 设置商品名称由品牌、型号、电池规格、租赁类型、套餐名称拼接 + String title = rentRuleItem.getBrandName() + rentRuleItem.getModelName() +"_"+ rentRuleItem.getCategoryName() + "_" + rentRuleItem.getRentType()+rentRuleItem.getRuleName(); + rentRuleItemvo.setItemDesc(title); + // 设置商品名称 + model.setTitle(title); + // 设置导购描述 + model.setDesc(title); + // 设置平台类目 + model.setCategoryId("C001684401"); + // 设置商品主图 + String imgStr = imageUploadService.uploadAndReturnId(rentRuleItem.getImage(), "ITEM_HEAD_IMG"); + model.setHeadImg(imgStr); + rentRuleItemvo.setImage(imgStr); + // 设置商品模版类型 + model.setItemType("2"); + //sku_attrs + + // 设置价格单元 + model.setPriceUnit("元/日"); + + //设置商品ID + model.setOutItemId(rentRuleItem.getOutItemId()); + rentRuleItemvo.setOutItemId(rentRuleItem.getOutItemId()); + rentRuleItemvo.setOutSkuId(rentRuleItem.getOutSkuId()); + //设置商品详情页路径 + model.setPath("alipays://platformapi/startapp?appId=2021004169641281&page=pages/rentGoodsDetail/index?outItemId="+rentRuleItem.getOutItemId()); + rentRuleItemvo.setPagePath("alipays://platformapi/startapp?appId=2021004169641281&page=pages/rentGoodsDetail/index?outItemId="+rentRuleItem.getOutItemId()); + + //设置商品状态 + model.setSaleStatus("AVAILABLE"); + //sku_attrs + model.setSkus(modifyItemSkus(rentRuleItem)); + + // 设置属性列表 + model.setAttrs(getItemAttrs(rentRuleItem, model.getImageList(), model.getItemType())); + + RentRuleItem res = rentRuleItemMapper.selectItemByFourIds(rentRuleItem.getBatteryRuleId(), rentRuleItem.getCarRuleId(), rentRuleItem.getCarModelId(), rentRuleItem.getBrandId()); + + if(res != null ){ + rentRuleItemMapper.updateItemIdByOutItemId(rentRuleItemvo); + }else { + rentRuleItemMapper.insertByFourId(rentRuleItemvo); + } + + logger.info("属性列表:" + JSON.toJSONString(model)); + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public String getData(AlipayOpenAppItemModifyResponse response) { + //rentRuleItemMapper.updateItemIdByOutItemId(response.getOutItemId(), response.getItemId()); + return response.getItemId(); + } + + @Override + public String getApiName() { + return "alipay.open.app.item.modify"; + } + }); + } + + @Override + public OpenResponse syncDeliveryInfo() { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + @Override + public AlipayOpenAppDeliveryInfoSyncResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayOpenAppDeliveryInfoSyncRequest request = new AlipayOpenAppDeliveryInfoSyncRequest(); + AlipayOpenAppDeliveryInfoSyncModel model = new AlipayOpenAppDeliveryInfoSyncModel(); + // 设置履约类型 + model.setDeliveryType("PICKUP"); + model.setDeliveryName("电动车租赁自提履约"); + + // 设置履约信息ID + model.setDeliveryId("2025090124000116301610"); + + // 设置履约属性 + List attrs = new ArrayList(); + AppItemAttrVO attrs0 = new AppItemAttrVO(); + attrs0.setAttrKey("delivery_name"); + attrs0.setAttrValue("同城门店自提"); + attrs.add(attrs0); + model.setAttrs(attrs); + + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public String getData(AlipayOpenAppDeliveryInfoSyncResponse response) { + return response.getDeliveryId(); + } + + @Override + public String getApiName() { + return "alipay.open.app.delivery.info.sync"; + } + }); + } + + + @Override + public OpenResponse deliveryInfoQuery() { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + @Override + public AlipayOpenAppDeliveryInfoQueryResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayOpenAppDeliveryInfoQueryRequest request = new AlipayOpenAppDeliveryInfoQueryRequest(); + AlipayOpenAppDeliveryInfoQueryModel model = new AlipayOpenAppDeliveryInfoQueryModel(); + // 设置履约类型 + model.setDeliveryType("PICKUP"); + + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public String getData(AlipayOpenAppDeliveryInfoQueryResponse response) { + return response.getBody(); + } + + @Override + public String getApiName() { + return "alipay.open.app.delivery.info.query"; + } + }); + } + + + @Override + public OpenResponse deliveryTemplateQuery() { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + @Override + public AlipayOpenAppDeliveryTemplateQueryResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayOpenAppDeliveryTemplateQueryRequest request = new AlipayOpenAppDeliveryTemplateQueryRequest(); + AlipayOpenAppDeliveryTemplateQueryModel model = new AlipayOpenAppDeliveryTemplateQueryModel(); + // 设置履约类型 + model.setDeliveryType("PICKUP"); + //ST1000000000000010 + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public String getData(AlipayOpenAppDeliveryTemplateQueryResponse response) { + return response.getBody(); + } + + @Override + public String getApiName() { + return "alipay.open.app.delivery.template.query"; + } + }); + } + + + @Override + public OpenResponse shopQuery() { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + @Override + public AntMerchantExpandShopPageQueryResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AntMerchantExpandShopPageQueryRequest request = new AntMerchantExpandShopPageQueryRequest(); + AntMerchantExpandShopPageQueryModel model = new AntMerchantExpandShopPageQueryModel(); + // 设置履约类型 + // 设置外部门店编号 + //model.setStoreId("No.0731101"); + model.setIpRoleId("2021004169641281"); + + // 设置页码 + model.setPageNum(1L); + + // 设置每页查询大小 + model.setPageSize(10L); + //ST1000000000000010 + request.setBizModel(model); + return alipaySdkUtil.execute(request); + } + + @Override + public String getData(AntMerchantExpandShopPageQueryResponse response) { + return response.getBody(); + } + + @Override + public String getApiName() { + return "ant.merchant.expand.shop.page.query"; + } + }); + } + + + private AlipayOpenAppItemCreateModel getAlipayOpenAppItemCreateModel(RentRuleItem rentRuleItem) throws AlipayApiException { + AlipayOpenAppItemCreateModel model = new AlipayOpenAppItemCreateModel(); + + // 设置商家侧商品ID + model.setOutItemId(rentRuleItem.getOutItemId()); + + + + + // 设置类目ID + model.setCategoryId("C001684401"); + + + + + + // 设置商品图片的文件id列表 +// List imageList = new ArrayList<>(); +// imageList.add(imageUploadService.uploadAndReturnId("file/head.jpg", "ITEM_IMAGE_LIST")); +// model.setImageList(imageList); + + //model.setItemDetailsPageModel("1"); + + // 设置商品详情页URL,需要先完成商品详情页组件开发接入 +// try { +// String url = "plugin-private://2021003177653028/pages/goodsDetail/goodsDetail?outItemId=" + param.getOutItemId(); +// model.setPath("alipays://platformapi/startApp?appId=" + alipaySdkUtil.getAppId() +// + "&page=" + URLEncoder.encode(url, "utf-8")); +// } catch (UnsupportedEncodingException e) { +// throw new AlipayApiException("url解析失败"); +// } + + + + // 设置商品售价 + Long rentalPriceInCents = rentRuleItem.getRentalPrice().multiply(new BigDecimal(100)).setScale(0, java.math.RoundingMode.HALF_UP).longValue(); + model.setSalePrice(rentalPriceInCents); + + // 设置商品原价 + model.setOriginalPrice(rentalPriceInCents); + + // 设置库存 + model.setStockNum(100L); + + // 设置商品售卖状态 + model.setSaleStatus("AVAILABLE"); + + return model; + } + + private List getTemplateItemAttrs(String categoryId, String itemType) throws AlipayApiException { + OpenResponse templateResponse = queryTemplate(categoryId, itemType); + if (!"10000".equals(templateResponse.getCode())) { + throw new AlipayApiException("查询普通商品模板失败"); + } + + if (templateResponse.getData().getAttr() == null) { + return null; + } + + return templateResponse.getData().getAttr().getItemAttrList(); + } + + private List getItemAttrs(RentRuleItem param, List imageList, String itemType) throws AlipayApiException { + List attributes = getTemplateItemAttrs("C001684401", itemType); + if (CollectionUtils.isEmpty(attributes)) { + return null; + } + List attrs = new ArrayList<>(); + for (AttributeVO attr : attributes) { + AppItemAttrVO appItemAttrVO = new AppItemAttrVO(); + appItemAttrVO.setAttrKey(attr.getKey()); + + switch (attr.getKey()) { + // -----BEGIN 普通实物类商品可传入以下属性 ----- +// case "delivery_info": +// // 商品履约信息 +// JSONObject jsonObject = new JSONObject(); +// jsonObject.put("delivery_id", "2025090124000116301610"); +// //jsonObject.put("shop_ids", param.getShopIds()); +// JSONArray jsonArray = new JSONArray(); +// jsonArray.add(jsonObject); +// appItemAttrVO.setAttrValue(JSONArray.toJSONString(jsonArray)); +// break; + case "rent_from_numbers_of_day": + // 起租天数 + appItemAttrVO.setAttrValue("1"); + break; + case "lease_term_type": + appItemAttrVO.setAttrValue("短租"); + break; + case "item_fineness": + // 商品成色 + appItemAttrVO.setAttrValue("secondHand"); + break; + case "item_fineness_grade": + appItemAttrVO.setAttrValue("99new"); + break; + case "whether_support_free_deposit": + // 是否支持免押 + appItemAttrVO.setAttrValue("1"); + break; + case "whether_continue_rent": + // 是否可续租 + appItemAttrVO.setAttrValue("1"); + break; + case "whether_buyout": + // 是否可买断 + appItemAttrVO.setAttrValue("1"); + break; + case "收货方式": + appItemAttrVO.setAttrValue("自提"); + break ; + } + if (!StringUtils.isEmpty(appItemAttrVO.getAttrValue())) { + attrs.add(appItemAttrVO); + } + } + return attrs; + } + private List getItemSkus(RentRuleItem param, List imageList, String itemType) throws AlipayApiException { + List itemSkus = new ArrayList<>(); + + ItemSkuCreateVO sku = new ItemSkuCreateVO(); + + // 设置商家侧SKU ID,需要全局唯一 + sku.setOutSkuId(param.getOutItemId() + "_sku"); + + // 设置售卖状态 + sku.setSaleStatus("AVAILABLE"); + + // 设置SKU类型为租赁 + sku.setSkuType("RENT"); + + // 设置价格相关字段 + Long rentalPriceInCents = param.getRentalPrice().multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).longValue(); +// sku.setSalePrice(rentalPriceInCents.toString()); +// // 设置原价为租赁价格的1.2倍 +// Long originalPriceInCents = new BigDecimal(rentalPriceInCents) +// .multiply(new BigDecimal("1.2")) +// .setScale(0, BigDecimal.ROUND_HALF_UP) +// .longValue(); +// sku.setOriginalPrice(originalPriceInCents); + + // 设置库存 + sku.setStockNum(String.valueOf(999999)); + + // 设置价格单元 + sku.setPriceUnit("元/日"); + + // 设置销售属性 + List skuAttrs = new ArrayList<>(); + + // 添加租赁商品属性 + ItemSkuAttrVO rentCommodityAttr = new ItemSkuAttrVO(); + rentCommodityAttr.setAttrKey("rent_commodity"); + rentCommodityAttr.setAttrType("custom"); + + RentCommodityConfig config = new RentCommodityConfig(); + config.setName("门店自提,租完归还"); + config.setDescription("套餐说明"); + config.setCustomLease("0"); + config.setSlopeFormula("DEFAULT_SLOPE_FORMULA"); + config.setDurationUnit("日"); + + //买断价10000元*100 单位分 + config.setBuyoutPrice("1000000"); + config.setPriceUnit("元"); + + int rentalDays = 1; + if(param.getRentalDays() != null && param.getRentalDays() > 0){ + rentalDays = param.getRentalDays(); + } + + // 创建时段价格列表根据基础价格创建3个时段价格 + List durationPriceList = new ArrayList<>(); + + //暂时设置为只保留一个时段价格 + for(int i = 1; i < 2; i++){ + RentCommodityConfig.DurationPrice durationPrice = new RentCommodityConfig.DurationPrice(); + String val = i*100 + ""; + durationPrice.setDuration(i*rentalDays+""); + durationPrice.setTotalSalePrice(param.getRentalPrice() + .multiply(new BigDecimal(val)) + .setScale(0, BigDecimal.ROUND_HALF_UP).toString()); + durationPrice.setTotalOriginalPrice(param.getRentalPrice() + .multiply(new BigDecimal(val)) + .setScale(0, BigDecimal.ROUND_HALF_UP).toString()); + durationPriceList.add(durationPrice); + } + config.setDurationPriceList(durationPriceList); + rentCommodityAttr.setAttrValue(JSON.toJSONString(config)); + + rentCommodityAttr.setAttrKey("收货方式"); + rentCommodityAttr.setAttrValue("到店自提"); + // 根据实际情况设置 + skuAttrs.add(rentCommodityAttr); + sku.setSkuAttrs(skuAttrs); + + + + itemSkus.add(sku); + return itemSkus; + } + + + private List modifyItemSkus(RentRuleItem param) throws AlipayApiException { + List itemSkus = new ArrayList<>(); + + ItemSkuVO sku = new ItemSkuVO(); + + // 设置商家侧SKU ID,需要全局唯一 + sku.setOutSkuId(param.getOutSkuId()); + + // 设置售卖状态 + sku.setSaleStatus("AVAILABLE"); + + // 设置SKU类型为租赁 + sku.setSkuType("RENT"); + + // 设置价格相关字段 + Long rentalPriceInCents = param.getRentalPrice().multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).longValue(); +// sku.setSalePrice(rentalPriceInCents.toString()); +// // 设置原价为租赁价格的1.2倍 +// Long originalPriceInCents = new BigDecimal(rentalPriceInCents) +// .multiply(new BigDecimal("1.2")) +// .setScale(0, BigDecimal.ROUND_HALF_UP) +// .longValue(); +// sku.setOriginalPrice(originalPriceInCents); + + // 设置库存 + sku.setStockNum(999999L); + + // 设置价格单元 + sku.setPriceUnit("元/日"); + + // 设置销售属性 + List skuAttrs = new ArrayList<>(); + + // 添加租赁商品属性 + ItemSkuAttrVO rentCommodityAttr = new ItemSkuAttrVO(); + rentCommodityAttr.setAttrKey("rent_commodity"); + rentCommodityAttr.setAttrType("custom"); + + RentCommodityConfig config = new RentCommodityConfig(); + config.setName("门店自提,租完归还"); + config.setDescription("套餐说明"); + config.setCustomLease("0"); + config.setSlopeFormula("DEFAULT_SLOPE_FORMULA"); + config.setDurationUnit("日"); + + //买断价10000元*100 单位分 + config.setBuyoutPrice("1000000"); + config.setPriceUnit("元"); + + int rentalDays = 1; + if(param.getRentalDays() != null && param.getRentalDays() > 0){ + rentalDays = param.getRentalDays(); + } + + // 创建时段价格列表根据基础价格创建3个时段价格 + List durationPriceList = new ArrayList<>(); + + for(int i = 1; i < 2; i++){ + RentCommodityConfig.DurationPrice durationPrice = new RentCommodityConfig.DurationPrice(); + String val = i*100 + ""; + durationPrice.setDuration(i*rentalDays+""); + durationPrice.setTotalSalePrice(param.getRentalPrice() + .multiply(new BigDecimal(val)) + .setScale(0, BigDecimal.ROUND_HALF_UP).toString()); + durationPrice.setTotalOriginalPrice(param.getRentalPrice() + .multiply(new BigDecimal(val)) + .setScale(0, BigDecimal.ROUND_HALF_UP).toString()); + durationPriceList.add(durationPrice); + } + config.setDurationPriceList(durationPriceList); + rentCommodityAttr.setAttrValue(JSON.toJSONString(config)); + // 根据实际情况设置 + skuAttrs.add(rentCommodityAttr); + sku.setSkuAttrs(skuAttrs); + + itemSkus.add(sku); + return itemSkus; + } + + + @Override + public OpenResponse queryItemDetail(String itemId, String outItemId, long needEditSpu) { + return AlipayApiTemplate.execute(new AlipayApiCallback() { + @Override + public AlipayOpenAppItemQueryResponse process() throws AlipayApiException { + // 构造请求参数以调用接口 + AlipayOpenAppItemQueryRequest request = new AlipayOpenAppItemQueryRequest(); + AlipayOpenAppItemQueryModel model = new AlipayOpenAppItemQueryModel(); + if (!StringUtils.isEmpty(itemId)) { + // 设置支付宝侧商品id + model.setItemId(itemId); + } + + if (!StringUtils.isEmpty(outItemId)) { + // 设置商家侧商品id + model.setOutItemId(outItemId); + } + + // 设置是否查询编辑版本 + model.setNeedEditSpu(needEditSpu); + + request.setBizModel(model); + + return alipaySdkUtil.execute(request); + } + + @Override + public AlipayOpenAppItemQueryResponse getData(AlipayOpenAppItemQueryResponse response) { + return response; + } + + @Override + public String getApiName() { + return "alipay.open.app.item.query"; + } + }); + } + +} diff --git a/src/main/java/com/sczx/pay/alipay/service/impl/RentOrderServiceImpl.java b/src/main/java/com/sczx/pay/alipay/service/impl/RentOrderServiceImpl.java new file mode 100644 index 0000000..59c4abc --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/service/impl/RentOrderServiceImpl.java @@ -0,0 +1,146 @@ +package com.sczx.pay.alipay.service.impl; + +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.*; +import com.alipay.api.request.AlipayCommerceRentOrderCreateRequest; +import com.alipay.api.response.AlipayCommerceRentOrderCreateResponse; +import com.sczx.pay.alipay.po.RentRuleItem; +import com.sczx.pay.alipay.service.RentOrderService; +import com.sczx.pay.alipay.vo.RentOrderCreateRequest; +import com.sczx.pay.mapper.RentRuleItemMapper; +import com.sczx.pay.thirdpart.dto.req.OrderDetailDTO; +import com.sczx.pay.thirdpart.integration.OrderInteg; +import com.sczx.pay.utils.AlipaySdkUtil; +import org.mapstruct.ap.shaded.freemarker.template.utility.DateUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Date; + +@Service +public class RentOrderServiceImpl implements RentOrderService { + + @Autowired + private AlipaySdkUtil alipaySdkUtil; + + @Autowired + private RentRuleItemMapper rentRuleItemMapper; + + @Autowired + private OrderInteg orderInteg; + + @Override + public AlipayCommerceRentOrderCreateResponse rentOrderCreate(RentOrderCreateRequest reqData) throws AlipayApiException { + + AlipayCommerceRentOrderCreateRequest request = new AlipayCommerceRentOrderCreateRequest(); + AlipayCommerceRentOrderCreateModel model = new AlipayCommerceRentOrderCreateModel(); + + RentRuleItem rentRuleItem = rentRuleItemMapper.selectItemByOutItemId(reqData.getOutItemId()); + OrderDetailDTO orderDetailDTO = orderInteg.getOrderDetailByOrderNo(reqData.getOutOrderid()); + + // 设置商户订单号 + model.setOutOrderId(reqData.getOutOrderid()); + + // 设置订单类型 + model.setOrderType(reqData.getRentType()); + + // 设置订单标题 + model.setTitle(rentRuleItem.getItemDesc()); + + // 设置租赁订单页面地址信息 + RentPathInfoDTO pathInfo = new RentPathInfoDTO(); + pathInfo.setDetailPath("pages/rentGoodsDetail/index?outItemId="+reqData.getOutOrderid()); + ArrayList serviceProtocolList = new ArrayList<>(); + RentServiceProtocolDTO serviceProtocol = new RentServiceProtocolDTO(); + serviceProtocol.setProtocolName("两轮电动车租赁协议"); + serviceProtocol.setProtocolPath("pages_order/rentDeal"); + serviceProtocolList.add(serviceProtocol); + model.setPathInfo(pathInfo); + + model.setPathInfo(pathInfo); + + // 设置订单金额信息 + RentOrderPriceInfoDTO priceInfo = new RentOrderPriceInfoDTO(); + // 可小于等于原订单租赁计划中buyout_price金额 + priceInfo.setOrderPrice(orderDetailDTO.getRentalPrice().toString()); + priceInfo.setDepositPrice(orderDetailDTO.getDepositPrice().toString()); + model.setPriceInfo(priceInfo); + + model.setSourceId(reqData.getSourceId()); + // 设置买家支付宝用户唯一标识 + model.setBuyerOpenId(reqData.getOpenId()); + + RentPlanInfoDTO planInfo = new RentPlanInfoDTO(); + planInfo.setRentStartTime(Date.from(orderDetailDTO.getStartRentTime().atZone(ZoneId.systemDefault()).toInstant())); + planInfo.setRentEndTime(Date.from(orderDetailDTO.getEndRentTime().atZone(ZoneId.systemDefault()).toInstant())); + ArrayList planItemList = new ArrayList<>(); + RentInstallmentInfo planItem = new RentInstallmentInfo(); + planItem.setInstallmentNo(1L); + planItem.setInstallmentPrice(orderDetailDTO.getRentalPrice().toString()); + planItem.setPlanPayTime(new Date()); + planItemList.add(planItem); + planInfo.setInstallments(planItemList); + model.setRentPlanInfo(planInfo); + + RentSignInfoDTO signInfo = new RentSignInfoDTO(); + + RentDeductInfoDTO deductInfo = new RentDeductInfoDTO(); + deductInfo.setSignScene("RENT_DEDUCT"); + deductInfo.setServiceName(rentRuleItem.getItemDesc()); + signInfo.setRentDeductInfo(deductInfo); + + RentFundAuthFreezeInfoDTO fundAuthFreezeInfo = new RentFundAuthFreezeInfoDTO(); + //需要实现回调监听 + fundAuthFreezeInfo.setFreezeNotifyUrl("https://www.alipay.com"); + signInfo.setFundAuthFreezeInfo(fundAuthFreezeInfo); + + //需要确认 + RentCreditInfoDTO creditInfo = new RentCreditInfoDTO(); + creditInfo.setZmServiceId("2025061101502300000000760095243636"); + creditInfo.setCategoryId("RENT_PHONE"); + signInfo.setCreditInfo(creditInfo); + model.setRentSignInfo(signInfo); + + ArrayList goodsDetailList = new ArrayList<>(); + RentGoodsDetailInfoDTO goodsDetail = new RentGoodsDetailInfoDTO(); + goodsDetail.setItemName(rentRuleItem.getItemDesc()); + goodsDetail.setOutItemId(rentRuleItem.getOutItemId()); + goodsDetail.setOutSkuId(rentRuleItem.getOutSkuId()); + goodsDetail.setItemCnt("1"); + if (rentRuleItem.getRentalDays() != null) { + BigDecimal rentalPrice = rentRuleItem.getRentalPrice(); + BigDecimal rentalDays = new BigDecimal(rentRuleItem.getRentalDays()); + BigDecimal salePrice = rentalPrice.divide(rentalDays, 2, RoundingMode.HALF_UP); + goodsDetail.setSalePrice(salePrice.toString()); + }else { + goodsDetail.setSalePrice(rentRuleItem.getRentalPrice().toString()); + } + goodsDetail.setItemFineness("secondHand"); + goodsDetail.setItemFinenessGrade("99new"); + goodsDetail.setItemValue("5000.00"); + goodsDetail.setRentModel("R00001"); + goodsDetailList.add(goodsDetail); + model.setItemInfos(goodsDetailList); + + + //TODO 续租 + + RentOrderDeliveryInfoDTO deliveryInfo = new RentOrderDeliveryInfoDTO(); + deliveryInfo.setDeliveryType("SELFPICK"); + RentPickupShopInfoDTO pickupShopInfo = new RentPickupShopInfoDTO(); + pickupShopInfo.setName("当前门店"); + pickupShopInfo.setAddress("门店地址"); + model.setDeliveryInfo(deliveryInfo); + + + request.setBizModel(model); + AlipayCommerceRentOrderCreateResponse response =alipaySdkUtil.execute(request); + return response; + } + + +} diff --git a/src/main/java/com/sczx/pay/alipay/service/impl/RentRuleItemServiceImpl.java b/src/main/java/com/sczx/pay/alipay/service/impl/RentRuleItemServiceImpl.java new file mode 100644 index 0000000..ad83668 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/service/impl/RentRuleItemServiceImpl.java @@ -0,0 +1,64 @@ +package com.sczx.pay.alipay.service.impl; + + +import com.alibaba.fastjson.JSON; +import com.sczx.pay.alipay.po.RentRuleItem; +import com.sczx.pay.alipay.service.ItemService; +import com.sczx.pay.alipay.service.RentRuleItemService; +import com.sczx.pay.alipay.vo.OpenResponse; +import com.sczx.pay.mapper.RentRuleItemMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + + +@Slf4j +@Service +public class RentRuleItemServiceImpl implements RentRuleItemService { + + @Autowired + private RentRuleItemMapper rentRuleItemMapper; + + @Autowired + private ItemService itemService; + + @Override + public Map syncItem(String brandId, String carModelId, String carRuleId, String batteryRuleId) { + RentRuleItem rentRuleItem = rentRuleItemMapper.selectByFourId(Long.valueOf(batteryRuleId), Long.valueOf(carRuleId), Long.valueOf(carModelId), Long.valueOf(brandId)); + log.info("rentRuleItem:{}", JSON.toJSONString(rentRuleItem)); + Map map = new HashMap<>(); + if (rentRuleItem == null){ + map.put("msg","未找到此套餐信息"); + map.put("code","500"); + return map; + } + RentRuleItem ruleRes = rentRuleItemMapper.selectItemByFourIds(Long.valueOf(batteryRuleId), Long.valueOf(carRuleId), Long.valueOf(carModelId), Long.valueOf(brandId)); + OpenResponse response = new OpenResponse<>(); + if(ruleRes == null){ + response = itemService.createRentItem(rentRuleItem); + }else{ + response = itemService.modifyItem(ruleRes); + } + + + + + + + + map.put("code",response.getCode()); + map.put("msg",response.getMsg()); + map.put("subCode",response.getSubCode()); + map.put("subMsg",response.getSubMsg()); + map.put("data",response.getData()); + + return map; + + } +} diff --git a/src/main/java/com/sczx/pay/alipay/vo/AppItemSkuAttrVo.java b/src/main/java/com/sczx/pay/alipay/vo/AppItemSkuAttrVo.java new file mode 100644 index 0000000..0ce54d0 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/vo/AppItemSkuAttrVo.java @@ -0,0 +1,39 @@ +package com.sczx.pay.alipay.vo; + +import com.alipay.api.AlipayObject; +import com.alipay.api.internal.mapping.ApiField; + +public class AppItemSkuAttrVo + extends AlipayObject +{ + private static final long serialVersionUID = 8193327886322884429L; + @ApiField("attr_key") + private String attrKey; + @ApiField("attr_value") + private String attrValue; + + public String getAttrType() { + return attrType; + } + + public void setAttrType(String attrType) { + this.attrType = attrType; + } + + @ApiField("attr_type") + private String attrType; + + public String getAttrKey() { + return this.attrKey; + } + public void setAttrKey(String attrKey) { + this.attrKey = attrKey; + } + + public String getAttrValue() { + return this.attrValue; + } + public void setAttrValue(String attrValue) { + this.attrValue = attrValue; + } +} diff --git a/src/main/java/com/sczx/pay/alipay/vo/ImageDirectoryRequest.java b/src/main/java/com/sczx/pay/alipay/vo/ImageDirectoryRequest.java new file mode 100644 index 0000000..414a608 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/vo/ImageDirectoryRequest.java @@ -0,0 +1,83 @@ +/** + * Alipay.com Inc. + * Copyright (c) 2004-2024 All Rights Reserved. + */ +package com.sczx.pay.alipay.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.io.Serializable; + +/** + * @author jishupei.jsp + * @version : ImageDirectoryRequest, v0.1 2024年05月21日 11:38 上午 jishupei.jsp Exp $ + */ +@ApiModel(description = "图片空间目录创建模型") +public class ImageDirectoryRequest implements Serializable { + + private static final long serialVersionUID = -6703393134790409650L; + + @ApiModelProperty(value = "目录名,创建时必填") + private String imageDirectoryName; + + @ApiModelProperty(value = "归属父目录ID,创建时必填", example = "0") + private String parentDirectoryId; + + @ApiModelProperty(value = "目录ID,更新时必填") + private String imageDirectoryId; + + /** + * Getter method for property imageDirectoryName. + * + * @return property value of imageDirectoryName + */ + public String getImageDirectoryName() { + return imageDirectoryName; + } + + /** + * Setter method for property imageDirectoryName. + * + * @param imageDirectoryName value to be assigned to property imageDirectoryName + */ + public void setImageDirectoryName(String imageDirectoryName) { + this.imageDirectoryName = imageDirectoryName; + } + + /** + * Getter method for property parentDirectoryId. + * + * @return property value of parentDirectoryId + */ + public String getParentDirectoryId() { + return parentDirectoryId; + } + + /** + * Setter method for property parentDirectoryId. + * + * @param parentDirectoryId value to be assigned to property parentDirectoryId + */ + public void setParentDirectoryId(String parentDirectoryId) { + this.parentDirectoryId = parentDirectoryId; + } + + /** + * Getter method for property imageDirectoryId. + * + * @return property value of imageDirectoryId + */ + public String getImageDirectoryId() { + return imageDirectoryId; + } + + /** + * Setter method for property imageDirectoryId. + * + * @param imageDirectoryId value to be assigned to property imageDirectoryId + */ + public void setImageDirectoryId(String imageDirectoryId) { + this.imageDirectoryId = imageDirectoryId; + } +} diff --git a/src/main/java/com/sczx/pay/alipay/vo/ItemCreateRequest.java b/src/main/java/com/sczx/pay/alipay/vo/ItemCreateRequest.java new file mode 100644 index 0000000..fbf1773 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/vo/ItemCreateRequest.java @@ -0,0 +1,164 @@ +/** + * Alipay.com Inc. + * Copyright (c) 2004-2024 All Rights Reserved. + */ +package com.sczx.pay.alipay.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.io.Serializable; +import java.util.List; + +/** + * @author jishupei.jsp + * @version : ItemCreateRequest, v0.1 2024年05月11日 3:31 下午 jishupei.jsp Exp $ + */ +@ApiModel(description = "普通商品创建模型") +public class ItemCreateRequest implements Serializable { + private static final long serialVersionUID = 89765465465L; + + /** + * 类目ID + */ + @ApiModelProperty(value = "类目ID", example = "C001375478", required = true) + private String categoryId; + + /** + * 商品名称 + */ + @ApiModelProperty(value = "商品名称", example = "测试xxx", required = true) + private String productName; + + /** + * 商家侧商品ID + */ + @ApiModelProperty(value = "商家侧商品ID", example = "10001", required = true) + private String outItemId; + + /** + * 商家名称 + */ + @ApiModelProperty(value = "商家名称") + private String merchantName = "测试商家"; + + /** + * 履约信息ID + */ + @ApiModelProperty(value = "履约信息ID,从履约信息同步接口中返回") + private String deliveryId; + + /** + * 门店ID列表 + */ + @ApiModelProperty(value = "履约信息ID关联的门店ID列表") + private List shopIds; + + /** + * Getter method for property categoryId. + * + * @return property value of categoryId + */ + public String getCategoryId() { + return categoryId; + } + + /** + * Setter method for property categoryId. + * + * @param categoryId value to be assigned to property categoryId + */ + public void setCategoryId(String categoryId) { + this.categoryId = categoryId; + } + + /** + * Getter method for property productName. + * + * @return property value of productName + */ + public String getProductName() { + return productName; + } + + /** + * Setter method for property productName. + * + * @param productName value to be assigned to property productName + */ + public void setProductName(String productName) { + this.productName = productName; + } + + /** + * Getter method for property outItemId. + * + * @return property value of outItemId + */ + public String getOutItemId() { + return outItemId; + } + + /** + * Setter method for property outItemId. + * + * @param outItemId value to be assigned to property outItemId + */ + public void setOutItemId(String outItemId) { + this.outItemId = outItemId; + } + + /** + * Getter method for property merchantName. + * + * @return property value of merchantName + */ + public String getMerchantName() { + return merchantName; + } + + /** + * Setter method for property merchantName. + * + * @param merchantName value to be assigned to property merchantName + */ + public void setMerchantName(String merchantName) { + this.merchantName = merchantName; + } + + /** + * Getter method for property deliveryId. + * + * @return property value of deliveryId + */ + public String getDeliveryId() { + return deliveryId; + } + + /** + * Setter method for property deliveryId. + * + * @param deliveryId value to be assigned to property deliveryId + */ + public void setDeliveryId(String deliveryId) { + this.deliveryId = deliveryId; + } + + /** + * Getter method for property shopIds. + * + * @return property value of shopIds + */ + public List getShopIds() { + return shopIds; + } + + /** + * Setter method for property shopIds. + * + * @param shopIds value to be assigned to property shopIds + */ + public void setShopIds(List shopIds) { + this.shopIds = shopIds; + } +} diff --git a/src/main/java/com/sczx/pay/alipay/vo/OpenResponse.java b/src/main/java/com/sczx/pay/alipay/vo/OpenResponse.java new file mode 100644 index 0000000..161df23 --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/vo/OpenResponse.java @@ -0,0 +1,136 @@ +/** + * Alipay.com Inc. + * Copyright (c) 2004-2024 All Rights Reserved. + */ +package com.sczx.pay.alipay.vo; + +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayResponse; +import org.apache.commons.lang3.StringUtils; + +import java.io.Serializable; + +/** + * @author jishupei.jsp + * @version : OpenResponse, v0.1 2024年03月26日 7:59 下午 jishupei.jsp Exp $ + */ +public class OpenResponse implements Serializable { + private static final long serialVersionUID = 8943991234L; + + private String code; + + private String msg; + + private String subCode; + + private String subMsg; + + private E data; + + public OpenResponse() { + } + + public OpenResponse(AlipayResponse response) { + this.code = response.getCode(); + this.msg = response.getMsg(); + this.subCode = response.getSubCode(); + this.subMsg = response.getSubMsg(); + } + + public OpenResponse(AlipayApiException e) { + this.code = "ALIPAY_SDK_ERROR"; + this.msg = "支付宝SDK调用异常"; + this.subCode = e.getErrCode(); + this.subMsg = StringUtils.defaultIfBlank(e.getErrMsg(), e.getMessage()); + } + + /** + * Getter method for property code. + * + * @return property value of code + */ + public String getCode() { + return code; + } + + /** + * Setter method for property code. + * + * @param code value to be assigned to property code + */ + public void setCode(String code) { + this.code = code; + } + + /** + * Getter method for property msg. + * + * @return property value of msg + */ + public String getMsg() { + return msg; + } + + /** + * Setter method for property msg. + * + * @param msg value to be assigned to property msg + */ + public void setMsg(String msg) { + this.msg = msg; + } + + /** + * Getter method for property subCode. + * + * @return property value of subCode + */ + public String getSubCode() { + return subCode; + } + + /** + * Setter method for property subCode. + * + * @param subCode value to be assigned to property subCode + */ + public void setSubCode(String subCode) { + this.subCode = subCode; + } + + /** + * Getter method for property subMsg. + * + * @return property value of subMsg + */ + public String getSubMsg() { + return subMsg; + } + + /** + * Setter method for property subMsg. + * + * @param subMsg value to be assigned to property subMsg + */ + public void setSubMsg(String subMsg) { + this.subMsg = subMsg; + } + + /** + * Getter method for property data. + * + * @return property value of data + */ + public E getData() { + return data; + } + + /** + * Setter method for property data. + * + * @param data value to be assigned to property data + */ + public void setData(E data) { + this.data = data; + } +} diff --git a/src/main/java/com/sczx/pay/alipay/vo/RentOrderCreateRequest.java b/src/main/java/com/sczx/pay/alipay/vo/RentOrderCreateRequest.java new file mode 100644 index 0000000..0277e7b --- /dev/null +++ b/src/main/java/com/sczx/pay/alipay/vo/RentOrderCreateRequest.java @@ -0,0 +1,15 @@ +package com.sczx.pay.alipay.vo; + +import lombok.Data; + +@Data +public class RentOrderCreateRequest { + + private String outOrderid; + private String outItemId; + private Long storeId; + private String rentType; + private String openId; + private String sourceId; + +} diff --git a/src/main/java/com/sczx/pay/aspect/FacadeAspect.java b/src/main/java/com/sczx/pay/aspect/FacadeAspect.java new file mode 100644 index 0000000..61e7d5b --- /dev/null +++ b/src/main/java/com/sczx/pay/aspect/FacadeAspect.java @@ -0,0 +1,54 @@ +package com.sczx.pay.aspect; + +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.stereotype.Component; + +/** + * @Author Huang Kai + * @Date 2023/1/18 17:01 + * @Version 1.0 + */ +@Aspect +@Component +@Slf4j +public class FacadeAspect { + + + + /** 以 controller 包下定义的所有请求为切入点 */ + @Pointcut("execution(public * com..facade.*.*(..))") + public void facadeLog() {} + + /** + * 在切点之前织入 + * @param joinPoint + */ + @Before("facadeLog()") + public void doBefore(JoinPoint joinPoint) { + // 打印请求相关参数 + log.info("******** remote Start *********"); + // 打印 Http method + //log.info("HTTP Method : {}", request.getMethod()); + // 打印调用 controller 的全路径以及执行方法 + log.info("Class Method:{}.{}, facadeAspect_Request:{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),JSON.toJSONString(joinPoint.getArgs())); + } + + @Around("facadeLog()") + public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { + long startTime = System.currentTimeMillis(); + Object result = proceedingJoinPoint.proceed(); + // 打印出参 + log.info("facadeAspect_Response:{}", JSON.toJSONString(result)); + // 执行耗时 + log.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime); + log.info("**************remote End ****************"); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/sczx/pay/aspect/WebLogAspect.java b/src/main/java/com/sczx/pay/aspect/WebLogAspect.java new file mode 100644 index 0000000..25e85b2 --- /dev/null +++ b/src/main/java/com/sczx/pay/aspect/WebLogAspect.java @@ -0,0 +1,127 @@ +package com.sczx.pay.aspect; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.*; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; + +/** + * @Author Huang Kai + * @Date 2023/1/18 17:01 + * @Version 1.0 + */ +@Aspect +@Component +@Slf4j +public class WebLogAspect { + + + + /** 以 controller 包下定义的所有请求为切入点 */ + @Pointcut("execution(public * com.sczx.*.controller.*.*(..))") + public void webLog() {} + + /** + * 在切点之前织入 + * @param joinPoint + */ + @Before("webLog()") + public void doBefore(JoinPoint joinPoint) { + // 检查是否是支付宝回调控制器 + String className = joinPoint.getTarget().getClass().getSimpleName(); + if ("AliPayNotifyController".equals(className)) { + // 对于支付宝回调,简化日志处理 + log.info("========================= Start ========================="); + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + HttpServletRequest request = attributes.getRequest(); + log.info("URL:{}", request.getRequestURL().toString()); + } + String requestId = UUID.randomUUID().toString(); + // 使用 requestId 的一部分作为日志标识 + MDC.put("requestId", requestId.substring(Math.max(0, requestId.length() - 12))); + return; + } + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = null; + String requestId = null; + + if (attributes != null) { + request = attributes.getRequest(); + // 从请求头中获取 requestId + requestId = request.getHeader("requestId"); + } + + // 如果请求头中没有 requestId,则生成新的 + if (StringUtils.isBlank(requestId)) { + requestId = UUID.randomUUID().toString(); + } + + // 使用 requestId 的一部分作为日志标识 + MDC.put("requestId", requestId.substring(Math.max(0, requestId.length() - 12))); + // 打印请求相关参数 + log.info("========================= Start ========================="); + // 打印请求入参 + try{ + Object[] args = joinPoint.getArgs(); + List filteredArgs = new ArrayList<>(); + for (Object arg : args) { + //如果是文件类,则只打印文件名称和大小 + if (arg instanceof MultipartFile) { + MultipartFile file = (MultipartFile) arg; + JSONObject fileJson = new JSONObject(); + fileJson.put("filename", file.getOriginalFilename()); + fileJson.put("size", file.getSize()); + filteredArgs.add(fileJson); + } else { + filteredArgs.add(arg); + } + } + Map paramMap = new HashMap<>(); + paramMap.put("args", filteredArgs); + + if (request != null) { + String token = request.getHeader("Authorization"); + if(StringUtils.isNotBlank(token)){ + paramMap.put("Authorization", token); + } + log.info("URL:{} , {}", request.getRequestURL().toString(), JSON.toJSONString(paramMap)); + } + } catch (Exception e){ + log.error("打印WebLogAspect_Request失败,args无法被序列化", e); + } + + } + + /** + * 处理完请求后执行 + * @param joinPoint + * @param result + */ + @AfterReturning(pointcut = "webLog()", returning = "result") + public void doAfterReturning(JoinPoint joinPoint, Object result) { + // 打印出参 + try { + log.info("WebLogAspect_Response:{}", JSON.toJSON(result)); + }catch (Exception e){ + log.error("打印WebLogAspect_Response失败,result无法被序列化", e); + } + log.info("========================= End ========================="); + MDC.clear(); + } + + @AfterThrowing(throwing="ex",pointcut="webLog()") + public void doRecoveryActions(Throwable ex){ + MDC.clear(); + } +} \ No newline at end of file diff --git a/src/main/java/com/sczx/pay/common/IApiCode.java b/src/main/java/com/sczx/pay/common/IApiCode.java new file mode 100644 index 0000000..4738be0 --- /dev/null +++ b/src/main/java/com/sczx/pay/common/IApiCode.java @@ -0,0 +1,12 @@ +package com.sczx.pay.common; + +/** + * @Author: 张黎 + * @Date: 2025/07/09/20:20 + * @Description: + */ +public interface IApiCode { + String getCode(); + + String getMsg(); +} diff --git a/src/main/java/com/sczx/pay/common/Result.java b/src/main/java/com/sczx/pay/common/Result.java new file mode 100644 index 0000000..af6cba3 --- /dev/null +++ b/src/main/java/com/sczx/pay/common/Result.java @@ -0,0 +1,121 @@ +package com.sczx.pay.common; + + +import com.sczx.pay.common.enums.ApiErrorCode; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Builder; +import lombok.Data; +import org.apache.commons.lang.StringUtils; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * @Author: 张黎 + * @Date: 2025/07/09/20:25 + * @Description: + */ +@ApiModel(value = "公共返回结果") +@Builder +@Data +public class Result implements Serializable { + private static final long serialVersionUID = 1331134667810352183L; + @ApiModelProperty(value = "成功标识") + private boolean success; + @ApiModelProperty(value = "返回编码") + private String code; + @ApiModelProperty(value = "返回信息说明") + private String msg; + private String issue; + @ApiModelProperty(value = "返回数据") + private T data; + + public Result(boolean success, String code, String msg, String issue, T data) { + this.success = success; + this.code = code; + this.msg = msg; + this.issue = issue; + this.data = data; + } + + public static Result result(IApiCode apiCode) { + return result(apiCode, (Object)null); + } + + public static Result result(IApiCode apiCode, Object data) { + return result(apiCode, apiCode.getMsg(), data); + } + + public static Result result(IApiCode apiCode, String issue, Object data) { + return result((IApiCode)apiCode, (String)null, issue, data); + } + + public static Result result(IApiCode apiCode, String msg, String issue, Object data) { + String message = apiCode.getMsg(); + if (StringUtils.isNotBlank(msg)) { + message = msg; + } + + return result(apiCode.getCode(), message, issue, data); + } + + public static Result result(String code, String msg, String issue, Object data) { + return builder().code(code).success("0".equals(code)).msg(msg).issue(issue).data(data).build(); + } + + public static Result ok() { + return ok((Object)null); + } + + public static Result ok(Object data) { + return result(ApiErrorCode.SUCCESS, data); + } + + public static Result ok(String key, Object value) { + Map map = new HashMap(1); + map.put(key, value); + return ok(map); + } + + public static Result fail(String msg) { + return result((IApiCode)ApiErrorCode.FAIL, msg, msg, (Object)null); + } + + public static Result fail(IApiCode apiCode) { + return result(apiCode, (Object)null); + } + + public static Result fail(IApiCode apiCode, String issue) { + return result(apiCode, issue, (Object)null); + } + + public static Result fail(String code, String msg) { + return result((String)code, msg, msg, (Object)null); + } + + public static Result fail(String code, String msg, String issue) { + return result((String)code, msg, issue, (Object)null); + } + + public static Result fail(String code, String msg, String issue, Object data) { + return result(code, msg, issue, data); + } + + public static Result fail(IApiCode apiCode, String issue, Object data) { + if (ApiErrorCode.SUCCESS == apiCode) { + throw new RuntimeException("失败结果状态码不能为" + ApiErrorCode.SUCCESS.getCode()); + } else { + return result(apiCode, issue, data); + } + } + + public static Result fail(String key, Object value) { + Map map = new HashMap(); + map.put(key, value); + return result(ApiErrorCode.FAIL, map); + } + public Result() { + } +} diff --git a/src/main/java/com/sczx/pay/common/constant/SystemConstants.java b/src/main/java/com/sczx/pay/common/constant/SystemConstants.java new file mode 100644 index 0000000..b1df4dd --- /dev/null +++ b/src/main/java/com/sczx/pay/common/constant/SystemConstants.java @@ -0,0 +1,11 @@ +package com.sczx.pay.common.constant; + + +public interface SystemConstants { + + /*** + * feign客户端所在包路径 + */ + String FEIGN_CLIENT_BASE_PACKAGE = "com.sczx.pay.thirdpart.facade"; + +} diff --git a/src/main/java/com/sczx/pay/common/enums/ApiErrorCode.java b/src/main/java/com/sczx/pay/common/enums/ApiErrorCode.java new file mode 100644 index 0000000..4bfd46b --- /dev/null +++ b/src/main/java/com/sczx/pay/common/enums/ApiErrorCode.java @@ -0,0 +1,55 @@ +package com.sczx.pay.common.enums; + + +import com.sczx.pay.common.IApiCode; + +/** + * @Author: 张黎 + * @Date: 2025/07/09/20:22 + * @Description: + */ +public enum ApiErrorCode implements IApiCode { + SUCCESS("0", "操作成功"), + ARG_ERROR("401000", "参数错误"), + NOT_PERMISSION("401001", "没有权限"), + HTTP_MEDIA_TYPE_NOT_SUPPORTED_ERROR("401002", "media类型出错"), + HTTP_METHOD_NOT_ALLOW_ERROR("401003", "http请求method错误"), + BODY_NOT_MATCH("401004", "请求的数据格式不符!"), + NOT_FOUND("404000", "你请求的路径不存在"), + UNAUTHORIZED("404001", "非法访问"), + FAIL("500000", "操作失败"), + INNER_ERROR("500001", "服务器内部异常"); + + private final String code; + private final String msg; + + private ApiErrorCode(String code, String msg) { + this.code = code; + this.msg = msg; + } + + public static ApiErrorCode getApiCode(String code) { + ApiErrorCode[] ecs = values(); + ApiErrorCode[] var2 = ecs; + int var3 = ecs.length; + + for(int var4 = 0; var4 < var3; ++var4) { + ApiErrorCode ec = var2[var4]; + if (ec.getCode().equals(code)) { + return ec; + } + } + + return SUCCESS; + } + + @Override + public String getCode() { + return this.code; + } + + @Override + public String getMsg() { + return this.msg; + } +} diff --git a/src/main/java/com/sczx/pay/config/AlipayConf.java b/src/main/java/com/sczx/pay/config/AlipayConf.java new file mode 100644 index 0000000..30f2eed --- /dev/null +++ b/src/main/java/com/sczx/pay/config/AlipayConf.java @@ -0,0 +1,114 @@ +package com.sczx.pay.config; + + +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayClient; +import com.sczx.pay.entity.CompanyAlipayConfig; +import com.sczx.pay.mapper.CompanyAlipayConfigMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +@Slf4j +@Component +public class AlipayConf { + +// @Value("${ali.pay.app-id}") +// private String appId; +// +// @Value("${ali.pay.gateway-url}") +// private String gatewayUrl; +// +// @Value("${ali.pay.privateKey}") +// private String privateKey; +// +//// @Value("${alipay.miniapp.publicKey}") +//// private String publicKey; +// +// @Value("${ali.pay.appCertPath}") +// private String appCertPath; +// +// @Value("${ali.pay.alipayCertPath}") +// private String alipayCertPath; +// +// @Value("${ali.pay.alipayRootCertPath}") +// private String alipayRootCertPath; + + + @Autowired + private CompanyAlipayConfigMapper companyAlipayConfigMapper; + + private static final Map apiClientMap = new HashMap<>(); + + private static AlipayClient alipayClient; + +// public AlipayClient alipayClient() throws AlipayApiException { +// +// if(Objects.isNull(alipayClient)){ +// AlipayConfig alipayConfig = new AlipayConfig(); +// //设置网关地址 +// alipayConfig.setServerUrl(gatewayUrl); +// //设置应用ID +// alipayConfig.setAppId(appId); +// //设置应用私钥 +// alipayConfig.setPrivateKey(privateKey); +// //设置请求格式,固定值json +// alipayConfig.setFormat("JSON"); +// //设置字符集 +// alipayConfig.setCharset("UTF-8"); +// //设置签名类型 +// alipayConfig.setSignType("RSA2"); +// //设置应用公钥证书路径 +// alipayConfig.setAppCertPath(appCertPath); +// //设置支付宝公钥证书路径 +// alipayConfig.setAlipayPublicCertPath(alipayCertPath); +// //设置支付宝根证书路径 +// alipayConfig.setRootCertPath(alipayRootCertPath); +// +// log.info("支付宝配置信息:{}",alipayConfig); +// +// alipayClient = new DefaultAlipayClient(alipayConfig); +// } +// return alipayClient; +// } + + public String getCompanyAppid(Long companyId) throws AlipayApiException { + CompanyAlipayConfig companyAlipayConfig = companyAlipayConfigMapper.getConfigByCompanyId(companyId); + if(Objects.nonNull(companyAlipayConfig)){ + return companyAlipayConfig.getMchId(); + }else { + throw new AlipayApiException("未找到公司对应的支付宝支付配置"); + } + } + + + /** + * 获取支付宝客户端 + * @param companyId + * @param privateKey + * @param alipayPublicKey + * @return + * @throws AlipayApiException +// */ +// public AlipayClient alipayClient(Long companyId, String privateKey, String alipayPublicKey) throws AlipayApiException { +// AlipayClient alipayClient = apiClientMap.get(companyId.toString()); +// if(Objects.isNull(alipayClient)){ +// AlipayConfig alipayConfig = new AlipayConfig(); +// alipayConfig.setServerUrl(gatewayUrl); +// alipayConfig.setAppId(appId); +// alipayConfig.setPrivateKey(privateKey); +// alipayConfig.setFormat("json"); +// alipayConfig.setAlipayPublicKey(alipayPublicKey); +// alipayConfig.setCharset("UTF-8"); +// alipayConfig.setSignType("RSA2"); +// alipayClient = new DefaultAlipayClient(alipayConfig); +// apiClientMap.put(companyId.toString(),alipayClient); +// } +// +// return alipayClient; +// } +} diff --git a/src/main/java/com/sczx/pay/config/DynamicWXPayConfig.java b/src/main/java/com/sczx/pay/config/DynamicWXPayConfig.java new file mode 100644 index 0000000..4ac799f --- /dev/null +++ b/src/main/java/com/sczx/pay/config/DynamicWXPayConfig.java @@ -0,0 +1,83 @@ +package com.sczx.pay.config; + +import com.sczx.pay.sdk.WXPayConfig; +import org.springframework.stereotype.Component; + +import java.io.InputStream; + +/** + * 动态微信支付配置类 + */ +@Component +public class DynamicWXPayConfig extends WXPayConfig { + + private String appId; + private String mchId; + private String key; + private String notifyUrl; + + // 构造函数 + public DynamicWXPayConfig() {} + + public DynamicWXPayConfig(String appId, String mchId, String key, String notifyUrl) { + this.appId = appId; + this.mchId = mchId; + this.key = key; + this.notifyUrl = notifyUrl; + } + + @Override + public String getAppID() { + return appId; + } + + @Override + public String getMchID() { + return mchId; + } + + @Override + public String getKey() { + return key; + } + + @Override + public InputStream getCertStream() { + return null; + } + + @Override + public int getHttpConnectTimeoutMs() { + return 8000; + } + + @Override + public int getHttpReadTimeoutMs() { + return 10000; + } + + // Getter和Setter方法 + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public void setMchId(String mchId) { + this.mchId = mchId; + } + + public void setKey(String key) { + this.key = key; + } + + public String getNotifyUrl() { + return notifyUrl; + } + + public void setNotifyUrl(String notifyUrl) { + this.notifyUrl = notifyUrl; + } +} diff --git a/src/main/java/com/sczx/pay/config/SwaggerConfig.java b/src/main/java/com/sczx/pay/config/SwaggerConfig.java new file mode 100644 index 0000000..21050f4 --- /dev/null +++ b/src/main/java/com/sczx/pay/config/SwaggerConfig.java @@ -0,0 +1,47 @@ +package com.sczx.pay.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.ParameterBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.schema.ModelRef; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import java.util.Arrays; + +@Configuration +@EnableSwagger2 +public class SwaggerConfig { + @Bean + public Docket createRestApi() { + return new Docket(DocumentationType.SWAGGER_2) + .apiInfo(apiInfo()) + .select() + .apis(RequestHandlerSelectors.basePackage("com.sczx.pay.controller")) // 修改为你的 controller 包路径 + .paths(PathSelectors.any()) + .build() + .globalOperationParameters(Arrays.asList( + new ParameterBuilder() + .name("Authorization") + .description("Bearer Token") + .modelRef(new ModelRef("string")) + .parameterType("header") + .required(true) + .build() + )); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("支付服务接口文档") + .description("sczx_singlepay服务接口文档") + .version("1.0") + .build(); + } +} diff --git a/src/main/java/com/sczx/pay/config/WXPayConfigImpl.java b/src/main/java/com/sczx/pay/config/WXPayConfigImpl.java new file mode 100644 index 0000000..6818094 --- /dev/null +++ b/src/main/java/com/sczx/pay/config/WXPayConfigImpl.java @@ -0,0 +1,85 @@ +package com.sczx.pay.config; + +import com.sczx.pay.sdk.WXPayConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import java.io.InputStream; + +/** + * 微信支付配置实现类 + */ +@Configuration +public class WXPayConfigImpl extends WXPayConfig { + + @Value("${wechat.pay.app-id}") + private String appId; + + @Value("${wechat.pay.mch-id}") + private String mchId; + + @Value("${wechat.pay.key}") + private String apiKey; + + @Value("${wechat.pay.notify-url}") + private String notifyUrl; + + @Override + public String getAppID() { + return appId; + } + + @Override + public String getMchID() { + return mchId; + } + + @Override + public String getKey() { + return apiKey; + } + + @Override + public InputStream getCertStream() { + return null; + } + + @Override + public int getHttpConnectTimeoutMs() { + return 8000; + } + + @Override + public int getHttpReadTimeoutMs() { + return 10000; + } + + // getter和setter方法 + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getMchId() { + return mchId; + } + + public void setMchId(String mchId) { + this.mchId = mchId; + } + + public void setKey(String key) { + this.apiKey = key; + } + + public String getNotifyUrl() { + return notifyUrl; + } + + public void setNotifyUrl(String notifyUrl) { + this.notifyUrl = notifyUrl; + } +} diff --git a/src/main/java/com/sczx/pay/controller/AliPayNotifyController.java b/src/main/java/com/sczx/pay/controller/AliPayNotifyController.java new file mode 100644 index 0000000..0e54192 --- /dev/null +++ b/src/main/java/com/sczx/pay/controller/AliPayNotifyController.java @@ -0,0 +1,144 @@ +package com.sczx.pay.controller; + +import com.sczx.pay.service.AlipayService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +@Api(value = "支付宝通知接口", tags = "支付宝支付接口") +@Slf4j +@RestController +@RequestMapping("/alipay") +public class AliPayNotifyController { + + @Autowired + private AlipayService alipayService; + + + /** + * 支付宝支付结果通知 + */ + @ApiOperation(value = "支付宝支付结果通知") + @PostMapping("/pay/notify") + public String alipayNotify(HttpServletRequest request) { + try { + // 读取支付宝回调数据 + Map params = new HashMap<>(); + Map requestParams = request.getParameterMap(); + for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) { + String name = iter.next(); + String[] values = requestParams.get(name); + String valueStr = ""; + for (int i = 0; i < values.length; i++) { + valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; + } + params.put(name, valueStr); + } + + String outTradeNo = params.get("out_trade_no"); + String companyIdStr = params.get("passback_params"); // 通过回传参数获取公司ID + Long companyId = companyIdStr != null ? Long.parseLong(companyIdStr) : null; + + log.info("收到支付宝支付通知,数据: {}, 商户订单号: {}", params, outTradeNo); + + // 验证签名 +// if (!alipayService.verifyNotifySign(companyId, params)) { +// log.warn("支付宝支付通知签名验证失败,公司ID: {}", companyId); +// return buildResponse("failure"); +// } + + String tradeStatus = params.get("trade_status"); + if (!"TRADE_SUCCESS".equals(tradeStatus) && !"TRADE_FINISHED".equals(tradeStatus)) { + log.warn("支付宝支付通知状态失败,商户订单号: {}: {}", outTradeNo, tradeStatus); + return buildResponse("success"); // 状态不是成功时也返回success,避免重复通知 + } + + // 处理支付成功的业务逻辑 + String tradeNo = params.get("trade_no"); + String totalAmount = params.get("total_amount"); + + // 更新数据库中的订单状态 + boolean success = alipayService.processPaySuccessNotify(companyId, params); + if (success) { + log.info("支付宝支付成功,公司ID: {}, 订单号: {}, 支付宝交易号: {}, 金额: {}", + companyId, outTradeNo, tradeNo, totalAmount); + return buildResponse("success"); + } else { + log.error("更新支付宝支付状态失败,公司ID: {}, 订单号: {}", companyId, outTradeNo); + return buildResponse("failure"); + } + + } catch (Exception e) { + log.error("处理支付宝支付通知异常", e); + return buildResponse("failure"); + } + } + + /** + * 支付宝退款结果通知 + */ + @ApiOperation(value = "支付宝退款结果通知") + @PostMapping("/refund/notify") + public String alipayRefundNotify(HttpServletRequest request) { + try { + // 读取支付宝退款回调数据 + Map params = new HashMap<>(); + Map requestParams = request.getParameterMap(); + for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) { + String name = iter.next(); + String[] values = requestParams.get(name); + String valueStr = ""; + for (int i = 0; i < values.length; i++) { + valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; + } + params.put(name, valueStr); + } + + log.info("收到支付宝退款通知, 数据: {}", params); + + // 验证签名 + // 注意:支付宝退款通知的签名验证方式可能与支付通知不同 + // 这里简化处理,实际应根据支付宝文档实现 + + String outTradeNo = params.get("out_trade_no"); + String refundStatus = params.get("refund_status"); + +// if (!"REFUND_SUCCESS".equals(refundStatus)) { +// log.warn("支付宝退款通知状态失败: {}", refundStatus); +// return buildResponse("success"); +// } + + // 处理退款通知的业务逻辑 + String outRequestNo = params.get("out_request_no"); + String refundAmount = params.get("refund_amount"); + + // 更新数据库中的退款状态 + boolean success = alipayService.processRefundNotify(params); + if (success) { + log.info("支付宝退款处理完成,订单号: {}, 退款请求号: {}, 退款金额: {}, 状态: {}", + outTradeNo, outRequestNo, refundAmount, refundStatus); + return buildResponse("success"); + } else { + log.error("更新支付宝退款状态失败,订单号: {}, 退款请求号: {}", outTradeNo, outRequestNo); + return buildResponse("failure"); + } + + } catch (Exception e) { + log.error("处理支付宝退款通知异常", e); + return buildResponse("failure"); + } + } + + private String buildResponse(String result) { + return result; + } +} diff --git a/src/main/java/com/sczx/pay/controller/AliPayOrderCloseController.java b/src/main/java/com/sczx/pay/controller/AliPayOrderCloseController.java new file mode 100644 index 0000000..32ce165 --- /dev/null +++ b/src/main/java/com/sczx/pay/controller/AliPayOrderCloseController.java @@ -0,0 +1,38 @@ +package com.sczx.pay.controller; + +import com.alipay.api.domain.AlipayCommerceRentOrderCloseModel; +import com.alipay.api.response.AlipayCommerceRentOrderCloseResponse; +import com.sczx.pay.service.AliPayOrderCloseService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +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; + + + +@Api(value = "支付宝租赁订单关闭接口", tags = "支付宝租赁订单关闭接口") +@Slf4j +@RestController +@RequestMapping("/alipay/orderClose") +public class AliPayOrderCloseController { + + @Autowired + private AliPayOrderCloseService aliPayOrderCloseService; + + /** + * 支付宝订单关闭接口 + */ + @ApiOperation(value = "支付宝订单关闭接口") + @PostMapping("/orderClose") + public AlipayCommerceRentOrderCloseResponse aliPayOrderClose(@RequestBody AlipayCommerceRentOrderCloseModel model) { + log.info("支付宝订单关闭接口请求: {}", model); + return aliPayOrderCloseService.aliPayOrderClose(model); + } + + + +} diff --git a/src/main/java/com/sczx/pay/controller/AliPayOrderFulfillmentController.java b/src/main/java/com/sczx/pay/controller/AliPayOrderFulfillmentController.java new file mode 100644 index 0000000..06797a0 --- /dev/null +++ b/src/main/java/com/sczx/pay/controller/AliPayOrderFulfillmentController.java @@ -0,0 +1,49 @@ +package com.sczx.pay.controller; + + +import com.alipay.api.domain.AlipayCommerceRentOrderFulfillmentFinishModel; +import com.alipay.api.response.AlipayCommerceRentOrderFulfillmentApproveResponse; +import com.alipay.api.domain.AlipayCommerceRentOrderFulfillmentApproveModel; +import com.alipay.api.response.AlipayCommerceRentOrderFulfillmentFinishResponse; +import com.sczx.pay.service.AliPayOrderFulfillmentService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +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; + + + +@Api(value = "支付宝订单履约接口", tags = "支付宝租赁履约接口") +@Slf4j +@RestController +@RequestMapping("/alipay/orderFulfillment") +public class AliPayOrderFulfillmentController { + + @Autowired + private AliPayOrderFulfillmentService aliPayOrderFulfillmentService; + + /** + * 支付宝订单履约审核接口 + */ + @ApiOperation(value = "支付宝订单履约审核接口") + @PostMapping("/approve") + public AlipayCommerceRentOrderFulfillmentApproveResponse aliPayOrderFulfillmentApprove(@RequestBody AlipayCommerceRentOrderFulfillmentApproveModel model) { + log.info("支付宝订单履约审核接口请求: {}", model); + return aliPayOrderFulfillmentService.aliPayOrderFulfillmentApprove(model); + } + + + /** + * 支付宝订单履约完成接口 + */ + @ApiOperation(value = "支付宝订单履约完成接口") + @PostMapping("/finish") + public AlipayCommerceRentOrderFulfillmentFinishResponse aliPayOrderFulfillmentFinish(@RequestBody AlipayCommerceRentOrderFulfillmentFinishModel model) { + log.info("支付宝订单履约审核接口请求: {}", model); + return aliPayOrderFulfillmentService.aliPayOrderFulfillmentFinish(model); + } +} diff --git a/src/main/java/com/sczx/pay/controller/AliPaymentController.java b/src/main/java/com/sczx/pay/controller/AliPaymentController.java new file mode 100644 index 0000000..fddcda1 --- /dev/null +++ b/src/main/java/com/sczx/pay/controller/AliPaymentController.java @@ -0,0 +1,70 @@ +package com.sczx.pay.controller; + +import com.sczx.pay.dto.*; +import com.sczx.pay.service.AlipayService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@Api(value = "支付宝支付接口", tags = "支付宝支付接口") +@Slf4j +@RestController +@RequestMapping("/api/alipay") +public class AliPaymentController { + + @Autowired + private AlipayService alipayService; + + /** + * 支付宝统一下单接口 + */ + @ApiOperation(value = "支付宝统一下单接口") + @PostMapping("/unifiedOrder") + public AlipayCreateResponse aliPayUnifiedOrder(@RequestBody AlipayCreateRequest request) { + log.info("收到支付宝支付请求: {}", request); + return alipayService.unifiedOrder(request); + } + + /** + * 查询订单接口 + */ + @ApiOperation(value = "查询订单接口") + @GetMapping("/query/{outTradeNo}") + public AlipayQueryResponse alipayOrderQuery(@PathVariable String outTradeNo) { + log.info("收到支付宝订单查询请求, 订单号: {}", outTradeNo); + return alipayService.orderQuery(outTradeNo); + } + + /** + * 关闭订单接口 + */ + @ApiOperation(value = "关闭订单接口") + @PostMapping("/close") + public AlipayResponse alipayCloseOrder(@RequestBody AlipayCloseRequest request) { + log.info("收到支付宝关闭订单请求: {}", request); + return alipayService.closeOrder(request); + } + + /** + * 申请退款接口 + */ + @ApiOperation(value = "申请退款接口") + @PostMapping("/refund") + public AlipayResponse alipayRefund(@RequestBody AlipayRefundRequest request) { + log.info("收到支付宝退款请求: {}", request); + return alipayService.refund(request); + } + + /** + * 查询退款接口 + */ + @ApiOperation(value = "查询退款接口") + @PostMapping("/refundQuery") + public AlipayResponse alipayRefundQuery(@RequestBody AlipayRefundRequest request) { + log.info("收到支付宝退款查询请求: {}", request); + return alipayService.refundQuery(request); + } + +} diff --git a/src/main/java/com/sczx/pay/controller/AlipayRentController.java b/src/main/java/com/sczx/pay/controller/AlipayRentController.java new file mode 100644 index 0000000..f0f16c3 --- /dev/null +++ b/src/main/java/com/sczx/pay/controller/AlipayRentController.java @@ -0,0 +1,29 @@ +package com.sczx.pay.controller; + +import com.sczx.pay.dto.ali.rent.AlipayRentPayReq; +import com.sczx.pay.dto.ali.rent.AlipayRentPayResponse; +import com.sczx.pay.service.AlipayRentService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +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; + +@Api(value = "支付宝租赁订单支付接口", tags = "支付宝租赁订单支付接口") +@Slf4j +@RestController +@RequestMapping("/alipay/rent") +public class AlipayRentController { + + @Autowired + private AlipayRentService alipayRentService; + + @ApiOperation(value = "创建支付单") + @PostMapping("/pay") + public AlipayRentPayResponse pay(@RequestBody AlipayRentPayReq request) { + return alipayRentService.rentPay(request); + } +} diff --git a/src/main/java/com/sczx/pay/controller/AlipayRentNotifyController.java b/src/main/java/com/sczx/pay/controller/AlipayRentNotifyController.java new file mode 100644 index 0000000..b113bb0 --- /dev/null +++ b/src/main/java/com/sczx/pay/controller/AlipayRentNotifyController.java @@ -0,0 +1,84 @@ +package com.sczx.pay.controller; + +import com.sczx.pay.service.AlipayRentService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +@Api(value = "支付宝租赁订单回调接口", tags = "支付宝租赁订单回调接口") +@Slf4j +@RestController +@RequestMapping("/alipay/rent/notify") +public class AlipayRentNotifyController { + + @Autowired + private AlipayRentService alipayRentService; + + /** + * 支付宝租赁订单支付结果通知 + */ + @ApiOperation(value = "支付宝租赁订单支付结果通知") + @PostMapping("/pay") + public String alipayNotify(HttpServletRequest request) { + try { + // 读取支付宝回调数据 + Map params = new HashMap<>(); + Map requestParams = request.getParameterMap(); + for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) { + String name = iter.next(); + String[] values = requestParams.get(name); + String valueStr = ""; + for (int i = 0; i < values.length; i++) { + valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; + } + params.put(name, valueStr); + } + + String outTradeNo = params.get("out_trade_no"); + String companyIdStr = params.get("passback_params"); // 通过回传参数获取公司ID + Long companyId = companyIdStr != null ? Long.parseLong(companyIdStr) : null; + + log.info("收到支付宝租赁订单支付通知,数据: {}, 商户订单号: {}", params, outTradeNo); + + // 验证签名 +// if (!alipayService.verifyNotifySign(companyId, params)) { +// log.warn("支付宝支付通知签名验证失败,公司ID: {}", companyId); +// return buildResponse("failure"); +// } + + String tradeStatus = params.get("trade_status"); + if (!"TRADE_SUCCESS".equals(tradeStatus) && !"TRADE_FINISHED".equals(tradeStatus)) { + log.warn("支付宝支付通知状态失败,商户订单号: {}: {}", outTradeNo, tradeStatus); + return "success"; // 状态不是成功时也返回success,避免重复通知 + } + + // 处理支付成功的业务逻辑 + String tradeNo = params.get("trade_no"); + String totalAmount = params.get("total_amount"); + + // 更新数据库中的订单状态 + boolean success = alipayRentService.processPaySuccessNotify(params); + if (success) { + log.info("支付宝支付成功,公司ID: {}, 订单号: {}, 支付宝交易号: {}, 金额: {}", + companyId, outTradeNo, tradeNo, totalAmount); + return "success"; + } else { + log.error("更新支付宝支付状态失败,公司ID: {}, 订单号: {}", companyId, outTradeNo); + return "failure"; + } + + } catch (Exception e) { + log.error("处理支付宝支付通知异常", e); + return "failure"; + } + } +} diff --git a/src/main/java/com/sczx/pay/controller/PaymentController.java b/src/main/java/com/sczx/pay/controller/PaymentController.java new file mode 100644 index 0000000..1382774 --- /dev/null +++ b/src/main/java/com/sczx/pay/controller/PaymentController.java @@ -0,0 +1,244 @@ +package com.sczx.pay.controller; + +import com.sczx.pay.dto.PaymentRequest; +import com.sczx.pay.dto.PaymentResponse; +import com.sczx.pay.dto.RefundRequest; +import com.sczx.pay.entity.CompanyWechatConfig; +import com.sczx.pay.mapper.CompanyWechatConfigMapper; +import com.sczx.pay.service.WechatPayService; +import com.sczx.pay.utils.WXPayUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.util.HashMap; +import java.util.Map; + +/** + * 微信支付控制器 + */ +@RestController +@RequestMapping("/api/payment") +public class PaymentController { + + private static final Logger logger = LoggerFactory.getLogger(PaymentController.class); + + @Autowired + private WechatPayService wechatPayService; + + @Autowired + private CompanyWechatConfigMapper companyWechatConfigMapper; + + /** + * 小程序统一下单接口 + */ + @PostMapping("/unifiedOrder") + public PaymentResponse unifiedOrder(@RequestBody PaymentRequest request) { + logger.info("收到支付请求: {}", request); + return wechatPayService.unifiedOrder(request); + } + + /** + * 查询订单接口 + */ + @GetMapping("/query/{companyId}/{outTradeNo}") + public Map orderQuery(@PathVariable Long companyId, @PathVariable String outTradeNo) { + logger.info("收到订单查询请求,公司ID: {}, 订单号: {}", companyId, outTradeNo); + try { + return wechatPayService.orderQuery(companyId, outTradeNo); + } catch (Exception e) { + logger.error("订单查询异常,公司ID: {}, 订单号: {}", companyId, outTradeNo, e); + Map errorResult = new HashMap<>(); + errorResult.put("return_code", "FAIL"); + errorResult.put("return_msg", "查询异常: " + e.getMessage()); + return errorResult; + } + } + + /** + * 关闭订单接口 + */ + @PostMapping("/close/{companyId}/{outTradeNo}") + public Map closeOrder(@PathVariable Long companyId, @PathVariable String outTradeNo) { + logger.info("收到关闭订单请求,公司ID: {}, 订单号: {}", companyId, outTradeNo); + try { + return wechatPayService.closeOrder(companyId, outTradeNo); + } catch (Exception e) { + logger.error("关闭订单异常,公司ID: {}, 订单号: {}", companyId, outTradeNo, e); + Map errorResult = new HashMap<>(); + errorResult.put("return_code", "FAIL"); + errorResult.put("return_msg", "关闭异常: " + e.getMessage()); + return errorResult; + } + } + + /** + * 申请退款接口 + */ + @PostMapping("/refund") + public Map refund(@RequestBody RefundRequest request) { + logger.info("收到退款请求: {}", request); + try { + return wechatPayService.refund(request); + } catch (Exception e) { + logger.error("退款异常,公司ID: {}, 订单号: {}", request.getCompanyId(), request.getOutTradeNo(), e); + Map errorResult = new HashMap<>(); + errorResult.put("return_code", "FAIL"); + errorResult.put("return_msg", "退款异常: " + e.getMessage()); + return errorResult; + } + } + + /** + * 查询退款接口 + */ + @GetMapping("/refundQuery/{companyId}") + public Map refundQuery(@PathVariable Long companyId, @RequestParam String outTradeNo) { + logger.info("收到退款查询请求,公司ID: {}, 订单号: {}", companyId, outTradeNo); + try { + return wechatPayService.refundQuery(companyId, outTradeNo); + } catch (Exception e) { + logger.error("退款查询异常,公司ID: {}, 订单号: {}", companyId, outTradeNo, e); + Map errorResult = new HashMap<>(); + errorResult.put("return_code", "FAIL"); + errorResult.put("return_msg", "退款查询异常: " + e.getMessage()); + return errorResult; + } + } + + /** + * 微信支付结果通知 + */ + @PostMapping("/notify") + public String notify(HttpServletRequest request) { + try { + // 读取微信回调数据 + StringBuilder sb = new StringBuilder(); + BufferedReader reader = request.getReader(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + + String xmlData = sb.toString(); + logger.info("收到微信支付通知,数据: {}", xmlData); + + // 解析XML数据 + Map notifyMap = WXPayUtil.xmlToMap(xmlData); + String companyIdStr = notifyMap.get("attach"); + Long companyId = Long.parseLong(companyIdStr); + + // 验证签名 + if (!wechatPayService.verifyNotifySign(companyId, notifyMap)) { + logger.warn("微信支付通知签名验证失败,公司ID: {}", companyId); + return buildResponse("FAIL", "签名失败"); + } + + String returnCode = notifyMap.get("return_code"); + if (!"SUCCESS".equals(returnCode)) { + logger.warn("微信支付通知返回失败,公司ID: {}: {}", companyId, notifyMap.get("return_msg")); + return buildResponse("FAIL", "返回失败"); + } + + String resultCode = notifyMap.get("result_code"); + if (!"SUCCESS".equals(resultCode)) { + logger.warn("微信支付业务失败,公司ID: {}: {}", companyId, notifyMap.get("err_code_des")); + return buildResponse("FAIL", "业务失败"); + } + + // 处理支付成功的业务逻辑 + String outTradeNo = notifyMap.get("out_trade_no"); + String transactionId = notifyMap.get("transaction_id"); + String totalFee = notifyMap.get("total_fee"); + + // 更新数据库中的订单状态 + boolean success = wechatPayService.processPaySuccessNotify(companyId, notifyMap); + if (success) { + logger.info("支付成功,公司ID: {}, 订单号: {}, 微信交易号: {}, 金额: {}", + companyId, outTradeNo, transactionId, totalFee); + return buildResponse("SUCCESS", "OK"); + } else { + logger.error("更新支付状态失败,公司ID: {}, 订单号: {}", companyId, outTradeNo); + return buildResponse("FAIL", "更新支付状态失败"); + } + + } catch (Exception e) { + logger.error("处理微信支付通知异常,{}", e); + return buildResponse("FAIL", "处理异常"); + } + } + @PostMapping("/refundNotify") + public String refundNotify(HttpServletRequest request) { + + try { + // 读取微信退款回调数据 + StringBuilder sb = new StringBuilder(); + BufferedReader reader = request.getReader(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + + String xmlData = sb.toString(); + logger.info("收到微信退款通知, 数据: {}", xmlData); + + // 解析XML数据 + Map notifyMap = WXPayUtil.xmlToMap(xmlData); + //WechatRefundNotify notify = WXPayUtil.xmlToEntity(xmlData, WechatRefundNotify.class); + + CompanyWechatConfig wechatConfig = companyWechatConfigMapper.getCompanyIdByMchId(notifyMap.get("mch_id")); + //CompanyWechatConfig wechatConfig = companyWechatConfigMapper.getWechatConfigByCompanyId(companyId); + //Long companyId = companyWechatConfigMapper.getCompanyIdByMchId(notifyMap.getMchid()); + + + Boolean isRefundSuccess; + // 验证签名 + try { + Map decryptMap = wechatPayService.decryptRefundNotify(xmlData, wechatConfig.getApikey()); + isRefundSuccess = wechatPayService.processRefundNotify(decryptMap); + } catch (Exception e){ + logger.warn("微信退款通知签名验证失败,公司ID: {}", wechatConfig.getId()); + return buildResponse("FAIL", "解密失败"); + } + + + String returnCode = notifyMap.get("return_code"); + if (!"SUCCESS".equals(returnCode)) { + logger.warn("微信退款通知返回失败,公司ID: {}: {}", wechatConfig.getId(), notifyMap.get("return_msg")); + return buildResponse("FAIL", "返回失败"); + } + + // 处理退款通知的业务逻辑 + String outRefundNo = notifyMap.get("out_refund_no"); + String refundId = notifyMap.get("refund_id"); + String refundStatus = notifyMap.get("refund_status"); + + // 更新数据库中的退款状态 + if (isRefundSuccess) { + logger.info("退款处理完成,公司ID: {}, 退款单号: {}, 微信退款单号: {}, 状态: {}", + wechatConfig.getId(), outRefundNo, refundId, refundStatus); + return buildResponse("SUCCESS", "OK"); + } else { + logger.error("更新退款状态失败,公司ID: {}, 退款单号: {}", wechatConfig.getId(), outRefundNo); + return buildResponse("FAIL", "更新退款状态失败"); + } + + } catch (Exception e) { + logger.error("处理微信退款通知异常", e); + return buildResponse("FAIL", "处理异常"); + } + } + private String buildResponse(String returnCode, String returnMsg) { + Map response = new HashMap<>(); + response.put("return_code", returnCode); + response.put("return_msg", returnMsg); + try { + return WXPayUtil.mapToXml(response); + } catch (Exception e) { + return ""; + } + } +} diff --git a/src/main/java/com/sczx/pay/dto/AlipayCloseRequest.java b/src/main/java/com/sczx/pay/dto/AlipayCloseRequest.java new file mode 100644 index 0000000..d6ad1d0 --- /dev/null +++ b/src/main/java/com/sczx/pay/dto/AlipayCloseRequest.java @@ -0,0 +1,14 @@ +package com.sczx.pay.dto; + +import lombok.Data; + +// 支付宝查询请求 +@Data +public class AlipayCloseRequest { + private Long companyId; + private String outTradeNo; // 商户订单号 + private String reasonCode; //原因编码 + private String reasonDesc; + private String openId; //买家支付宝用户唯一标识 + +} diff --git a/src/main/java/com/sczx/pay/dto/AlipayCreateRequest.java b/src/main/java/com/sczx/pay/dto/AlipayCreateRequest.java new file mode 100644 index 0000000..a8a0fc0 --- /dev/null +++ b/src/main/java/com/sczx/pay/dto/AlipayCreateRequest.java @@ -0,0 +1,14 @@ +package com.sczx.pay.dto; + +import lombok.Data; + +// 支付宝支付请求 +@Data +public class AlipayCreateRequest { + private Long companyId; // 公司ID + private String outTradeNo; // 商户订单号 + private String subject; // 订单标题 + private String totalAmount; // 订单总金额 + private String body; // 订单描述 + private String openId; // 用户ID +} diff --git a/src/main/java/com/sczx/pay/dto/AlipayCreateResponse.java b/src/main/java/com/sczx/pay/dto/AlipayCreateResponse.java new file mode 100644 index 0000000..a5388f3 --- /dev/null +++ b/src/main/java/com/sczx/pay/dto/AlipayCreateResponse.java @@ -0,0 +1,14 @@ +package com.sczx.pay.dto; + +import lombok.Data; + +// 支付宝支付响应 +@Data +public class AlipayCreateResponse { + private boolean success; + private String message; + private String tradeNo; // 支付宝交易号 + private String outTradeNo; // 商户订单号 + private String orderStr; // 支付串(用于前端调起支付) + private String code; // 状态码 +} diff --git a/src/main/java/com/sczx/pay/dto/AlipayQueryRequest.java b/src/main/java/com/sczx/pay/dto/AlipayQueryRequest.java new file mode 100644 index 0000000..a0d63b2 --- /dev/null +++ b/src/main/java/com/sczx/pay/dto/AlipayQueryRequest.java @@ -0,0 +1,11 @@ +package com.sczx.pay.dto; + +import lombok.Data; + +// 支付宝查询请求 +@Data +public class AlipayQueryRequest { + private Long companyId; + private String outTradeNo; // 商户订单号 + private String tradeNo; // 支付宝交易号(可选,与outTradeNo二选一) +} diff --git a/src/main/java/com/sczx/pay/dto/AlipayQueryResponse.java b/src/main/java/com/sczx/pay/dto/AlipayQueryResponse.java new file mode 100644 index 0000000..5f53535 --- /dev/null +++ b/src/main/java/com/sczx/pay/dto/AlipayQueryResponse.java @@ -0,0 +1,14 @@ +package com.sczx.pay.dto; + +import lombok.Data; + +// 支付宝订单查询响应 +@Data +public class AlipayQueryResponse { + private boolean success; + private String message; + private String tradeNo; // 支付宝交易号 + private String outTradeNo; // 商户订单号 + private String tradeStatus; // 交易状态 + private String code; // 状态码 +} diff --git a/src/main/java/com/sczx/pay/dto/AlipayRefundRequest.java b/src/main/java/com/sczx/pay/dto/AlipayRefundRequest.java new file mode 100644 index 0000000..0d1d75f --- /dev/null +++ b/src/main/java/com/sczx/pay/dto/AlipayRefundRequest.java @@ -0,0 +1,14 @@ +package com.sczx.pay.dto; + +import lombok.Data; + +// 支付宝退款请求 +@Data +public class AlipayRefundRequest { + private Long companyId; + private String outTradeNo; // 商户订单号 + private String refundAmount; // 退款金额 + private String refundReason; // 退款原因 + private String outRequestNo; // 退款请求号 + private String tradeNo; // 支付宝交易号(可选,与outTradeNo二选一) +} diff --git a/src/main/java/com/sczx/pay/dto/AlipayResponse.java b/src/main/java/com/sczx/pay/dto/AlipayResponse.java new file mode 100644 index 0000000..5767619 --- /dev/null +++ b/src/main/java/com/sczx/pay/dto/AlipayResponse.java @@ -0,0 +1,10 @@ +package com.sczx.pay.dto; + +import lombok.Data; + +@Data +public class AlipayResponse { + private boolean success; + private String message; + private String code; // 状态码 +} diff --git a/src/main/java/com/sczx/pay/dto/PaymentRequest.java b/src/main/java/com/sczx/pay/dto/PaymentRequest.java new file mode 100644 index 0000000..b841cd1 --- /dev/null +++ b/src/main/java/com/sczx/pay/dto/PaymentRequest.java @@ -0,0 +1,74 @@ +package com.sczx.pay.dto; + +/** + * 支付请求数据传输对象 + */ +public class PaymentRequest { + private Long companyId; // 公司ID + private String body; // 商品描述 + private String outTradeNo; // 商户订单号 + private Integer totalFee; // 总金额,单位为分 + private String spbillCreateIp; // 终端IP + private String openId; // 用户标识 + private String attach; // 附加数据 + + // 构造函数 + public PaymentRequest() {} + + // getter和setter方法 + public Long getCompanyId() { + return companyId; + } + + public void setCompanyId(Long companyId) { + this.companyId = companyId; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getOutTradeNo() { + return outTradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public Integer getTotalFee() { + return totalFee; + } + + public void setTotalFee(Integer totalFee) { + this.totalFee = totalFee; + } + + public String getSpbillCreateIp() { + return spbillCreateIp; + } + + public void setSpbillCreateIp(String spbillCreateIp) { + this.spbillCreateIp = spbillCreateIp; + } + + public String getOpenId() { + return openId; + } + + public void setOpenId(String openId) { + this.openId = openId; + } + + public String getAttach() { + return attach; + } + + public void setAttach(String attach) { + this.attach = attach; + } +} diff --git a/src/main/java/com/sczx/pay/dto/PaymentResponse.java b/src/main/java/com/sczx/pay/dto/PaymentResponse.java new file mode 100644 index 0000000..8187cbd --- /dev/null +++ b/src/main/java/com/sczx/pay/dto/PaymentResponse.java @@ -0,0 +1,38 @@ +package com.sczx.pay.dto; + +import java.util.Map; + +/** + * 支付响应数据传输对象 + */ +public class PaymentResponse { + private String code; + private String message; + private Map payData; + + public PaymentResponse() {} + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Map getPayData() { + return payData; + } + + public void setPayData(Map payData) { + this.payData = payData; + } +} diff --git a/src/main/java/com/sczx/pay/dto/RefundRequest.java b/src/main/java/com/sczx/pay/dto/RefundRequest.java new file mode 100644 index 0000000..0232228 --- /dev/null +++ b/src/main/java/com/sczx/pay/dto/RefundRequest.java @@ -0,0 +1,65 @@ +package com.sczx.pay.dto; + +/** + * 退款请求数据传输对象 + */ +public class RefundRequest { + private Long companyId; // 公司ID + private String outTradeNo; // 商户订单号 + private String outRefundNo; // 商户退款单号 + private Integer totalFee; // 订单金额(分) + private Integer refundFee; // 退款金额(分) + private String refundDesc; // 退款原因 + + // 构造函数 + public RefundRequest() {} + + // getter和setter方法 + public Long getCompanyId() { + return companyId; + } + + public void setCompanyId(Long companyId) { + this.companyId = companyId; + } + + public String getOutTradeNo() { + return outTradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public String getOutRefundNo() { + return outRefundNo; + } + + public void setOutRefundNo(String outRefundNo) { + this.outRefundNo = outRefundNo; + } + + public Integer getTotalFee() { + return totalFee; + } + + public void setTotalFee(Integer totalFee) { + this.totalFee = totalFee; + } + + public Integer getRefundFee() { + return refundFee; + } + + public void setRefundFee(Integer refundFee) { + this.refundFee = refundFee; + } + + public String getRefundDesc() { + return refundDesc; + } + + public void setRefundDesc(String refundDesc) { + this.refundDesc = refundDesc; + } +} diff --git a/src/main/java/com/sczx/pay/dto/ali/rent/AlipayRentPayReq.java b/src/main/java/com/sczx/pay/dto/ali/rent/AlipayRentPayReq.java new file mode 100644 index 0000000..b1cf623 --- /dev/null +++ b/src/main/java/com/sczx/pay/dto/ali/rent/AlipayRentPayReq.java @@ -0,0 +1,13 @@ +package com.sczx.pay.dto.ali.rent; + +import io.swagger.annotations.ApiModel; +import lombok.Data; + +@ApiModel(value = "支付宝租赁订单支付请求") +@Data +public class AlipayRentPayReq { + private Long companyId; // 公司ID + private String outTradeNo; // 商户订单号 + private String orderId; //交易组件业务订单号 + private String payAmount; // 支付金额 +} diff --git a/src/main/java/com/sczx/pay/dto/ali/rent/AlipayRentPayResponse.java b/src/main/java/com/sczx/pay/dto/ali/rent/AlipayRentPayResponse.java new file mode 100644 index 0000000..a19e567 --- /dev/null +++ b/src/main/java/com/sczx/pay/dto/ali/rent/AlipayRentPayResponse.java @@ -0,0 +1,17 @@ +package com.sczx.pay.dto.ali.rent; + +import com.sczx.pay.dto.AlipayResponse; +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "支付宝租赁订单支付结果") +@Data +public class AlipayRentPayResponse extends AlipayResponse { + + private String tradeNo; // 支付宝交易号 + private String outTradeNo; // 商户订单号 + private String orderId; //交易组件业务订单号 + private String payAmount; // 支付金额 +} diff --git a/src/main/java/com/sczx/pay/entity/CompanyAlipayConfig.java b/src/main/java/com/sczx/pay/entity/CompanyAlipayConfig.java new file mode 100644 index 0000000..66d9c34 --- /dev/null +++ b/src/main/java/com/sczx/pay/entity/CompanyAlipayConfig.java @@ -0,0 +1,44 @@ +package com.sczx.pay.entity; + +/** + * 公司支付宝支付配置实体类 + */ +public class CompanyAlipayConfig { + private Long id; + private String mchId; + private String apikey; + + // 构造函数 + public CompanyAlipayConfig() {} + + public CompanyAlipayConfig(Long id, String mchId, String apikey) { + this.id = id; + this.mchId = mchId; + this.apikey = apikey; + } + + // Getter和Setter方法 + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMchId() { + return mchId; + } + + public void setMchId(String mchId) { + this.mchId = mchId; + } + + public String getApikey() { + return apikey; + } + + public void setApikey(String apikey) { + this.apikey = apikey; + } +} diff --git a/src/main/java/com/sczx/pay/entity/CompanyWechatConfig.java b/src/main/java/com/sczx/pay/entity/CompanyWechatConfig.java new file mode 100644 index 0000000..390bf33 --- /dev/null +++ b/src/main/java/com/sczx/pay/entity/CompanyWechatConfig.java @@ -0,0 +1,44 @@ +package com.sczx.pay.entity; + +/** + * 公司微信支付配置实体类 + */ +public class CompanyWechatConfig { + private Long id; + private String mchId; + private String apikey; + + // 构造函数 + public CompanyWechatConfig() {} + + public CompanyWechatConfig(Long id, String mchId, String apikey) { + this.id = id; + this.mchId = mchId; + this.apikey = apikey; + } + + // Getter和Setter方法 + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMchId() { + return mchId; + } + + public void setMchId(String mchId) { + this.mchId = mchId; + } + + public String getApikey() { + return apikey; + } + + public void setApikey(String apikey) { + this.apikey = apikey; + } +} diff --git a/src/main/java/com/sczx/pay/entity/OrderMain.java b/src/main/java/com/sczx/pay/entity/OrderMain.java new file mode 100644 index 0000000..21955a8 --- /dev/null +++ b/src/main/java/com/sczx/pay/entity/OrderMain.java @@ -0,0 +1,48 @@ +package com.sczx.pay.entity; + +import java.math.BigDecimal; + +public class OrderMain { + private Long orderId; + private String orderNo; + private String orderStatus; + private Integer overdueDays; + private BigDecimal orderAmount; + + public Long getOrderId() { + return orderId; + } + public void setOrderId(Long orderId) { + this.orderId = orderId; + } + + public String getOrderNo() { + return orderNo; + } + public void setOrderNo(String orderNo) { + this.orderNo = orderNo; + } + + public String getOrderStatus() { + return orderStatus; + } + public void setOrderStatus(String orderStatus) { + this.orderStatus = orderStatus; + } + + public Integer getOverdueDays() { + return overdueDays; + } + + public void setOverdueDays(Integer overdueDays) { + this.overdueDays = overdueDays; + } + + public BigDecimal getOrderAmount() { + return orderAmount; + } + + public void setOrderAmount(BigDecimal orderAmount) { + this.orderAmount = orderAmount; + } +} diff --git a/src/main/java/com/sczx/pay/entity/OrderSub.java b/src/main/java/com/sczx/pay/entity/OrderSub.java new file mode 100644 index 0000000..2a79faa --- /dev/null +++ b/src/main/java/com/sczx/pay/entity/OrderSub.java @@ -0,0 +1,29 @@ +package com.sczx.pay.entity; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +public class OrderSub { + + @ApiModelProperty("子订单ID(主键)") + private Long suborderId; + + @ApiModelProperty("关联的订单ID(外键)") + private Long orderId; + + @ApiModelProperty("子订单编号") + private String suborderNo; + + @ApiModelProperty("子订单类型(首租、续租、逾期、押金、租电等)") + private String suborderType; + + @ApiModelProperty("订单金额") + private BigDecimal amount; + + @ApiModelProperty("还车时间,租车子订单") + private LocalDateTime returnTime; +} diff --git a/src/main/java/com/sczx/pay/entity/PayStatus.java b/src/main/java/com/sczx/pay/entity/PayStatus.java new file mode 100644 index 0000000..a070863 --- /dev/null +++ b/src/main/java/com/sczx/pay/entity/PayStatus.java @@ -0,0 +1,113 @@ +package com.sczx.pay.entity; + +/** + * 支付状态枚举 + */ +public enum PayStatus { + /** + * 未支付 + */ + NOTPAY(1, "NOTPAY", "未支付"), + + /** + * 支付成功 + */ + SUCCESS(2, "SUCCESS", "支付成功"), + + /** + * 转入退款 + */ + REFUND(3, "REFUND", "转入退款"), + + /** + * 已关闭 + */ + CLOSED(4, "CLOSED", "已关闭"), + + /** + * 已撤销 + */ + REVOKED(5, "REVOKED", "已撤销"), + + /** + * 支付失败 + */ + PAYERROR(6, "PAYERROR", "支付失败"); + + private final int id; + private final String code; + private final String description; + + PayStatus(int id, String code, String description) { + this.id = id; + this.code = code; + this.description = description; + } + + /** + * 根据ID获取支付状态 + */ + public static PayStatus fromId(int id) { + for (PayStatus status : PayStatus.values()) { + if (status.getId() == id) { + return status; + } + } + throw new IllegalArgumentException("未知的支付状态ID: " + id); + } + + /** + * 根据编码获取支付状态 + */ + public static PayStatus fromCode(String code) { + for (PayStatus status : PayStatus.values()) { + if (status.getCode().equals(code)) { + return status; + } + } + throw new IllegalArgumentException("未知的支付状态编码: " + code); + } + + /** + * 判断支付是否成功 + */ + public boolean isPaySuccess() { + return this == SUCCESS; + } + + /** + * 判断是否为退款状态 + */ + public boolean isRefund() { + return this == REFUND; + } + + /** + * 判断交易是否完成(成功或关闭) + */ + public boolean isTradeFinished() { + return this == SUCCESS || this == CLOSED || this == REVOKED; + } + + // Getter方法 + public int getId() { + return id; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + return "PayStatus{" + + "id=" + id + + ", code='" + code + '\'' + + ", description='" + description + '\'' + + '}'; + } +} diff --git a/src/main/java/com/sczx/pay/entity/PaymentNotifyRecord.java b/src/main/java/com/sczx/pay/entity/PaymentNotifyRecord.java new file mode 100644 index 0000000..472bde7 --- /dev/null +++ b/src/main/java/com/sczx/pay/entity/PaymentNotifyRecord.java @@ -0,0 +1,169 @@ +package com.sczx.pay.entity; + +import java.util.Date; + +/** + * 支付通知记录实体类 + */ +public class PaymentNotifyRecord { + private Long id; + private Long companyId; + private String outTradeNo; // 商户订单号 + private String outRefundNo; // 商户退款单号 + private String notifyData; // 通知原始数据 + private String notifyType; // 通知类型:PAY-支付通知,REFUND-退款通知 + private String tradeStatus; // 交易状态 + private String refundStatus; // 退款状态 + private String payChannel; // 支付渠道:WECHAT-微信支付,ALIPAY-支付宝 + private Integer processStatus; // 处理状态:0-未处理,1-已处理,2-处理失败 + private String processResult; // 处理结果 + private Date createTime; // 创建时间 + private Date updateTime; // 更新时间 + + // 通知类型枚举 + public enum NotifyType { + PAY("支付通知"), + REFUND("退款通知"); + + private final String description; + + NotifyType(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + } + + // 处理状态枚举 + public enum ProcessStatus { + UNPROCESSED(0, "未处理"), + PROCESSED(1, "已处理"), + FAILED(2, "处理失败"); + + private final int code; + private final String description; + + ProcessStatus(int code, String description) { + this.code = code; + this.description = description; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + } + + // 构造函数 + public PaymentNotifyRecord() {} + + // Getter和Setter方法 + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getCompanyId() { + return companyId; + } + + public void setCompanyId(Long companyId) { + this.companyId = companyId; + } + + public String getOutTradeNo() { + return outTradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public String getOutRefundNo() { + return outRefundNo; + } + + public void setOutRefundNo(String outRefundNo) { + this.outRefundNo = outRefundNo; + } + + public String getNotifyData() { + return notifyData; + } + + public void setNotifyData(String notifyData) { + this.notifyData = notifyData; + } + + public String getNotifyType() { + return notifyType; + } + + public void setNotifyType(String notifyType) { + this.notifyType = notifyType; + } + + public String getTradeStatus() { + return tradeStatus; + } + + public void setTradeStatus(String tradeStatus) { + this.tradeStatus = tradeStatus; + } + + public String getRefundStatus() { + return refundStatus; + } + + public void setRefundStatus(String refundStatus) { + this.refundStatus = refundStatus; + } + + public String getPayChannel() { + return payChannel; + } + + public void setPayChannel(String payChannel) { + this.payChannel = payChannel; + } + + public Integer getProcessStatus() { + return processStatus; + } + + public void setProcessStatus(Integer processStatus) { + this.processStatus = processStatus; + } + + public String getProcessResult() { + return processResult; + } + + public void setProcessResult(String processResult) { + this.processResult = processResult; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } +} diff --git a/src/main/java/com/sczx/pay/entity/PaymentRecord.java b/src/main/java/com/sczx/pay/entity/PaymentRecord.java new file mode 100644 index 0000000..7b4a3fd --- /dev/null +++ b/src/main/java/com/sczx/pay/entity/PaymentRecord.java @@ -0,0 +1,177 @@ +package com.sczx.pay.entity; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 支付记录实体类 + */ +public class PaymentRecord { + private Long id; + private Long companyId; + private String outTradeNo; // 商户订单号 + private String transactionId; // 支付平台交易号 + private BigDecimal totalFee; // 订单金额 + private String body; // 商品描述 + private String openid; // 用户标识(微信支付专用) + private String tradeCompOrderId; // 支付宝交易组件订单号 + private String tradeState; // 交易状态 + private String tradeStateDesc; // 交易状态描述 + private Date createTime; // 创建时间 + private Date updateTime; // 更新时间 + private Date payTime; // 支付时间 + private String attach; // 附加数据 + private String payChannel; // 支付渠道:WECHAT-微信支付,ALIPAY-支付宝 + private String buyerId; // 买家用户ID(支付宝专用) + + // 支付渠道枚举 + public enum PayChannel { + WECHAT("微信支付"), + ALIPAY("支付宝"), + ALIPAY_RENT("支付宝租赁支付"); + + private final String description; + + PayChannel(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + } + + // 构造函数 + public PaymentRecord() { + this.payChannel = PayChannel.WECHAT.name(); // 默认为微信支付 + } + + // Getter和Setter方法 + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getCompanyId() { + return companyId; + } + + public void setCompanyId(Long companyId) { + this.companyId = companyId; + } + + public String getOutTradeNo() { + return outTradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public BigDecimal getTotalFee() { + return totalFee; + } + + public void setTotalFee(BigDecimal totalFee) { + this.totalFee = totalFee; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getOpenid() { + return openid; + } + + public void setOpenid(String openid) { + this.openid = openid; + } + + public String getTradeState() { + return tradeState; + } + + public void setTradeState(String tradeState) { + this.tradeState = tradeState; + } + + public String getTradeStateDesc() { + return tradeStateDesc; + } + + public void setTradeStateDesc(String tradeStateDesc) { + this.tradeStateDesc = tradeStateDesc; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public Date getPayTime() { + return payTime; + } + + public void setPayTime(Date payTime) { + this.payTime = payTime; + } + + public String getAttach() { + return attach; + } + + public void setAttach(String attach) { + this.attach = attach; + } + + public String getPayChannel() { + return payChannel; + } + + public void setPayChannel(String payChannel) { + this.payChannel = payChannel; + } + + public String getBuyerId() { + return buyerId; + } + + public void setBuyerId(String buyerId) { + this.buyerId = buyerId; + } + + public String getTradeCompOrderId() { + return tradeCompOrderId; + } + + public void setTradeCompOrderId(String tradeCompOrderId) { + this.tradeCompOrderId = tradeCompOrderId; + } +} diff --git a/src/main/java/com/sczx/pay/entity/ReasonCodes.java b/src/main/java/com/sczx/pay/entity/ReasonCodes.java new file mode 100644 index 0000000..e886e27 --- /dev/null +++ b/src/main/java/com/sczx/pay/entity/ReasonCodes.java @@ -0,0 +1,165 @@ +package com.sczx.pay.entity; + +public enum ReasonCodes { + + /** + * 用户租赁计划有变 + */ + USER_LEASE_PLAN_CHANGED("3103", "用户租赁计划有变"), + + /** + * 用户订单信息错误(租期、地址、商品信息等) + */ + USER_ORDER_INFO_ERROR("3104", "用户订单信息错误(租期、地址、商品信息等)"), + + /** + * 用户测试或误操作 + */ + USER_TEST_OR_MISOPERATION("3105", "用户测试或误操作"), + + /** + * 用户重复下单 + */ + USER_DUPLICATE_ORDER("3106", "用户重复下单"), + + /** + * 用户不接电话 + */ + USER_NOT_ANSWERING_CALLS("3107", "用户不接电话"), + + /** + * 租金扣款失败 + */ + RENT_DEDUCTION_FAILED("3108", "租金扣款失败"), + + /** + * 用户不愿意预付租金 + */ + USER_REFUSES_PREPAY_RENT("3109", "用户不愿意预付租金"), + + /** + * 用户不愿意付押金 + */ + USER_REFUSES_DEPOSIT("3110", "用户不愿意付押金"), + + /** + * 用户对价格不认可 + */ + USER_PRICE_DISAGREEMENT("3111", "用户对价格不认可"), + + /** + * 用户担心非正品 + */ + USER_WORRIED_NOT_AUTHENTIC("3132", "用户担心非正品"), + + /** + * 用户担心商品损坏 + */ + USER_WORRIED_PRODUCT_DAMAGE("3133", "用户担心商品损坏"), + + /** + * 用户不要监管机 + */ + USER_REFUSES_SUPERVISED_DEVICE("3134", "用户不要监管机"), + + /** + * 收货地区不支持发货 + */ + DELIVERY_AREA_NOT_SUPPORTED("3135", "收货地区不支持发货"), + + /** + * 已购买同款商品 + */ + ALREADY_PURCHASED_SAME_PRODUCT("3136", "已购买同款商品"), + + /** + * 用户电话空号/关机 + */ + USER_PHONE_UNAVAILABLE("3137", "用户电话空号/关机"), + + /** + * 用户提供虚假信息 + */ + USER_PROVIDED_FALSE_INFO("3112", "用户提供虚假信息"), + + /** + * 用户不配合补充审核资料 + */ + USER_NOT_COOPERATING_AUDIT("3113", "用户不配合补充审核资料"), + + /** + * 用户资质问题 + */ + USER_QUALIFICATION_ISSUES("3114", "用户资质问题"), + + /** + * 智安盾高风险用户 + */ + HIGH_RISK_USER("3115", "智安盾高风险用户"), + + /** + * 审核时间过长 + */ + AUDIT_TIME_TOO_LONG("3138", "审核时间过长"), + + /** + * 商家私下要我隐私资料 + */ + MERCHANT_ASKING_PRIVATE_INFO("3139", "商家私下要我隐私资料"), + + /** + * 发货时间来不及 + */ + DELIVERY_TIME_INSUFFICIENT("3116", "发货时间来不及"), + + /** + * 商品缺货 + */ + PRODUCT_OUT_OF_STOCK("3117", "商品缺货"), + + /** + * 无物流信息 + */ + NO_LOGISTICS_INFO("3140", "无物流信息"), + + /** + * 服务态度差 + */ + POOR_SERVICE_ATTITUDE("3141", "服务态度差"), + + /** + * 私下收不合理费用 + */ + UNREASONABLE_PRIVATE_CHARGES("3142", "私下收不合理费用"), + + /** + * 联系不上商家 + */ + CANNOT_CONTACT_MERCHANT("3143", "联系不上商家"), + + /** + * 让我其他方式交易 + */ + ASKED_FOR_OTHER_TRANSACTION_METHOD("3144", "让我其他方式交易"); + + private final String code; + private final String description; + + ReasonCodes(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + return code; + } +} diff --git a/src/main/java/com/sczx/pay/entity/RefundRecord.java b/src/main/java/com/sczx/pay/entity/RefundRecord.java new file mode 100644 index 0000000..b8f820b --- /dev/null +++ b/src/main/java/com/sczx/pay/entity/RefundRecord.java @@ -0,0 +1,158 @@ +package com.sczx.pay.entity; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 退款记录实体类 + */ +public class RefundRecord { + private Long id; + private Long companyId; + private String outTradeNo; // 商户订单号 + private String outRefundNo; // 商户退款单号 + private String refundId; // 支付平台退款单号 + private BigDecimal totalFee; // 订单金额 + private BigDecimal refundFee; // 退款金额 + private String refundStatus; // 退款状态 + private String refundStatusDesc; // 退款状态描述 + private String refundDesc; // 退款原因 + private Date createTime; // 创建时间 + private Date updateTime; // 更新时间 + private Date refundTime; // 退款成功时间 + private String payChannel; // 支付渠道:WECHAT-微信支付,ALIPAY-支付宝 + + // 支付渠道枚举 + public enum PayChannel { + WECHAT("微信支付"), + ALIPAY("支付宝"); + + private final String description; + + PayChannel(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + } + + // 构造函数 + public RefundRecord() { + this.payChannel = PayChannel.WECHAT.name(); // 默认为微信支付 + } + + // Getter和Setter方法 + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getCompanyId() { + return companyId; + } + + public void setCompanyId(Long companyId) { + this.companyId = companyId; + } + + public String getOutTradeNo() { + return outTradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public String getOutRefundNo() { + return outRefundNo; + } + + public void setOutRefundNo(String outRefundNo) { + this.outRefundNo = outRefundNo; + } + + public String getRefundId() { + return refundId; + } + + public void setRefundId(String refundId) { + this.refundId = refundId; + } + + public BigDecimal getTotalFee() { + return totalFee; + } + + public void setTotalFee(BigDecimal totalFee) { + this.totalFee = totalFee; + } + + public BigDecimal getRefundFee() { + return refundFee; + } + + public void setRefundFee(BigDecimal refundFee) { + this.refundFee = refundFee; + } + + public String getRefundStatus() { + return refundStatus; + } + + public void setRefundStatus(String refundStatus) { + this.refundStatus = refundStatus; + } + + public String getRefundStatusDesc() { + return refundStatusDesc; + } + + public void setRefundStatusDesc(String refundStatusDesc) { + this.refundStatusDesc = refundStatusDesc; + } + + public String getRefundDesc() { + return refundDesc; + } + + public void setRefundDesc(String refundDesc) { + this.refundDesc = refundDesc; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public Date getRefundTime() { + return refundTime; + } + + public void setRefundTime(Date refundTime) { + this.refundTime = refundTime; + } + + public String getPayChannel() { + return payChannel; + } + + public void setPayChannel(String payChannel) { + this.payChannel = payChannel; + } +} diff --git a/src/main/java/com/sczx/pay/entity/RefundStatus.java b/src/main/java/com/sczx/pay/entity/RefundStatus.java new file mode 100644 index 0000000..cc2b465 --- /dev/null +++ b/src/main/java/com/sczx/pay/entity/RefundStatus.java @@ -0,0 +1,110 @@ +package com.sczx.pay.entity; + +/** + * 退款状态枚举 + */ +public enum RefundStatus { + /** + * 退款处理中 + */ + PROCESSING(1, "PROCESSING", "退款处理中"), + + /** + * 退款成功 + */ + SUCCESS(2, "SUCCESS", "退款成功"), + + /** + * 退款关闭 + */ + REFUNDCLOSE(3, "REFUNDCLOSE", "退款关闭"), + + /** + * 退款异常 + */ + CHANGE(4, "CHANGE", "退款异常"); + + private final int id; + private final String code; + private final String description; + + RefundStatus(int id, String code, String description) { + this.id = id; + this.code = code; + this.description = description; + } + + /** + * 根据ID获取退款状态 + */ + public static RefundStatus fromId(int id) { + for (RefundStatus status : RefundStatus.values()) { + if (status.getId() == id) { + return status; + } + } + throw new IllegalArgumentException("未知的退款状态ID: " + id); + } + + /** + * 根据编码获取退款状态 + */ + public static RefundStatus fromCode(String code) { + for (RefundStatus status : RefundStatus.values()) { + if (status.getCode().equals(code)) { + return status; + } + } + throw new IllegalArgumentException("未知的退款状态编码: " + code); + } + + /** + * 判断退款是否成功 + */ + public boolean isRefundSuccess() { + return this == SUCCESS; + } + + /** + * 判断退款是否完成(成功或关闭) + */ + public boolean isRefundFinished() { + return this == SUCCESS || this == REFUNDCLOSE; + } + + /** + * 判断退款是否异常 + */ + public boolean isRefundAbnormal() { + return this == CHANGE; + } + + /** + * 判断退款是否处理中 + */ + public boolean isProcessing() { + return this == PROCESSING; + } + + // Getter方法 + public int getId() { + return id; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + return "RefundStatus{" + + "id=" + id + + ", code='" + code + '\'' + + ", description='" + description + '\'' + + '}'; + } +} diff --git a/src/main/java/com/sczx/pay/exception/InnerException.java b/src/main/java/com/sczx/pay/exception/InnerException.java new file mode 100644 index 0000000..6df71e1 --- /dev/null +++ b/src/main/java/com/sczx/pay/exception/InnerException.java @@ -0,0 +1,86 @@ +package com.sczx.pay.exception; + + +import com.sczx.pay.common.IApiCode; +import com.sczx.pay.common.enums.ApiErrorCode; + +/** + * + * @author zhangli + + */ +public class InnerException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + protected String errorCode; + /** + * 错误信息 + */ + protected String errorMsg; + + public InnerException() { + super(); + } + + public InnerException(IApiCode apiCode) { + super(apiCode.getCode()); + this.errorCode = apiCode.getCode(); + this.errorMsg = apiCode.getMsg(); + } + + public InnerException(IApiCode apiCode, Throwable cause) { + super(apiCode.getCode(), cause); + this.errorCode = apiCode.getCode(); + this.errorMsg = apiCode.getMsg(); + } + + public InnerException(String errorMsg) { + super(errorMsg); + this.errorCode = ApiErrorCode.INNER_ERROR.getCode(); + this.errorMsg = errorMsg; + } + + public InnerException(String errorCode, String errorMsg) { + super(errorCode); + this.errorCode = errorCode; + this.errorMsg = errorMsg; + } + + public InnerException(String errorCode, String errorMsg, Throwable cause) { + super(errorCode, cause); + this.errorCode = errorCode; + this.errorMsg = errorMsg; + } + + + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public String getErrorMsg() { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) { + this.errorMsg = errorMsg; + } + + @Override + public String getMessage() { + return errorMsg; + } + + @Override + public Throwable fillInStackTrace() { + return this; + } + +} diff --git a/src/main/java/com/sczx/pay/mapper/CompanyAlipayConfigMapper.java b/src/main/java/com/sczx/pay/mapper/CompanyAlipayConfigMapper.java new file mode 100644 index 0000000..2885413 --- /dev/null +++ b/src/main/java/com/sczx/pay/mapper/CompanyAlipayConfigMapper.java @@ -0,0 +1,22 @@ +package com.sczx.pay.mapper; + +import com.sczx.pay.entity.CompanyAlipayConfig; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface CompanyAlipayConfigMapper { + + /** + * 根据公司ID获取支付宝支付配置 + * @param companyId 公司ID + * @return 微信支付配置信息 + */ + @Select("SELECT id, ali_receiving_account AS mchId, ali_key AS apikey FROM zc_company WHERE id = #{companyId}") + CompanyAlipayConfig getConfigByCompanyId(@Param("companyId") Long companyId); + + + @Select("SELECT id,ali_receiving_account AS mchId, ali_key AS apikey FROM zc_company WHERE ali_receiving_account = #{mchId} limit 1") + CompanyAlipayConfig getCompanyIdByMchId(@Param("mchId") String mchId); +} diff --git a/src/main/java/com/sczx/pay/mapper/CompanyWechatConfigMapper.java b/src/main/java/com/sczx/pay/mapper/CompanyWechatConfigMapper.java new file mode 100644 index 0000000..b57ad6b --- /dev/null +++ b/src/main/java/com/sczx/pay/mapper/CompanyWechatConfigMapper.java @@ -0,0 +1,22 @@ +package com.sczx.pay.mapper; + +import com.sczx.pay.entity.CompanyWechatConfig; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface CompanyWechatConfigMapper { + + /** + * 根据公司ID获取微信支付配置 + * @param companyId 公司ID + * @return 微信支付配置信息 + */ + @Select("SELECT id, wechat_receiving_account AS mchId, wechat_key AS apikey FROM zc_company WHERE id = #{companyId}") + CompanyWechatConfig getWechatConfigByCompanyId(@Param("companyId") Long companyId); + + + @Select("SELECT id,wechat_receiving_account AS mchId, wechat_key AS apikey FROM zc_company WHERE wechat_receiving_account = #{mchId} limit 1") + CompanyWechatConfig getCompanyIdByMchId(@Param("mchId") String mchId); +} diff --git a/src/main/java/com/sczx/pay/mapper/OrderPayMapper.java b/src/main/java/com/sczx/pay/mapper/OrderPayMapper.java new file mode 100644 index 0000000..9630be6 --- /dev/null +++ b/src/main/java/com/sczx/pay/mapper/OrderPayMapper.java @@ -0,0 +1,73 @@ +package com.sczx.pay.mapper; + +import com.sczx.pay.entity.OrderMain; +import com.sczx.pay.entity.OrderSub; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Date; + +@Mapper +public interface OrderPayMapper { + + /** + * 根据商户订单号更新支付状态 + */ + @Update("UPDATE zc_order_main SET pay_type = #{payType}, pay_status = #{payStatus}, " + + "payment_no = #{transactionId}" + + "WHERE order_no = #{outTradeNo}") + int updatePaymentStatus(@Param("outTradeNo") String outTradeNo, + @Param("payType") String payType, + @Param("payStatus") String payStatus, + @Param("transactionId") String transactionId); + + @Update("update zc_order_main as om,zc_order_sub as os set os.transaction_id = #{transactionId},os.pay_status = #{payStatus}," + + "os.payment_method = #{payType}" + + " where om.order_id = os.order_id and os.payment_id = #{outTradeNo}") + int updateSubOrderPaymentStatus(@Param("outTradeNo") String outTradeNo, + @Param("payType") String payType, + @Param("payStatus") String payStatus, + @Param("transactionId") String transactionId); + + @Update("UPDATE zc_order_main SET order_status = #{orderStatus}" + + "WHERE order_no = #{outTradeNo}") + int updateOrderStatus(@Param("outTradeNo") String outTradeNo, + @Param("orderStatus") String orderStatus); + + @Update("UPDATE zc_order_main SET order_status = #{orderStatus} , end_rent_time = #{returnTime}, overdue_days = #{overdueDays}, order_amount = #{orderAmount}" + + "WHERE order_no = #{outTradeNo}") + int updateOrderStatusAndEndRentTime(@Param("outTradeNo") String outTradeNo,@Param("returnTime") LocalDateTime returnTime,@Param("overdueDays") Integer overdueDays,@Param("orderAmount") BigDecimal orderAmount, + @Param("orderStatus") String orderStatus); + + @Update("update zc_order_main as om,zc_order_sub as os set os.transaction_id = #{paymentId},os.pay_status = #{payStatus}" + + " where om.order_id = os.order_id and om.order_no = #{outTradeNo} and suborder_type = 'DEPOSIT'") + int updateRefundOrderStatus(@Param("outTradeNo") String outTradeNo, + @Param("orderStatus") String orderStatus, + @Param("transactionId") String transactionId); + + + @Select("select order_id,order_no,order_status,overdue_days, order_amount from zc_order_main " + + "where order_id in (select order_id from zc_order_sub " + + "where payment_id = #{paymentId} and del_flag = '0')") + OrderMain getOrderStatusByOrderNo(@Param("paymentId") String paymentId); + + @Update("update zc_order_main as om,zc_order_sub as os set os.transaction_id = #{refundId},os.pay_status = #{payStatus}, os.amount = #{refundFee},os.update_time = #{updateTime}" + + " where om.order_id = os.order_id and os.payment_id = #{outTradeNo} and os.suborder_type = 'FD_DEPOSIT'" ) + int updateSubOrderRefundStatus(@Param("outTradeNo") String outTradeNo, + @Param("payStatus") String payStatus, + @Param("refundFee") BigDecimal refundFee, + @Param("updateTime") Date updateTime, + @Param("refundId") String refundId); + + + @Select("select suborder_id from zc_order_sub where payment_id = #{paymentId} and suborder_type = 'RENTBATTEY' limit 1") + Long getSubOrderIdByTradeNo(@Param("paymentId") String paymentId); + + @Select("select * from zc_order_sub where payment_id = #{paymentId} order by suborder_id desc limit 1") + OrderSub getSubOrderByTradeNo(@Param("paymentId") String paymentId); + +} diff --git a/src/main/java/com/sczx/pay/mapper/PaymentNotifyRecordMapper.java b/src/main/java/com/sczx/pay/mapper/PaymentNotifyRecordMapper.java new file mode 100644 index 0000000..a1be63d --- /dev/null +++ b/src/main/java/com/sczx/pay/mapper/PaymentNotifyRecordMapper.java @@ -0,0 +1,28 @@ +package com.sczx.pay.mapper; + +import com.sczx.pay.entity.PaymentNotifyRecord; +import org.apache.ibatis.annotations.*; + +@Mapper +public interface PaymentNotifyRecordMapper { + + /** + * 插入支付通知记录 + */ + @Insert("INSERT INTO payment_notify_record (company_id, out_trade_no, out_refund_no, notify_data, notify_type, " + + "trade_status, refund_status, pay_channel, process_status, process_result) " + + "VALUES (#{companyId}, #{outTradeNo}, #{outRefundNo}, #{notifyData}, #{notifyType}, " + + "#{tradeStatus}, #{refundStatus}, #{payChannel}, #{processStatus}, #{processResult})") + @Options(useGeneratedKeys = true, keyProperty = "id") + int insertPaymentNotifyRecord(PaymentNotifyRecord paymentNotifyRecord); + + /** + * 更新支付通知记录处理状态 + */ + @Update("UPDATE payment_notify_record SET process_status = #{processStatus}, process_result = #{processResult}, " + + "update_time = #{updateTime} WHERE id = #{id}") + int updateProcessStatus(@Param("id") Long id, + @Param("processStatus") Integer processStatus, + @Param("processResult") String processResult, + @Param("updateTime") java.util.Date updateTime); +} diff --git a/src/main/java/com/sczx/pay/mapper/PaymentRecordMapper.java b/src/main/java/com/sczx/pay/mapper/PaymentRecordMapper.java new file mode 100644 index 0000000..4918a0b --- /dev/null +++ b/src/main/java/com/sczx/pay/mapper/PaymentRecordMapper.java @@ -0,0 +1,62 @@ +package com.sczx.pay.mapper; + +import com.sczx.pay.entity.PaymentRecord; +import org.apache.ibatis.annotations.*; + +import java.util.Date; + +@Mapper +public interface PaymentRecordMapper { + + /** + * 插入支付记录 + */ + @Insert("INSERT INTO zc_payment_record (company_id, out_trade_no, transaction_id, total_fee, body, openid, " + + "trade_state, trade_state_desc, create_time, update_time, pay_time, attach, pay_channel, buyer_id) " + + "VALUES (#{companyId}, #{outTradeNo}, #{transactionId}, #{totalFee}, #{body}, #{openid}, " + + "#{tradeState}, #{tradeStateDesc}, #{createTime}, #{updateTime}, #{payTime}, #{attach}, #{payChannel}, #{buyerId})") + @Options(useGeneratedKeys = true, keyProperty = "id") + int insertPaymentRecord(PaymentRecord paymentRecord); + + /** + * 根据商户订单号查询支付记录 + */ + @Select("SELECT * FROM zc_payment_record WHERE out_trade_no = #{outTradeNo} LIMIT 1") + PaymentRecord getPaymentRecordByOutTradeNo(@Param("outTradeNo") String outTradeNo); + + /** + * 根据商户订单号更新支付状态 + */ + @Update("UPDATE zc_payment_record SET trade_state = #{tradeState}, trade_state_desc = #{tradeStateDesc}, " + + "transaction_id = #{transactionId}, pay_time = #{payTime}, update_time = #{updateTime} " + + "WHERE out_trade_no = #{outTradeNo}") + int updatePaymentStatus(@Param("outTradeNo") String outTradeNo, + @Param("tradeState") String tradeState, + @Param("tradeStateDesc") String tradeStateDesc, + @Param("transactionId") String transactionId, + @Param("payTime") Date payTime, + @Param("updateTime") Date updateTime); + + /** + * 根据商户订单号更新为支付成功状态(微信支付) + */ + @Update("UPDATE zc_payment_record SET trade_state = 'SUCCESS', trade_state_desc = '支付成功', " + + "transaction_id = #{transactionId}, pay_time = #{payTime}, update_time = #{updateTime} " + + "WHERE out_trade_no = #{outTradeNo}") + int updateToSuccess(@Param("outTradeNo") String outTradeNo, + @Param("transactionId") String transactionId, + @Param("payTime") Date payTime, + @Param("updateTime") Date updateTime); + + /** + * 根据商户订单号更新为支付成功状态(支付宝) + */ + @Update("UPDATE zc_payment_record SET trade_state = 'SUCCESS', trade_state_desc = '支付成功', " + + "transaction_id = #{transactionId}, buyer_id = #{buyerId}, pay_time = #{payTime}, update_time = #{updateTime} " + + "WHERE out_trade_no = #{outTradeNo}") + int updateToSuccessForAlipay(@Param("outTradeNo") String outTradeNo, + @Param("transactionId") String transactionId, + @Param("buyerId") String buyerId, + @Param("payTime") Date payTime, + @Param("updateTime") Date updateTime); +} diff --git a/src/main/java/com/sczx/pay/mapper/RefundRecordMapper.java b/src/main/java/com/sczx/pay/mapper/RefundRecordMapper.java new file mode 100644 index 0000000..cf4c4f6 --- /dev/null +++ b/src/main/java/com/sczx/pay/mapper/RefundRecordMapper.java @@ -0,0 +1,39 @@ +package com.sczx.pay.mapper; + +import com.sczx.pay.entity.RefundRecord; +import org.apache.ibatis.annotations.*; + +import java.util.Date; + +@Mapper +public interface RefundRecordMapper { + + /** + * 插入退款记录 + */ + @Insert("INSERT INTO zc_refund_record (company_id, out_trade_no, out_refund_no, refund_id, total_fee, refund_fee, " + + "refund_status, refund_status_desc, refund_desc, create_time, update_time, refund_time, pay_channel) " + + "VALUES (#{companyId}, #{outTradeNo}, #{outRefundNo}, #{refundId}, #{totalFee}, #{refundFee}, " + + "#{refundStatus}, #{refundStatusDesc}, #{refundDesc}, #{createTime}, #{updateTime}, #{refundTime}, #{payChannel})") + @Options(useGeneratedKeys = true, keyProperty = "id") + int insertRefundRecord(RefundRecord refundRecord); + + /** + * 根据商户退款单号查询退款记录 + */ + @Select("SELECT * FROM zc_refund_record WHERE out_refund_no = #{outRefundNo} LIMIT 1") + RefundRecord getRefundRecordByOutRefundNo(@Param("outRefundNo") String outRefundNo); + + /** + * 根据商户退款单号更新退款状态 + */ + @Update("UPDATE zc_refund_record SET refund_status = #{refundStatus}, refund_status_desc = #{refundStatusDesc}, " + + "refund_id = #{refundId}, refund_time = #{refundTime}, update_time = #{updateTime} " + + "WHERE out_refund_no = #{outRefundNo}") + int updateRefundStatus(@Param("outRefundNo") String outRefundNo, + @Param("refundStatus") String refundStatus, + @Param("refundStatusDesc") String refundStatusDesc, + @Param("refundId") String refundId, + @Param("refundTime") Date refundTime, + @Param("updateTime") Date updateTime); +} diff --git a/src/main/java/com/sczx/pay/mapper/RentRuleItemMapper.java b/src/main/java/com/sczx/pay/mapper/RentRuleItemMapper.java new file mode 100644 index 0000000..461bd03 --- /dev/null +++ b/src/main/java/com/sczx/pay/mapper/RentRuleItemMapper.java @@ -0,0 +1,30 @@ +package com.sczx.pay.mapper; + +import com.sczx.pay.alipay.po.RentRuleItem; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import java.util.List; + +/** + * 车型租赁规则项数据访问层 + */ +@Mapper +public interface RentRuleItemMapper { + + RentRuleItem selectByAliId(@Param("aliItemId") String aliItemId); + + RentRuleItem selectByFourId(@Param("batteryRuleId") Long batteryRuleId, @Param("carRuleId") Long carRuleId, @Param("carModelId") Long carModelId,@Param("brandId") Long brandId); + + RentRuleItem selectItemByFourIds(@Param("batteryRuleId") Long batteryRuleId, @Param("carRuleId") Long carRuleId, @Param("carModelId") Long carModelId,@Param("brandId") Long brandId); + + RentRuleItem selectItemByOutItemId(@Param("outItemId") String outItemId); + + + int insertByFourId(RentRuleItem zcRentRuleItem); + + int updateItemIdByOutItemId(RentRuleItem rentRuleItem); + + int updateByOutItemId(@Param("outItemId") String outItemId, @Param("aliItemId") String aliItemId); + + +} diff --git a/src/main/java/com/sczx/pay/sdk/WXPay.java b/src/main/java/com/sczx/pay/sdk/WXPay.java new file mode 100644 index 0000000..db46bf3 --- /dev/null +++ b/src/main/java/com/sczx/pay/sdk/WXPay.java @@ -0,0 +1,315 @@ +package com.sczx.pay.sdk; + +import com.sczx.pay.utils.WXPayUtil; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContexts; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.security.KeyStore; +import java.util.Map; + +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; + +import javax.net.ssl.SSLContext; + + +/** + * 微信支付接口 + */ +public class WXPay { + + private static final Logger logger = LoggerFactory.getLogger(WXPay.class); + + private WXPayConfig config; + private boolean autoReport; + private boolean useSandbox; + private String baseUrl = "https://api.mch.weixin.qq.com"; + + public WXPay(WXPayConfig config) { + this(config, true, false); + } + + public WXPay(WXPayConfig config, boolean autoReport) { + this(config, autoReport, false); + } + + public WXPay(WXPayConfig config, boolean autoReport, boolean useSandbox) { + this.config = config; + this.autoReport = autoReport; + this.useSandbox = useSandbox; + if (useSandbox) { + this.baseUrl = "https://api.mch.weixin.qq.com/sandboxnew"; + } else { + this.baseUrl = "https://api.mch.weixin.qq.com"; + } + } + + /** + * 统一下单 + */ + public Map unifiedOrder(Map reqData) throws Exception { + return this.unifiedOrder(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); + } + + public Map unifiedOrder(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { + String url; + if (this.useSandbox) { + url = this.baseUrl + "/pay/unifiedorder"; + } else { + url = this.baseUrl + "/pay/unifiedorder"; + } + if (this.autoReport) { + // TODO: 添加自动上报逻辑 + } + String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); + return WXPayUtil.xmlToMap(respXml); + } + + /** + * 订单查询 + */ + public Map orderQuery(Map reqData) throws Exception { + return this.orderQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); + } + + public Map orderQuery(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { + String url; + if (this.useSandbox) { + url = this.baseUrl + "/pay/orderquery"; + } else { + url = this.baseUrl + "/pay/orderquery"; + } + String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); + return WXPayUtil.xmlToMap(respXml); + } + + /** + * 关闭订单 + */ + public Map closeOrder(Map reqData) throws Exception { + return this.closeOrder(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); + } + + public Map closeOrder(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { + String url; + if (this.useSandbox) { + url = this.baseUrl + "/pay/closeorder"; + } else { + url = this.baseUrl + "/pay/closeorder"; + } + String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); + return WXPayUtil.xmlToMap(respXml); + } + + /** + * 申请退款 + */ + public Map refund(Map reqData) throws Exception { + return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); + } + + public Map refund(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { + String url; + if (this.useSandbox) { + url = this.baseUrl + "/secapi/pay/refund"; + } else { + url = this.baseUrl + "/secapi/pay/refund"; + } + String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); + return WXPayUtil.xmlToMap(respXml); + } + + /** + * 退款查询 + */ + public Map refundQuery(Map reqData) throws Exception { + return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); + } + + public Map refundQuery(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { + String url; + if (this.useSandbox) { + url = this.baseUrl + "/pay/refundquery"; + } else { + url = this.baseUrl + "/pay/refundquery"; + } + String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); + return WXPayUtil.xmlToMap(respXml); + } + + /** + * 下载对账单 + */ + public String downloadBill(Map reqData) throws Exception { + return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); + } + + public String downloadBill(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { + String url; + if (this.useSandbox) { + url = this.baseUrl + "/pay/downloadbill"; + } else { + url = this.baseUrl + "/pay/downloadbill"; + } + return this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); + } + + /** + * 填充请求数据(生成签名) + */ + private Map fillRequestData(Map reqData) throws Exception { + reqData.put("appid", config.getAppID()); + reqData.put("mch_id", config.getMchID()); + if (!reqData.containsKey("nonce_str")) { + reqData.put("nonce_str", WXPayUtil.generateNonceStr()); + } + reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey())); + return reqData; + } + + /** + * 无证书请求 + */ + private String requestWithoutCert(String url, Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { + String xml = WXPayUtil.mapToXml(reqData); + logger.info("微信支付请求URL: {}, 请求数据: {}", url, xml); + + String response = httpRequest(url, xml, connectTimeoutMs, readTimeoutMs); + logger.info("微信支付响应数据: {}", response); + + return response; + } + + /** + * 有证书请求 + */ + private String requestWithCert(String url, Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { + String xml = WXPayUtil.mapToXml(reqData); + logger.info("微信支付请求URL: {}, 请求数据: {}", url, xml); + + String response = httpsRequestWithCert(url, xml, connectTimeoutMs, readTimeoutMs); + logger.info("微信支付响应数据: {}", response); + + return response; + } + + /** + * HTTP请求 + */ + private String httpRequest(String url, String requestData, int connectTimeoutMs, int readTimeoutMs) throws Exception { + java.net.HttpURLConnection connection = null; + try { + java.net.URL reqUrl = new java.net.URL(url); + connection = (java.net.HttpURLConnection) reqUrl.openConnection(); + + // 设置请求参数 + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setDoInput(true); + connection.setUseCaches(false); + connection.setRequestProperty("Content-Type", "application/xml;charset=UTF-8"); + connection.setConnectTimeout(connectTimeoutMs); + connection.setReadTimeout(readTimeoutMs); + + // 发送请求数据 + if (requestData != null) { + java.io.OutputStream outputStream = connection.getOutputStream(); + outputStream.write(requestData.getBytes("UTF-8")); + outputStream.close(); + } + + // 读取响应数据 + java.io.InputStream inputStream = connection.getInputStream(); + java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream, "UTF-8")); + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + inputStream.close(); + + return response.toString(); + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + /** + * 带证书的HTTPS请求 + */ + private String httpsRequestWithCert(String url, String requestData, int connectTimeoutMs, int readTimeoutMs) throws Exception { + // 加载证书 + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + FileInputStream instream = new FileInputStream(new File(this.config.getCertPath())); + try { + keyStore.load(instream, this.config.getMchID().toCharArray()); + } finally { + instream.close(); + } + + // 创建SSL上下文 + SSLContext sslcontext = SSLContexts.custom() + .loadKeyMaterial(keyStore, this.config.getMchID().toCharArray()) + .build(); + + // 创建SSL连接工厂 + SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( + sslcontext, + new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"}, + null, + (hostname, session) -> true); + + // 创建HTTP客户端 + CloseableHttpClient httpclient = HttpClients.custom() + .setSSLSocketFactory(sslsf) + .build(); + + try { + // 创建POST请求 + HttpPost httpost = new HttpPost(url); + httpost.addHeader("Content-Type", "application/xml;charset=UTF-8"); + httpost.setEntity(new StringEntity(requestData, "UTF-8")); + + // 执行请求 + CloseableHttpResponse response = httpclient.execute(httpost); + try { + HttpEntity entity = response.getEntity(); + String result = EntityUtils.toString(response.getEntity(), "UTF-8"); + EntityUtils.consume(entity); + return result; + } finally { + response.close(); + } + } finally { + httpclient.close(); + } + } + + + + /** + * 判断支付结果 + */ + public boolean isResponseSignatureValid(Map respData) { + try { + return WXPayUtil.isSignatureValid(respData, this.config.getKey()); + } catch (Exception e) { + logger.error("验证响应签名异常", e); + return false; + } + } + + +} diff --git a/src/main/java/com/sczx/pay/sdk/WXPayConfig.java b/src/main/java/com/sczx/pay/sdk/WXPayConfig.java new file mode 100644 index 0000000..95fc7b2 --- /dev/null +++ b/src/main/java/com/sczx/pay/sdk/WXPayConfig.java @@ -0,0 +1,54 @@ +package com.sczx.pay.sdk; + +import java.io.InputStream; + +/** + * 微信支付配置抽象类 + */ +public abstract class WXPayConfig { + + /** + * 获取 App ID + */ + public abstract String getAppID(); + + /** + * 获取商户号 + */ + public abstract String getMchID(); + + /** + * 获取 API 密钥 + */ + public abstract String getKey(); + + private String certPath; + + /** + * 获取证书内容 + */ + public abstract InputStream getCertStream(); + + /** + * HTTP(S) 连接超时时间,单位毫秒 + */ + public int getHttpConnectTimeoutMs() { + return 6*1000; + } + + /** + * HTTP(S) 读数据超时时间,单位毫秒 + */ + public int getHttpReadTimeoutMs() { + return 8*1000; + } + + + public String getCertPath() { + return certPath; + } + + public void setCertPath(String certPath) { + this.certPath = certPath; + } +} diff --git a/src/main/java/com/sczx/pay/service/AliPayOrderCloseService.java b/src/main/java/com/sczx/pay/service/AliPayOrderCloseService.java new file mode 100644 index 0000000..ca5258a --- /dev/null +++ b/src/main/java/com/sczx/pay/service/AliPayOrderCloseService.java @@ -0,0 +1,15 @@ +package com.sczx.pay.service; + +import com.alipay.api.domain.AlipayCommerceRentOrderCloseModel; +import com.alipay.api.response.AlipayCommerceRentOrderCloseResponse; + +public interface AliPayOrderCloseService { + + /** + * 支付宝订单关闭接口 + * @param model + * @return + */ + public AlipayCommerceRentOrderCloseResponse aliPayOrderClose(AlipayCommerceRentOrderCloseModel model); + +} diff --git a/src/main/java/com/sczx/pay/service/AliPayOrderFulfillmentService.java b/src/main/java/com/sczx/pay/service/AliPayOrderFulfillmentService.java new file mode 100644 index 0000000..dbc6096 --- /dev/null +++ b/src/main/java/com/sczx/pay/service/AliPayOrderFulfillmentService.java @@ -0,0 +1,25 @@ +package com.sczx.pay.service; + +import com.alipay.api.domain.AlipayCommerceRentOrderFulfillmentApproveModel; +import com.alipay.api.domain.AlipayCommerceRentOrderFulfillmentFinishModel; +import com.alipay.api.response.AlipayCommerceRentOrderFulfillmentApproveResponse; +import com.alipay.api.response.AlipayCommerceRentOrderFulfillmentFinishResponse; + +public interface AliPayOrderFulfillmentService { + + /** + * 支付宝订单履约审核 + * @param model + * @return + */ + public AlipayCommerceRentOrderFulfillmentApproveResponse aliPayOrderFulfillmentApprove(AlipayCommerceRentOrderFulfillmentApproveModel model); + + + /** + * 支付宝订单履约完成 + * @param model + * @return + */ + public AlipayCommerceRentOrderFulfillmentFinishResponse aliPayOrderFulfillmentFinish(AlipayCommerceRentOrderFulfillmentFinishModel model); + +} diff --git a/src/main/java/com/sczx/pay/service/AlipayRentService.java b/src/main/java/com/sczx/pay/service/AlipayRentService.java new file mode 100644 index 0000000..b04eeb8 --- /dev/null +++ b/src/main/java/com/sczx/pay/service/AlipayRentService.java @@ -0,0 +1,23 @@ +package com.sczx.pay.service; + +import com.sczx.pay.dto.ali.rent.AlipayRentPayReq; +import com.sczx.pay.dto.ali.rent.AlipayRentPayResponse; + +import java.util.Map; + +public interface AlipayRentService { + + /** + * 租赁订单支付 + * @param request + * @return + */ + AlipayRentPayResponse rentPay(AlipayRentPayReq request); + + /** + * 支付成功回调 + * @param notifyMap + * @return + */ + boolean processPaySuccessNotify(Map notifyMap); +} diff --git a/src/main/java/com/sczx/pay/service/AlipayService.java b/src/main/java/com/sczx/pay/service/AlipayService.java new file mode 100644 index 0000000..03cbff6 --- /dev/null +++ b/src/main/java/com/sczx/pay/service/AlipayService.java @@ -0,0 +1,47 @@ +package com.sczx.pay.service; + +import com.sczx.pay.dto.*; + +import java.util.Map; + +public interface AlipayService { + /** + * 支付宝统一下单 + */ + AlipayCreateResponse unifiedOrder(AlipayCreateRequest request); + + /** + * 查询订单 + */ + AlipayQueryResponse orderQuery(String outTradeNo); + + /** + * 关闭订单 + */ + AlipayResponse closeOrder(AlipayCloseRequest alipayCloseRequest); + + /** + * 申请退款 + */ + AlipayResponse refund(AlipayRefundRequest request); + + /** + * 查询退款 + */ + AlipayResponse refundQuery(AlipayRefundRequest refundQueryRequest); + + /** + * 验证通知签名 + */ + boolean verifyNotifySign(Long companyId, Map params); + + /** + * 处理支付成功通知 + */ + boolean processPaySuccessNotify(Long companyId, Map notifyMap); + + /** + * 处理退款通知 + */ + boolean processRefundNotify(Map notifyMap); +} diff --git a/src/main/java/com/sczx/pay/service/WechatPayService.java b/src/main/java/com/sczx/pay/service/WechatPayService.java new file mode 100644 index 0000000..170a822 --- /dev/null +++ b/src/main/java/com/sczx/pay/service/WechatPayService.java @@ -0,0 +1,564 @@ +package com.sczx.pay.service; + +import com.alibaba.fastjson.JSON; +import com.sczx.pay.config.DynamicWXPayConfig; +import com.sczx.pay.dto.PaymentRequest; +import com.sczx.pay.dto.PaymentResponse; +import com.sczx.pay.dto.RefundRequest; +import com.sczx.pay.entity.*; +import com.sczx.pay.mapper.CompanyWechatConfigMapper; +import com.sczx.pay.mapper.OrderPayMapper; +import com.sczx.pay.mapper.PaymentRecordMapper; +import com.sczx.pay.mapper.RefundRecordMapper; +import com.sczx.pay.sdk.WXPay; +import com.sczx.pay.utils.WXPayUtil; +import com.sczx.pay.utils.IPUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 微信支付服务类 + */ +@Service +public class WechatPayService { + + private static final Logger logger = LoggerFactory.getLogger(WechatPayService.class); + + // 全局公网IP变量 + private static String SERVER_PUBLIC_IP = "127.0.0.1"; + + @Autowired + private CompanyWechatConfigMapper companyWechatConfigMapper; + + @Autowired + private PaymentRecordMapper paymentRecordMapper; + + @Autowired + private RefundRecordMapper refundRecordMapper; + + @Autowired + private OrderPayMapper orderPayMapper; + + @Value("${wechat.pay.app-id}") + private String appId; + + @Value("${wechat.pay.notify-url}") + private String notifyUrl; + + @Value("${wechat.pay.refund-notify-url}") + private String refundNotifyUrl; + + @Value("${cert-path}") + private String certPath; + + /** + * 项目初始化时获取服务器公网IP + */ + @PostConstruct + public void initServerPublicIP() { + try { + SERVER_PUBLIC_IP = IPUtils.getServerPublicIP(); + logger.info("服务器公网IP初始化完成: {}", SERVER_PUBLIC_IP); + } catch (Exception e) { + logger.error("初始化服务器公网IP失败,使用默认IP: {}", SERVER_PUBLIC_IP, e); + } + } + + /** + * 获取服务器公网IP(静态方法) + */ + public static String getServerPublicIP() { + return SERVER_PUBLIC_IP; + } + + /** + * 小程序统一下单 + */ + @Transactional + public PaymentResponse unifiedOrder(PaymentRequest request) { + PaymentResponse response = new PaymentResponse(); + + try { + // 根据companyId获取微信支付配置 + CompanyWechatConfig companyConfig = companyWechatConfigMapper.getWechatConfigByCompanyId(request.getCompanyId()); + if (companyConfig == null) { + response.setCode("FAIL"); + response.setMessage("未找到公司对应的微信支付配置"); + return response; + } + + // 创建动态配置 + DynamicWXPayConfig wxPayConfig = new DynamicWXPayConfig( + appId, + companyConfig.getMchId(), + companyConfig.getApikey(), + notifyUrl + ); + + WXPay wxPay = new WXPay(wxPayConfig); + + // 构造请求参数 + Map reqData = new HashMap<>(); + reqData.put("body", request.getBody()); + reqData.put("out_trade_no", request.getOutTradeNo()); + reqData.put("total_fee", String.valueOf(request.getTotalFee())); + // 自动获取服务器公网IP + reqData.put("spbill_create_ip", getServerPublicIP()); + reqData.put("notify_url", notifyUrl); + reqData.put("trade_type", "JSAPI"); + reqData.put("openid", request.getOpenId()); + reqData.put("attach", request.getCompanyId().toString()); + + // 调用微信统一下单接口 + Map result = wxPay.unifiedOrder(reqData); + + logger.info("微信统一下单结果: {}", result); + + // 处理返回结果 + if ("SUCCESS".equals(result.get("return_code"))) { + if ("SUCCESS".equals(result.get("result_code"))) { + // 构造小程序支付参数 + Map payData = buildPayData(result.get("prepay_id"), wxPayConfig); + response.setCode("SUCCESS"); + response.setMessage("下单成功"); + response.setPayData(payData); + + // 记录支付信息到数据库 + recordPaymentInfo(request, companyConfig); + } else { + response.setCode("FAIL"); + response.setMessage(result.get("err_code_des")); + } + } else { + response.setCode("FAIL"); + response.setMessage(result.get("return_msg")); + } + + } catch (Exception e) { + logger.error("微信统一下单异常", e); + response.setCode("ERROR"); + response.setMessage("系统异常: " + e.getMessage()); + } + + return response; + } + + /** + * 记录支付信息到数据库 + */ + private void recordPaymentInfo(PaymentRequest request, CompanyWechatConfig companyConfig) { + try { + PaymentRecord paymentRecord = new PaymentRecord(); + paymentRecord.setCompanyId(request.getCompanyId()); + paymentRecord.setOutTradeNo(request.getOutTradeNo()); + paymentRecord.setTotalFee(new BigDecimal(request.getTotalFee()).divide(new BigDecimal(100))); // 转换为元 + paymentRecord.setBody(request.getBody()); + paymentRecord.setOpenid(request.getOpenId()); + paymentRecord.setTradeState("NOTPAY"); // 未支付 + paymentRecord.setTradeStateDesc("未支付"); + paymentRecord.setCreateTime(new Date()); + paymentRecord.setUpdateTime(new Date()); + paymentRecord.setAttach(request.getAttach()); + paymentRecord.setPayChannel(PaymentRecord.PayChannel.WECHAT.name()); // 设置支付渠道为微信支付 + + paymentRecordMapper.insertPaymentRecord(paymentRecord); + logger.info("支付记录已保存,订单号: {},内容为: {}",paymentRecord.getOutTradeNo(), JSON.toJSONString(paymentRecord)); + } catch (Exception e) { + logger.error("保存支付记录异常,订单号: {}", request.getOutTradeNo(), e); + } + } + + /** + * 查询订单 + */ + public Map orderQuery(Long companyId, String outTradeNo) throws Exception { + // 根据companyId获取微信支付配置 + CompanyWechatConfig companyConfig = companyWechatConfigMapper.getWechatConfigByCompanyId(companyId); + if (companyConfig == null) { + Map errorResult = new HashMap<>(); + errorResult.put("return_code", "FAIL"); + errorResult.put("return_msg", "未找到公司对应的微信支付配置"); + return errorResult; + } + + // 创建动态配置 + DynamicWXPayConfig wxPayConfig = new DynamicWXPayConfig( + appId, + companyConfig.getMchId(), + companyConfig.getApikey(), + notifyUrl + ); + + WXPay wxPay = new WXPay(wxPayConfig); + + Map reqData = new HashMap<>(); + reqData.put("out_trade_no", outTradeNo); + + return wxPay.orderQuery(reqData); + } + + /** + * 关闭订单 + */ + public Map closeOrder(Long companyId, String outTradeNo) throws Exception { + // 根据companyId获取微信支付配置 + CompanyWechatConfig companyConfig = companyWechatConfigMapper.getWechatConfigByCompanyId(companyId); + if (companyConfig == null) { + Map errorResult = new HashMap<>(); + errorResult.put("return_code", "FAIL"); + errorResult.put("return_msg", "未找到公司对应的微信支付配置"); + return errorResult; + } + + // 创建动态配置 + DynamicWXPayConfig wxPayConfig = new DynamicWXPayConfig( + appId, + companyConfig.getMchId(), + companyConfig.getApikey(), + notifyUrl + ); + + WXPay wxPay = new WXPay(wxPayConfig); + + Map reqData = new HashMap<>(); + reqData.put("out_trade_no", outTradeNo); + + return wxPay.closeOrder(reqData); + } + + /** + * 申请退款 + */ + @Transactional + public Map refund(RefundRequest request) throws Exception { + // 根据companyId获取微信支付配置 + CompanyWechatConfig companyConfig = companyWechatConfigMapper.getWechatConfigByCompanyId(request.getCompanyId()); + PaymentRecord paymentRecord = paymentRecordMapper.getPaymentRecordByOutTradeNo(request.getOutTradeNo()); + if (companyConfig == null) { + Map errorResult = new HashMap<>(); + errorResult.put("return_code", "FAIL"); + errorResult.put("return_msg", "未找到公司对应的微信支付配置"); + return errorResult; + } + + Map reqData = new HashMap<>(); + reqData.put("transaction_id",String.valueOf(paymentRecord.getTransactionId())); + reqData.put("out_trade_no", request.getOutTradeNo()); + reqData.put("out_refund_no", request.getOutRefundNo()); + reqData.put("total_fee", String.valueOf(paymentRecord.getTotalFee().setScale(2, RoundingMode.HALF_UP) .movePointRight(2) .intValueExact()));// 转换为分 + reqData.put("refund_fee", String.valueOf(request.getRefundFee())); + reqData.put("notify_url",refundNotifyUrl); + BigDecimal totalFee = paymentRecord.getTotalFee(); + + + // 创建动态配置 + DynamicWXPayConfig wxPayConfig = new DynamicWXPayConfig( + appId, + companyConfig.getMchId(), + companyConfig.getApikey(), + refundNotifyUrl + ); + + String path = certPath+companyConfig.getMchId()+".p12"; + String certAbsolutePath = getCertAbsolutePath(path); + wxPayConfig.setCertPath(certAbsolutePath); + + + + WXPay wxPay = new WXPay(wxPayConfig); + + // 退款需要证书,这里调用带证书的接口 + Map result = wxPay.refund(reqData); + + // 记录退款信息到数据库 + if ("SUCCESS".equals(result.get("return_code"))) { + recordRefundInfo(request, companyConfig, result,totalFee); + } + + return result; + } + + /** + * 记录退款信息到数据库 + */ + private void recordRefundInfo(RefundRequest request, CompanyWechatConfig companyConfig, Map result,BigDecimal totalFee) { + try { + RefundRecord refundRecord = new RefundRecord(); + refundRecord.setCompanyId(request.getCompanyId()); + refundRecord.setOutTradeNo(request.getOutTradeNo()); + refundRecord.setOutRefundNo(request.getOutRefundNo()); + refundRecord.setTotalFee(totalFee); + refundRecord.setRefundFee(new BigDecimal(request.getRefundFee()).divide(new BigDecimal(100))); // 转换为元 + refundRecord.setRefundDesc(request.getRefundDesc()); + refundRecord.setCreateTime(new Date()); + refundRecord.setUpdateTime(new Date()); + + if ("SUCCESS".equals(result.get("result_code"))) { + refundRecord.setRefundStatus("PROCESSING"); // 退款处理中 + refundRecord.setRefundStatusDesc("退款处理中"); + refundRecord.setRefundId(result.get("refund_id")); + } else { + refundRecord.setRefundStatus("FAIL"); // 退款失败 + refundRecord.setRefundStatusDesc(result.get("err_code_des")); + } + + refundRecordMapper.insertRefundRecord(refundRecord); + logger.info("退款记录已保存,退款单号: {}", request.getOutRefundNo()); + } catch (Exception e) { + logger.error("保存退款记录异常,退款单号: {}", request.getOutRefundNo(), e); + } + } + + /** + * 查询退款 + */ + public Map refundQuery(Long companyId, String outTradeNo) throws Exception { + // 根据companyId获取微信支付配置 + CompanyWechatConfig companyConfig = companyWechatConfigMapper.getWechatConfigByCompanyId(companyId); + if (companyConfig == null) { + Map errorResult = new HashMap<>(); + errorResult.put("return_code", "FAIL"); + errorResult.put("return_msg", "未找到公司对应的微信支付配置"); + return errorResult; + } + + // 创建动态配置 + DynamicWXPayConfig wxPayConfig = new DynamicWXPayConfig( + appId, + companyConfig.getMchId(), + companyConfig.getApikey(), + notifyUrl + ); + + WXPay wxPay = new WXPay(wxPayConfig); + + Map reqData = new HashMap<>(); + reqData.put("out_trade_no", outTradeNo); + + return wxPay.refundQuery(reqData); + } + + /** + * 构造小程序支付参数 + */ + private Map buildPayData(String prepayId, DynamicWXPayConfig wxPayConfig) throws Exception { + Map payData = new HashMap<>(); + payData.put("appId", wxPayConfig.getAppID()); + payData.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); + payData.put("nonceStr", WXPayUtil.generateNonceStr()); + payData.put("package", "prepay_id=" + prepayId); + payData.put("signType", "MD5"); + + // 生成签名 + String paySign = WXPayUtil.generateSignature(payData, wxPayConfig.getKey()); + payData.put("paySign", paySign); + + return payData; + } + + /** + * 验证微信支付通知签名 + */ + public boolean verifyNotifySign(Long companyId, Map notifyData) { + try { + // 根据companyId获取微信支付配置 + CompanyWechatConfig companyConfig = companyWechatConfigMapper.getWechatConfigByCompanyId(companyId); + if (companyConfig == null) { + return false; + } + + return WXPayUtil.isSignatureValid(notifyData, companyConfig.getApikey()); + } catch (Exception e) { + logger.error("验证微信支付通知签名异常", e); + return false; + } + } + + /** + * 处理支付成功通知并更新订单状态 + */ + @Transactional + public boolean processPaySuccessNotify(Long companyId, Map notifyData) { + try { + String outTradeNo = notifyData.get("out_trade_no"); + String transactionId = notifyData.get("transaction_id"); + String totalFee = notifyData.get("total_fee"); + + // 更新支付记录状态 + int updated = paymentRecordMapper.updateToSuccess( + outTradeNo, + transactionId, + new Date(), // 支付时间 + new Date() // 更新时间 + ); + + if (updated > 0) { + logger.info("微信支付记录状态已更新,订单号: {}, 微信交易号: {}", outTradeNo, transactionId); + //更新主订单状态 + OrderMain orderMain = orderPayMapper.getOrderStatusByOrderNo(outTradeNo); + + String OrderStatus = orderMain.getOrderStatus(); + + if(OrderStatus.equals("WAIT_PAY")){ + orderPayMapper.updateOrderStatus(orderMain.getOrderNo(),"WAIT_PICK"); + }else if (OrderStatus.equals("RERENT_WAIT_PAY")){ + OrderSub orderSub = orderPayMapper.getSubOrderByTradeNo(outTradeNo); + orderPayMapper.updateOrderStatusAndEndRentTime(orderMain.getOrderNo(),orderSub.getReturnTime(),0,orderMain.getOrderAmount().add(orderSub.getAmount()),"RENT_ING"); + } + orderPayMapper.updateSubOrderPaymentStatus(outTradeNo,"WX_PAY",PayStatus.SUCCESS.getCode(),transactionId); + + + return true; + } else { + logger.warn("未找到对应的微信支付记录,订单号: {}", outTradeNo); + return false; + } + } catch (Exception e) { + logger.error("处理微信支付成功通知异常,订单号: {}", notifyData.get("out_trade_no"), e); + return false; + } + } + + /** + * 处理退款通知并更新退款状态 + */ + @Transactional + public boolean processRefundNotify(Map notifyData) { + + try { + String outRefundNo = notifyData.get("out_refund_no"); + String refundId = notifyData.get("refund_id"); + String refundStatus = notifyData.get("refund_status"); + String outTradeNo = notifyData.get("out_trade_no"); + BigDecimal refundFee = new BigDecimal(notifyData.get("refund_fee")); + + // 根据退款状态更新退款记录 + String statusDesc = ""; + String payStatus = ""; + switch (refundStatus) { + case "SUCCESS": + statusDesc = "退款成功"; + payStatus = "REFUND_SUCCESS"; + break; + case "REFUNDCLOSE": + statusDesc = "退款关闭"; + payStatus = "REFUND_SUCCESS"; + break; + case "PROCESSING": + statusDesc = "退款处理中"; + payStatus = "REFUNDING"; + break; + case "CHANGE": + statusDesc = "退款异常"; + payStatus = "REFUND_ERROR"; + break; + default: + statusDesc = "未知状态"; + payStatus = "REFUND_ERROR"; + } + orderPayMapper.updateSubOrderRefundStatus(outTradeNo,payStatus,refundFee,new Date(),refundId); + int updated = refundRecordMapper.updateRefundStatus( + outRefundNo, + refundStatus, + statusDesc, + refundId, + "SUCCESS".equals(refundStatus) ? new Date() : null, // 退款成功时间 + new Date() // 更新时间 + ); + + if (updated > 0) { + logger.info("微信退款记录状态已更新,退款单号: {}, 微信退款单号: {}, 状态: {}", outRefundNo, refundId, refundStatus); + return true; + } else { + logger.warn("未找到对应的微信退款记录,退款单号: {}", outRefundNo); + return false; + } + } catch (Exception e) { + logger.error("处理微信退款通知异常,退款单号: {}", notifyData.get("out_refund_no"), e); + return false; + } + } + + + public Map decryptRefundNotify(String xmlData, String apiKey) throws Exception { + try { + // 解析XML数据 + Map notifyData = WXPayUtil.xmlToMap(xmlData); + + // 检查是否包含req_info字段 + if (!notifyData.containsKey("req_info")) { + logger.warn("微信退款通知中不包含req_info字段"); + return notifyData; + } + + // 获取加密的req_info + String reqInfo = notifyData.get("req_info"); + + // 解密req_info + String decryptedReqInfo = WXPayUtil.decryptReqInfo(reqInfo, apiKey); + + // 解析解密后的XML数据 + Map decryptedData = WXPayUtil.xmlToMap(decryptedReqInfo); + + // 将解密后的数据合并到原始通知数据中 + notifyData.putAll(decryptedData); + + return notifyData; + } catch (Exception e) { + logger.error("处理微信退款通知失败", e); + throw new Exception("处理微信退款通知失败: " + e.getMessage(), e); + } + } + + + /** + * 获取证书文件的绝对路径 + */ + private String getCertAbsolutePath(String certPath) { + try { + // 如果certPath是绝对路径,直接返回 + if (new File(certPath).isAbsolute()) { + return certPath; + } + + // 如果是相对路径,则从classpath下获取 + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + java.net.URL resource = classLoader.getResource(certPath); + + if (resource != null) { + return resource.getPath(); + } else { + // 如果在classpath中找不到,则尝试从当前类的classpath下获取 + resource = WechatPayService.class.getClassLoader().getResource(certPath); + if (resource != null) { + return resource.getPath(); + } else { + // 如果还找不到,则使用默认路径 + String defaultPath = "cert/1679965261.p12"; + resource = classLoader.getResource(defaultPath); + if (resource != null) { + return resource.getPath(); + } else { + throw new RuntimeException("无法找到证书文件: " + certPath); + } + } + } + } catch (Exception e) { + logger.error("获取证书路径失败", e); + throw new RuntimeException("获取证书路径失败: " + e.getMessage(), e); + } + } + +} diff --git a/src/main/java/com/sczx/pay/service/impl/AliPayOrderCloseServiceImpl.java b/src/main/java/com/sczx/pay/service/impl/AliPayOrderCloseServiceImpl.java new file mode 100644 index 0000000..7484e00 --- /dev/null +++ b/src/main/java/com/sczx/pay/service/impl/AliPayOrderCloseServiceImpl.java @@ -0,0 +1,66 @@ +package com.sczx.pay.service.impl; + +import com.alipay.api.domain.AlipayCommerceRentOrderCloseModel; +import com.alipay.api.request.AlipayCommerceRentOrderCloseRequest; +import com.alipay.api.response.AlipayCommerceRentOrderCloseResponse; +import com.sczx.pay.entity.OrderMain; +import com.sczx.pay.entity.PayStatus; +import com.sczx.pay.entity.ReasonCodes; +import com.sczx.pay.mapper.OrderPayMapper; +import com.sczx.pay.service.AliPayOrderCloseService; +import com.sczx.pay.utils.AlipaySdkUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class AliPayOrderCloseServiceImpl implements AliPayOrderCloseService { + + @Autowired + private AlipaySdkUtil alipaySdkUtil; + @Autowired + private OrderPayMapper orderPayMapper; + + /** + * 支付宝订单关闭接口 + * + * @param model + * @return + */ + @Override + public AlipayCommerceRentOrderCloseResponse aliPayOrderClose(AlipayCommerceRentOrderCloseModel model) { + AlipayCommerceRentOrderCloseResponse response = new AlipayCommerceRentOrderCloseResponse(); + try{ + // 构造请求参数以调用接口 + AlipayCommerceRentOrderCloseRequest request = new AlipayCommerceRentOrderCloseRequest(); + + // 设置关单原因编码 + model.setReasonCode(ReasonCodes.USER_QUALIFICATION_ISSUES.getCode()); + + // 设置关单原因说明 + model.setReasonDesc(ReasonCodes.USER_QUALIFICATION_ISSUES.getDescription()); + + request.setBizModel(model); + // 第三方代调用模式下请设置app_auth_token + // request.putOtherTextParam("app_auth_token", "<-- 请填写应用授权令牌 -->"); + + response = alipaySdkUtil.execute(request); + log.info("支付宝订单关闭接口响应 : {}",response.getBody()); + if (response.isSuccess()) { + log.info("支付宝订单关闭接口调用成功,开始更新订单状态"); + /** 更新订单状态 */ + /** 履约审核通过,更新订单状态为租赁中 */ + OrderMain orderMain = orderPayMapper.getOrderStatusByOrderNo(model.getOutOrderId()); + orderPayMapper.updateOrderStatus(orderMain.getOrderNo(), PayStatus.CLOSED.getCode()); + }else{ + log.error("支付宝订单关闭接口调用失败,OutOrderId: {}", model.getOutOrderId()); + } + } catch (Exception e) { + log.error("支付宝关闭异常,OutOrderId: {}", model.getOutOrderId(), e); + response.setCode("FAIL"); + response.setMsg("关闭订单失败异常: " + e.getMessage()); + } + return response; + } +} diff --git a/src/main/java/com/sczx/pay/service/impl/AliPayOrderFulfillmentServiceImpl.java b/src/main/java/com/sczx/pay/service/impl/AliPayOrderFulfillmentServiceImpl.java new file mode 100644 index 0000000..1e5d78b --- /dev/null +++ b/src/main/java/com/sczx/pay/service/impl/AliPayOrderFulfillmentServiceImpl.java @@ -0,0 +1,82 @@ +package com.sczx.pay.service.impl; + +import com.alipay.api.domain.AlipayCommerceRentOrderFulfillmentApproveModel; +import com.alipay.api.domain.AlipayCommerceRentOrderFulfillmentFinishModel; +import com.alipay.api.request.AlipayCommerceRentOrderFulfillmentApproveRequest; +import com.alipay.api.request.AlipayCommerceRentOrderFulfillmentFinishRequest; +import com.alipay.api.response.AlipayCommerceRentOrderFulfillmentApproveResponse; +import com.alipay.api.response.AlipayCommerceRentOrderFulfillmentFinishResponse; +import com.sczx.pay.entity.OrderMain; +import com.sczx.pay.entity.OrderSub; +import com.sczx.pay.entity.PayStatus; +import com.sczx.pay.mapper.OrderPayMapper; +import com.sczx.pay.service.AliPayOrderFulfillmentService; +import com.sczx.pay.utils.AlipaySdkUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class AliPayOrderFulfillmentServiceImpl implements AliPayOrderFulfillmentService { + + @Autowired + private AlipaySdkUtil alipaySdkUtil; + @Autowired + private OrderPayMapper orderPayMapper; + + public AlipayCommerceRentOrderFulfillmentApproveResponse aliPayOrderFulfillmentApprove(AlipayCommerceRentOrderFulfillmentApproveModel model) { + AlipayCommerceRentOrderFulfillmentApproveResponse response = new AlipayCommerceRentOrderFulfillmentApproveResponse(); + try{ + // 构造请求参数以调用接口 + AlipayCommerceRentOrderFulfillmentApproveRequest request = new AlipayCommerceRentOrderFulfillmentApproveRequest(); + + request.setBizModel(model); + + response = alipaySdkUtil.execute(request); + log.info("支付宝订单履约审核接口响应 : {}",response.getBody()); + if (response.isSuccess()) { + log.info("支付宝订单履约审核接口调用成功,更新订单状态为租赁中,订单号: {}", model.getOutOrderId()); + /** 履约审核通过,更新订单状态为租赁中 */ + OrderMain orderMain = orderPayMapper.getOrderStatusByOrderNo(model.getOutOrderId()); + orderPayMapper.updateOrderStatus(orderMain.getOrderNo(),"RENT_ING"); + } else { + log.error("支付宝订单履约审核接口调用失败,OutOrderId: {}", model.getOutOrderId()); + } + } catch (Exception e) { + log.error("支付宝订单履约审核异常,OutOrderId: {}", model.getOutOrderId(), e); + response.setCode("FAIL"); + response.setMsg("支付宝订单履约审核失败异常: " + e.getMessage()); + } + return response; + } + + @Override + public AlipayCommerceRentOrderFulfillmentFinishResponse aliPayOrderFulfillmentFinish(AlipayCommerceRentOrderFulfillmentFinishModel model) { + AlipayCommerceRentOrderFulfillmentFinishResponse response = new AlipayCommerceRentOrderFulfillmentFinishResponse(); + try{ + // 构造请求参数以调用接口 + AlipayCommerceRentOrderFulfillmentFinishRequest request = new AlipayCommerceRentOrderFulfillmentFinishRequest(); + + request.setBizModel(model); + + response = alipaySdkUtil.execute(request); + log.info("支付宝订单履约完成接口响应 : {}",response.getBody()); + if (response.isSuccess()) { + log.info("支付宝订单履约完成接口调用成功,更新订单状态为己结束-还车,订单号: {}", model.getOutOrderId()); + /** 支付宝订单履约完成通过,更新订单状态为己结束-还车 */ + //更新主订单状态 + OrderMain orderMain = orderPayMapper.getOrderStatusByOrderNo(model.getOutOrderId()); + orderPayMapper.updateOrderStatus(orderMain.getOrderNo(),"AUTO_END"); + } else { + log.error("支付宝订单履约完成接口调用失败,OutOrderId: {}", model.getOutOrderId()); + } + } catch (Exception e) { + log.error("支付宝订单履约完成异常,OutOrderId: {}", model.getOutOrderId(), e); + response.setCode("FAIL"); + response.setMsg("支付宝订单履约完成异常: " + e.getMessage()); + } + return response; + } + +} diff --git a/src/main/java/com/sczx/pay/service/impl/AlipayRentServiceImpl.java b/src/main/java/com/sczx/pay/service/impl/AlipayRentServiceImpl.java new file mode 100644 index 0000000..426616f --- /dev/null +++ b/src/main/java/com/sczx/pay/service/impl/AlipayRentServiceImpl.java @@ -0,0 +1,196 @@ +package com.sczx.pay.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.alipay.api.domain.AlipayCommerceRentOrderPayModel; +import com.alipay.api.domain.RentPayItemDTO; +import com.alipay.api.request.AlipayCommerceRentOrderPayRequest; +import com.alipay.api.response.AlipayCommerceRentOrderPayResponse; +import com.sczx.pay.config.AlipayConf; +import com.sczx.pay.dto.ali.rent.AlipayRentPayReq; +import com.sczx.pay.dto.ali.rent.AlipayRentPayResponse; +import com.sczx.pay.entity.OrderMain; +import com.sczx.pay.entity.OrderSub; +import com.sczx.pay.entity.PayStatus; +import com.sczx.pay.entity.PaymentRecord; +import com.sczx.pay.mapper.OrderPayMapper; +import com.sczx.pay.mapper.PaymentRecordMapper; +import com.sczx.pay.mapper.RefundRecordMapper; +import com.sczx.pay.service.AlipayRentService; +import com.sczx.pay.utils.AlipaySdkUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +public class AlipayRentServiceImpl implements AlipayRentService { + + @Autowired + private AlipayConf alipayConf; + + @Autowired + private AlipaySdkUtil alipaySdkUtil; + + @Value("${alipay.rent.payNotifyUrl}") + private String notifyUrl; + + @Autowired + private PaymentRecordMapper paymentRecordMapper; + + + @Autowired + private RefundRecordMapper refundRecordMapper; + + @Autowired + private OrderPayMapper orderPayMapper; + + @Override + public AlipayRentPayResponse rentPay(AlipayRentPayReq alipayRentPayReq) { + AlipayRentPayResponse response = new AlipayRentPayResponse(); + try { + // 构造请求参数以调用接口 + AlipayCommerceRentOrderPayRequest request = new AlipayCommerceRentOrderPayRequest(); + AlipayCommerceRentOrderPayModel model = new AlipayCommerceRentOrderPayModel(); + + // 设置租金支付外部请求号 + model.setOutTradeNo(alipayRentPayReq.getOutTradeNo()); + + // 设置业务订单号 + model.setOrderId(alipayRentPayReq.getOrderId()); + + // 设置支付方式,pay_method还可以选择:PRE_AUTH 预授权支付。部分支付场景不可使用租赁代扣 RENT_DEDUCT + model.setPayMethod("JSAPI"); + + // 设置支付金额 + model.setPayAmount(alipayRentPayReq.getPayAmount()); + + // 设置订单费用项明细列表 + List payItems = new ArrayList(); + RentPayItemDTO payItems0 = new RentPayItemDTO(); + payItems0.setType("RENT"); + payItems0.setPayAmount(alipayRentPayReq.getPayAmount()); + payItems.add(payItems0); + model.setPayItems(payItems); + + //二级商户信息 +// RentSubMerchantDTO rentSubMerchantDTO = new RentSubMerchantDTO(); +// rentSubMerchantDTO.setMerchantId(alipayConf.getCompanyAppid(alipayRentPayReq.getCompanyId())); +// model.setSubMerchant(rentSubMerchantDTO); + + // 设置支付结果通知地址 + model.setPayNotifyUrl(notifyUrl); + + // 设置支付超时时间 + model.setPayTimeoutExpress("15m"); + + request.setBizModel(model); + + // 第三方代调用模式下请设置app_auth_token + // request.putOtherTextParam("app_auth_token", "<-- 请填写应用授权令牌 -->"); + + + + // 第三方代调用模式下请设置app_auth_token + // request.putOtherTextParam("app_auth_token", "<-- 请填写应用授权令牌 -->"); + log.info("支付宝租赁订单下单请求:{}", JSONObject.toJSONString(request)); + AlipayCommerceRentOrderPayResponse rentOrderPayResponse = alipaySdkUtil.execute(request); + log.info("支付宝租赁订单下单下单返回:{}", JSONObject.toJSONString(rentOrderPayResponse)); + if (rentOrderPayResponse.isSuccess()) { + response.setSuccess(true); + response.setOutTradeNo(rentOrderPayResponse.getOutTradeNo()); + response.setTradeNo(rentOrderPayResponse.getTradeNo()); + response.setCode("SUCCESS"); + response.setMessage("下单成功"); + //保存支付单 + recordPaymentInfo(alipayRentPayReq); + + } else { + response.setSuccess(false); + response.setCode("FAIL"); + response.setMessage("下单失败: " + rentOrderPayResponse.getMsg() + ":" + rentOrderPayResponse.getSubMsg()); + } + } catch (Exception e) { + log.error("支付宝下单异常,公司ID: {}, 订单号: {}", alipayRentPayReq.getCompanyId(), alipayRentPayReq.getOutTradeNo(), e); + response.setSuccess(false); + response.setCode("FAIL"); + response.setMessage("下单异常: " + e.getMessage()); + } + + return response; + } + + @Override + public boolean processPaySuccessNotify(Map notifyMap) { + try { + // 这里实现您的业务逻辑 + // 例如:更新订单状态、发送通知等 + + String outTradeNo = notifyMap.get("out_trade_no"); + String tradeNo = notifyMap.get("trade_no"); + String totalAmount = notifyMap.get("total_amount"); + + log.info("处理支付宝租赁订单支付成功通知,订单号: {}, 交易号: {}, 金额: {}", + outTradeNo, tradeNo, totalAmount); + + // 更新支付记录状态 + int updated = paymentRecordMapper.updateToSuccess( + outTradeNo, + tradeNo, + new Date(), // 支付时间 + new Date() // 更新时间 + ); + if (updated > 0) { + log.info("支付宝支付记录状态已更新,订单号: {}, 支付宝交易号: {}", outTradeNo, tradeNo); + //更新主订单状态 + OrderMain orderMain = orderPayMapper.getOrderStatusByOrderNo(outTradeNo); + String OrderStatus = orderMain.getOrderStatus(); + + if(OrderStatus.equals("WAIT_PAY")){ + orderPayMapper.updateOrderStatus(orderMain.getOrderNo(),"WAIT_PICK"); + }else if (OrderStatus.equals("RERENT_WAIT_PAY")){ + OrderSub orderSub = orderPayMapper.getSubOrderByTradeNo(outTradeNo); + orderPayMapper.updateOrderStatusAndEndRentTime(orderMain.getOrderNo(),orderSub.getReturnTime(),0,orderMain.getOrderAmount().add(orderSub.getAmount()),"RENT_ING"); + } + orderPayMapper.updateSubOrderPaymentStatus(outTradeNo,"ZFB_PAY", PayStatus.SUCCESS.getCode(),tradeNo); + return true; + } else { + log.warn("未找到对应的支付宝支付记录,订单号: {}", outTradeNo); + return false; + } + } catch (Exception e) { + log.error("处理支付宝支付成功通知异常", e); + return false; + } + } + + /** + * 记录支付信息到数据库 + */ + private void recordPaymentInfo(AlipayRentPayReq alipayRentPayReq) { + try { + PaymentRecord paymentRecord = new PaymentRecord(); + paymentRecord.setCompanyId(alipayRentPayReq.getCompanyId()); + paymentRecord.setOutTradeNo(alipayRentPayReq.getOutTradeNo()); + paymentRecord.setTotalFee(new BigDecimal(alipayRentPayReq.getPayAmount())); // 转换为元 + paymentRecord.setTradeCompOrderId(alipayRentPayReq.getOrderId()); + paymentRecord.setTradeState("NOTPAY"); // 未支付 + paymentRecord.setTradeStateDesc("未支付"); + paymentRecord.setCreateTime(new Date()); + paymentRecord.setUpdateTime(new Date()); +// paymentRecord.setAttach(request.getAttach()); + paymentRecord.setPayChannel(PaymentRecord.PayChannel.ALIPAY_RENT.name()); // 设置支付渠道为支付宝支付 + + paymentRecordMapper.insertPaymentRecord(paymentRecord); + log.info("支付记录已保存,订单号: {}", alipayRentPayReq.getOutTradeNo()); + } catch (Exception e) { + log.error("保存支付记录异常,订单号: {}", alipayRentPayReq.getOutTradeNo(), e); + } + } +} diff --git a/src/main/java/com/sczx/pay/service/impl/AlipayServiceImpl.java b/src/main/java/com/sczx/pay/service/impl/AlipayServiceImpl.java new file mode 100644 index 0000000..bf11cae --- /dev/null +++ b/src/main/java/com/sczx/pay/service/impl/AlipayServiceImpl.java @@ -0,0 +1,469 @@ +package com.sczx.pay.service.impl; + + +import com.alibaba.fastjson.JSONObject; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.*; +import com.alipay.api.internal.util.AlipaySignature; +import com.alipay.api.request.*; +import com.alipay.api.response.*; +import com.sczx.pay.config.AlipayConf; +import com.sczx.pay.dto.*; +import com.sczx.pay.entity.*; +import com.sczx.pay.mapper.OrderPayMapper; +import com.sczx.pay.mapper.PaymentRecordMapper; +import com.sczx.pay.mapper.RefundRecordMapper; +import com.sczx.pay.service.AlipayService; +import com.sczx.pay.utils.AlipaySdkUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.Map; + +@Slf4j +@Service +public class AlipayServiceImpl implements AlipayService { + + @Autowired + private AlipayConf alipayConf; + + @Autowired + private AlipaySdkUtil alipaySdkUtil; + + @Value("${alipay.notify-url}") + private String notifyUrl; + + @Value("${alipay.refund-notify-url}") + private String refundNotifyUrl; + + @Value("${alipay.alipay-public-cert-path}") + private String alipayPublicCertPath; + + + @Autowired + private PaymentRecordMapper paymentRecordMapper; + + @Autowired + private RefundRecordMapper refundRecordMapper; + + @Autowired + private OrderPayMapper orderPayMapper; + + @Override + public AlipayCreateResponse unifiedOrder(AlipayCreateRequest alipayCreateRequest) { + AlipayCreateResponse response = new AlipayCreateResponse(); + try { +// AlipayClient alipayClient = alipayConf.alipayClient(); + + // 构造请求参数以调用接口 + AlipayTradeCreateRequest request = new AlipayTradeCreateRequest(); + AlipayTradeCreateModel model = new AlipayTradeCreateModel(); + + // 设置商户订单号 + model.setOutTradeNo(alipayCreateRequest.getOutTradeNo()); + + // 设置订单总金额 + model.setTotalAmount(alipayCreateRequest.getTotalAmount()); + + // 设置订单标题 + model.setSubject(alipayCreateRequest.getSubject()); + + // 设置订单相对超时时间 +// model.setTimeoutExpress("90m"); + + // uid参数未来计划废弃,存量商户可继续使用,新商户请使用openid。请根据应用-开发配置-openid配置选择支持的字段。 + // model.setBuyerId("2088102146225135"); + + // 设置买家支付宝用户唯一标识 + model.setBuyerOpenId(alipayCreateRequest.getOpenId()); + +// model.setOpAppId(alipayConf.getCompanyAppid(alipayCreateRequest.getCompanyId())); + + // 设置产品码 + model.setProductCode("JSAPI_PAY"); + + + request.setBizModel(model); + //设置回调地址 + request.setNotifyUrl(notifyUrl); + + // 第三方代调用模式下请设置app_auth_token + // request.putOtherTextParam("app_auth_token", "<-- 请填写应用授权令牌 -->"); + log.info("支付宝下单请求:{}", JSONObject.toJSONString(request)); + AlipayTradeCreateResponse alipayResponse = alipaySdkUtil.execute(request); + log.info("支付宝下单返回:{}", JSONObject.toJSONString(alipayResponse)); + if (alipayResponse.isSuccess()) { + response.setSuccess(true); + response.setOrderStr(alipayResponse.getBody()); + response.setOutTradeNo(alipayCreateRequest.getOutTradeNo()); + response.setTradeNo(alipayResponse.getTradeNo()); + response.setCode("SUCCESS"); + response.setMessage("下单成功"); + //保存支付单 + recordPaymentInfo(alipayCreateRequest); + + } else { + response.setSuccess(false); + response.setCode("FAIL"); + response.setMessage("下单失败: " + alipayResponse.getMsg() + ":" + alipayResponse.getSubMsg()); + } + } catch (Exception e) { + log.error("支付宝下单异常,公司ID: {}, 订单号: {}", alipayCreateRequest.getCompanyId(), alipayCreateRequest.getOutTradeNo(), e); + response.setSuccess(false); + response.setCode("FAIL"); + response.setMessage("下单异常: " + e.getMessage()); + } + + return response; + } + + @Override + public AlipayQueryResponse orderQuery(String outTradeNo) { + AlipayQueryResponse response = new AlipayQueryResponse(); + try { +// AlipayClient alipayClient = alipayConf.alipayClient(); + + // 构造请求参数以调用接口 + // 构造请求参数以调用接口 + AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); + AlipayTradeQueryModel model = new AlipayTradeQueryModel(); + + // 设置订单支付时传入的商户订单号 + model.setOutTradeNo(outTradeNo); + + request.setBizModel(model); + + // 第三方代调用模式下请设置app_auth_token + // request.putOtherTextParam("app_auth_token", "<-- 请填写应用授权令牌 -->"); + + AlipayTradeQueryResponse alipayResponse = alipaySdkUtil.execute(request); + + if (alipayResponse.isSuccess()) { + response.setSuccess(true); + response.setOutTradeNo(alipayResponse.getOutTradeNo()); + response.setTradeNo(alipayResponse.getTradeNo()); + response.setTradeStatus(alipayResponse.getTradeStatus()); + response.setCode("SUCCESS"); + response.setMessage("查询支付单成功"); + } else { + response.setSuccess(false); + response.setCode("FAIL"); + response.setMessage("查询支付单失败: " + alipayResponse.getMsg() + ":" + alipayResponse.getSubMsg()); + } + } catch (Exception e) { + log.error("支付宝订单查询异常,订单号: {}", outTradeNo, e); + response.setSuccess(false); + response.setCode("FAIL"); + response.setMessage("支付宝订单查询败异常: " + e.getMessage()); + } + return response; + } + + @Override + public AlipayResponse closeOrder(AlipayCloseRequest alipayCloseRequest) { + AlipayResponse response = new AlipayResponse(); + try { +// AlipayClient alipayClient = alipayConf.alipayClient(); + // 构造请求参数以调用接口 + AlipayTradeCloseRequest request = new AlipayTradeCloseRequest(); + AlipayTradeCloseModel model = new AlipayTradeCloseModel(); + + + // 设置订单支付时传入的商户订单号 + model.setOutTradeNo(alipayCloseRequest.getOutTradeNo()); + + request.setBizModel(model); + // 第三方代调用模式下请设置app_auth_token + // request.putOtherTextParam("app_auth_token", "<-- 请填写应用授权令牌 -->"); + + AlipayTradeCloseResponse closeResponse = alipaySdkUtil.execute(request); + log.info("关闭订单响应 : {}",closeResponse.getBody()); + + if (closeResponse.isSuccess()) { + response.setSuccess(true); + response.setCode("SUCCESS"); + response.setMessage("关闭支付单成功"); + } else { + response.setSuccess(false); + response.setCode("FAIL"); + response.setMessage("关闭支付单失败: " + closeResponse.getMsg() + ":" + closeResponse.getSubMsg()); + } + } catch (Exception e) { + log.error("支付宝关闭异常,公司ID: {}, 订单号: {}", alipayCloseRequest.getCompanyId(), alipayCloseRequest.getOutTradeNo(), e); + response.setSuccess(false); + response.setCode("FAIL"); + response.setMessage("关闭支付单失败异常: " + e.getMessage()); + } + return response; + } + + @Override + public AlipayResponse refund(AlipayRefundRequest request) { + AlipayResponse response = new AlipayResponse(); + try { +// AlipayClient alipayClient = alipayConf.alipayClient(); + // 构造请求参数以调用接口 + AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest(); + AlipayTradeRefundModel model = new AlipayTradeRefundModel(); + + // 设置商户订单号 + model.setOutTradeNo(request.getOutTradeNo()); +// +// // 设置支付宝交易号 +// model.setTradeNo("2014112611001004680073956707"); + + // 设置退款金额 + model.setRefundAmount(request.getRefundAmount()); + + // 设置退款原因说明 + model.setRefundReason(request.getRefundReason()); + + // 设置退款请求号 + model.setOutRequestNo(request.getOutRequestNo()); + refundRequest.setBizModel(model); + refundRequest.setNotifyUrl(refundNotifyUrl); + + AlipayTradeRefundResponse refundResponse = alipaySdkUtil.execute(refundRequest); + log.info("退款响应 : {}",refundResponse.getBody()); + + if (refundResponse.isSuccess()) { + response.setSuccess(true); + response.setCode("SUCCESS"); + response.setMessage("发起退款成功"); + //保存退款信息 + recordRefundInfo(request); + } else { + response.setSuccess(false); + response.setCode("FAIL"); + response.setMessage("退款失败: " + refundResponse.getMsg() + ":" + refundResponse.getSubMsg()); + } + } catch (Exception e) { + log.error("支付宝退款异常,公司ID: {}, 订单号: {}", request.getCompanyId(), request.getOutTradeNo(), e); + response.setSuccess(false); + response.setCode("FAIL"); + response.setMessage("退款失败异常: " + e.getMessage()); + } + return response; + } + + @Override + public AlipayResponse refundQuery(AlipayRefundRequest refundQueryRequest) { + AlipayResponse response = new AlipayResponse(); + try { +// AlipayClient alipayClient = alipayConf.alipayClient(); + // 构造请求参数以调用接口 + AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest(); + AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel(); + + // 设置商户订单号 + model.setOutTradeNo(refundQueryRequest.getOutTradeNo()); + + // 设置退款请求号 + model.setOutRequestNo(refundQueryRequest.getOutRequestNo()); + + + request.setBizModel(model); + // 第三方代调用模式下请设置app_auth_token + // request.putOtherTextParam("app_auth_token", "<-- 请填写应用授权令牌 -->"); + + AlipayTradeFastpayRefundQueryResponse refundQueryResponse = alipaySdkUtil.execute(request); + log.info("退款查询响应 : {}",refundQueryResponse.getBody()); + + if (refundQueryResponse.isSuccess() && "REFUND_SUCCESS".equals(refundQueryResponse.getRefundStatus())) { + response.setSuccess(true); + response.setCode("SUCCESS"); + response.setMessage("退款成功"); + } else { + response.setSuccess(false); + response.setCode("FAIL"); + response.setMessage("退款查询失败: " + refundQueryResponse.getMsg() + ":" + refundQueryResponse.getSubMsg()); + } + } catch (Exception e) { + log.error("支付宝退款异常,公司ID: {}, 订单号: {}", refundQueryRequest.getCompanyId(), refundQueryRequest.getOutTradeNo(), e); + response.setSuccess(false); + response.setCode("FAIL"); + response.setMessage("支付宝退款失败异常: " + e.getMessage()); + } + return response; + } + + @Override + public boolean verifyNotifySign(Long companyId, Map params) { + try { + return AlipaySignature.rsaCertCheckV1(params, alipayPublicCertPath, "UTF-8", "RSA2"); + } catch (AlipayApiException e) { + log.error("支付宝通知签名验证异常,公司ID: {}", companyId, e); + return false; + } + } + + @Override + public boolean processPaySuccessNotify(Long companyId, Map notifyMap) { + try { + // 这里实现您的业务逻辑 + // 例如:更新订单状态、发送通知等 + + String outTradeNo = notifyMap.get("out_trade_no"); + String tradeNo = notifyMap.get("trade_no"); + String totalAmount = notifyMap.get("total_amount"); + + log.info("处理支付宝支付成功通知,公司ID: {}, 订单号: {}, 交易号: {}, 金额: {}", + companyId, outTradeNo, tradeNo, totalAmount); + + // 更新支付记录状态 + int updated = paymentRecordMapper.updateToSuccess( + outTradeNo, + tradeNo, + new Date(), // 支付时间 + new Date() // 更新时间 + ); + if (updated > 0) { + log.info("支付宝支付记录状态已更新,订单号: {}, 支付宝交易号: {}", outTradeNo, tradeNo); + //更新主订单状态 + OrderMain orderMain = orderPayMapper.getOrderStatusByOrderNo(outTradeNo); + String OrderStatus = orderMain.getOrderStatus(); + + if(OrderStatus.equals("WAIT_PAY")){ + orderPayMapper.updateOrderStatus(orderMain.getOrderNo(),"WAIT_PICK"); + }else if (OrderStatus.equals("RERENT_WAIT_PAY")){ + OrderSub orderSub = orderPayMapper.getSubOrderByTradeNo(outTradeNo); + orderPayMapper.updateOrderStatusAndEndRentTime(orderMain.getOrderNo(),orderSub.getReturnTime(),0,orderMain.getOrderAmount().add(orderSub.getAmount()),"RENT_ING"); + } + orderPayMapper.updateSubOrderPaymentStatus(outTradeNo,"ZFB_PAY", PayStatus.SUCCESS.getCode(),tradeNo); + return true; + } else { + log.warn("未找到对应的支付宝支付记录,订单号: {}", outTradeNo); + return false; + } + } catch (Exception e) { + log.error("处理支付宝支付成功通知异常,公司ID: {}", companyId, e); + return false; + } + } + + @Override + public boolean processRefundNotify(Map notifyMap) { + try { + String outTradeNo = notifyMap.get("out_trade_no"); + String outRequestNo = notifyMap.get("out_request_no"); + String refundAmount = notifyMap.get("refund_amount"); + String refundStatus = notifyMap.get("refund_status"); + + log.info("处理支付宝退款通知,订单号: {}, 退款请求号: {}, 退款金额: {}", + outTradeNo, outRequestNo, refundAmount); +// +// +// String outRefundNo = notifyData.get("out_refund_no"); +// String refundId = notifyData.get("refund_id"); +// String refundStatus = notifyData.get("refund_status"); +// String outTradeNo = notifyData.get("out_trade_no"); +// BigDecimal refundFee = new BigDecimal(notifyData.get("refund_fee")); + + // 根据退款状态更新退款记录 + String statusDesc = ""; + String payStatus = ""; + statusDesc = "退款成功"; + payStatus = "REFUND_SUCCESS"; +// switch (refundStatus) { +// case "SUCCESS": +// statusDesc = "退款成功"; +// payStatus = "REFUND_SUCCESS"; +// break; +// case "REFUNDCLOSE": +// statusDesc = "退款关闭"; +// payStatus = "REFUND_SUCCESS"; +// break; +// case "PROCESSING": +// statusDesc = "退款处理中"; +// payStatus = "REFUNDING"; +// break; +// case "CHANGE": +// statusDesc = "退款异常"; +// payStatus = "REFUND_ERROR"; +// break; +// default: +// statusDesc = "未知状态"; +// payStatus = "REFUND_ERROR"; +// } + orderPayMapper.updateSubOrderRefundStatus(outTradeNo,payStatus,new BigDecimal(refundAmount),new Date(),outRequestNo); + int updated = refundRecordMapper.updateRefundStatus( + outRequestNo, + refundStatus, + statusDesc, + outRequestNo, + "SUCCESS".equals(refundStatus) ? new Date() : null, // 退款成功时间 + new Date() // 更新时间 + ); + + if (updated > 0) { + log.info("支付宝退款记录状态已更新,退款单号: {}, 支付宝退款单号: {}, 状态: {}", outRequestNo, outRequestNo, refundStatus); + return true; + } else { + log.warn("未找到对应的微信退款记录,退款单号: {}", outRequestNo); + return false; + } + } catch (Exception e) { + log.error("处理微信退款通知异常,退款单号: {}", notifyMap.get("out_request_no"), e); + return false; + } + } + + /** + * 记录支付信息到数据库 + */ + private void recordPaymentInfo(AlipayCreateRequest request) { + try { + PaymentRecord paymentRecord = new PaymentRecord(); + paymentRecord.setCompanyId(request.getCompanyId()); + paymentRecord.setOutTradeNo(request.getOutTradeNo()); + paymentRecord.setTotalFee(new BigDecimal(request.getTotalAmount())); // 转换为元 + paymentRecord.setBody(request.getBody()); + paymentRecord.setOpenid(request.getOpenId()); + paymentRecord.setTradeState("NOTPAY"); // 未支付 + paymentRecord.setTradeStateDesc("未支付"); + paymentRecord.setCreateTime(new Date()); + paymentRecord.setUpdateTime(new Date()); +// paymentRecord.setAttach(request.getAttach()); + paymentRecord.setPayChannel(PaymentRecord.PayChannel.ALIPAY.name()); // 设置支付渠道为支付宝支付 + + paymentRecordMapper.insertPaymentRecord(paymentRecord); + log.info("支付记录已保存,订单号: {}", request.getOutTradeNo()); + } catch (Exception e) { + log.error("保存支付记录异常,订单号: {}", request.getOutTradeNo(), e); + } + } + + /** + * 记录退款信息到数据库 + */ + private void recordRefundInfo(AlipayRefundRequest request) { + try { + PaymentRecord paymentRecord = paymentRecordMapper.getPaymentRecordByOutTradeNo(request.getOutTradeNo()); + + RefundRecord refundRecord = new RefundRecord(); + refundRecord.setCompanyId(request.getCompanyId()); + refundRecord.setOutTradeNo(request.getOutTradeNo()); + refundRecord.setOutRefundNo(request.getOutRequestNo()); + refundRecord.setTotalFee(paymentRecord.getTotalFee()); + refundRecord.setRefundFee(new BigDecimal(request.getRefundAmount())); // 转换为元 + refundRecord.setRefundDesc(request.getRefundReason()); + refundRecord.setCreateTime(new Date()); + refundRecord.setUpdateTime(new Date()); + + refundRecord.setRefundStatus("PROCESSING"); // 退款处理中 + refundRecord.setRefundStatusDesc("退款处理中"); + refundRecord.setRefundId(request.getOutRequestNo()); + refundRecord.setPayChannel(PaymentRecord.PayChannel.ALIPAY.name()); + + + refundRecordMapper.insertRefundRecord(refundRecord); + log.info("退款记录已保存,退款单号: {}", request.getOutRequestNo()); + } catch (Exception e) { + log.error("保存退款记录异常,退款单号: {}", request.getOutRequestNo(), e); + } + } +} diff --git a/src/main/java/com/sczx/pay/thirdpart/dto/req/CarDTO.java b/src/main/java/com/sczx/pay/thirdpart/dto/req/CarDTO.java new file mode 100644 index 0000000..3ba3b4c --- /dev/null +++ b/src/main/java/com/sczx/pay/thirdpart/dto/req/CarDTO.java @@ -0,0 +1,132 @@ +package com.sczx.pay.thirdpart.dto.req; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + *

+ * 电动车信息表 + *

+ * + * @author zhangli + * @since 2025-07-30 17:05:03 + */ + +@Data +@ApiModel(value = "CarDTO对象", description = "电动车信息表") +public class CarDTO { + + @ApiModelProperty("主键ID") + private Long id; + + @ApiModelProperty("车架号(VIN)") + private String vin; + + @ApiModelProperty("车牌号码") + private String licensePlate; + + @ApiModelProperty("车辆品牌ID") + private Long brandId; + + @ApiModelProperty("车辆品牌名称") + private String brandName; + + @ApiModelProperty("车辆型号ID") + private Long modelId; + + @ApiModelProperty("车辆型号名称") + private String modelName; + + @ApiModelProperty("支持电池类型(48V标准版/100km,48V超长版/200km等)") + private String batteryType; + + @ApiModelProperty("整车重量(kg)") + private String weight; + + @ApiModelProperty("最高时速(km/h)") + private String maxSpeed; + + @ApiModelProperty("LOT识别号") + private String lotNumber; + + @ApiModelProperty("采购日期") + private LocalDateTime purchaseDate; + + @ApiModelProperty("采购价格(元)") + private BigDecimal purchasePrice; + + @ApiModelProperty("车辆归属(0归属于合,1归属运营商)") + private String belongType; + + @ApiModelProperty("车辆图片(多个图片用逗号分隔)") + private String images; + + @ApiModelProperty("BRS车辆状态(空闲/使用中/维修中/丢失报损等)") + private String brsStatus; + + @ApiModelProperty("IoT设备状态") + private String iotStatus; + + @ApiModelProperty("IoT识别码") + private String iotCode; + + @ApiModelProperty("所属运营商ID") + private Long operatorId; + + @ApiModelProperty("所属运营商名称") + private String operatorName; + + @ApiModelProperty("所属门店ID") + private Long storeId; + + @ApiModelProperty("所属门店名称") + private String storeName; + + @ApiModelProperty("应用套餐ID") + private Long packageId; + + @ApiModelProperty("应用套餐名称") + private String packageName; + + @ApiModelProperty("状态(0正常 1停用)") + private String status; + + @ApiModelProperty("删除标志(0代表存在 2代表删除)") + private String delFlag; + + @ApiModelProperty("创建者") + private String createBy; + + @ApiModelProperty("创建时间") + private LocalDateTime createTime; + + @ApiModelProperty("更新者") + private String updateBy; + + @ApiModelProperty("更新时间") + private LocalDateTime updateTime; + + @ApiModelProperty("备注信息") + private String remark; + + @ApiModelProperty("扩展字段1") + private String extend1; + + @ApiModelProperty("扩展字段2") + private String extend2; + + @ApiModelProperty("扩展字段3") + private String extend3; + + @ApiModelProperty("扩展字段4") + private String extend4; + + @ApiModelProperty("扩展字段5") + private String extend5; + + +} diff --git a/src/main/java/com/sczx/pay/thirdpart/dto/req/CarModelSimpleDTO.java b/src/main/java/com/sczx/pay/thirdpart/dto/req/CarModelSimpleDTO.java new file mode 100644 index 0000000..6597806 --- /dev/null +++ b/src/main/java/com/sczx/pay/thirdpart/dto/req/CarModelSimpleDTO.java @@ -0,0 +1,40 @@ +package com.sczx.pay.thirdpart.dto.req; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "车型简单对象") +public class CarModelSimpleDTO { + + @ApiModelProperty("车型ID") + private Long carModelId; + + @ApiModelProperty("门店id") + private Integer storeId; + + @ApiModelProperty("门店编号") + private String storeNumber; + + @ApiModelProperty("车型名称") + private String modelName; + + @ApiModelProperty("品牌名称") + private String brandName; + + @ApiModelProperty("租赁类型(时租/日租/按天数/以租代售),用逗号分隔") + private String batteryTypes; + + @ApiModelProperty("是否支持免押(0不支持 1支持)") + private String depositFree; + + @ApiModelProperty("是否支持代扣(0不支持 1支持)") + private String autoDeduct; + + @ApiModelProperty("电池类型") + private String batteryType; + + @ApiModelProperty("车型照片") + private String image; +} diff --git a/src/main/java/com/sczx/pay/thirdpart/dto/req/CompanyStoreDTO.java b/src/main/java/com/sczx/pay/thirdpart/dto/req/CompanyStoreDTO.java new file mode 100644 index 0000000..935e7cb --- /dev/null +++ b/src/main/java/com/sczx/pay/thirdpart/dto/req/CompanyStoreDTO.java @@ -0,0 +1,68 @@ +package com.sczx.pay.thirdpart.dto.req; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@ApiModel(value = "CompanyStoreDTO对象", description = "门店信息") +public class CompanyStoreDTO { + + @ApiModelProperty("主键id") + private Integer id; + + @ApiModelProperty("门店名称") + private String name; + + @ApiModelProperty("联系人") + private String contactPerson; + + @ApiModelProperty("手机号码") + private String phone; + + @ApiModelProperty("联系人2") + private String contactPerson2; + + @ApiModelProperty("手机号码2") + private String phone2; + + + @ApiModelProperty("详细地址") + private String detailedAddress; + + @ApiModelProperty("门店logo") + private String image; + + @ApiModelProperty("纬度") + private Double latitude; + + @ApiModelProperty("经度") + private Double longitude; + + @ApiModelProperty("标签 1.可租车 2.可换电 3.二手车,多个用,号隔开,例如1,2,3") + private String label; + + @ApiModelProperty("运营公司id") + private Integer operatingCompanyId; + + @ApiModelProperty("运营性质 1.直营 0.合作") + private Boolean operatingNature; + + @ApiModelProperty("简介") + private String introduction; + + + @ApiModelProperty("门店编号") + private String storeNumber; + + @ApiModelProperty("租车分成比例") + private BigDecimal zucheRatio; + + @ApiModelProperty("租电分成比例") + private BigDecimal zudianRatio; + + @ApiModelProperty("以租代售分成比例") + private BigDecimal daishouRatio; +} diff --git a/src/main/java/com/sczx/pay/thirdpart/dto/req/OrderCarImgDTO.java b/src/main/java/com/sczx/pay/thirdpart/dto/req/OrderCarImgDTO.java new file mode 100644 index 0000000..cc12db3 --- /dev/null +++ b/src/main/java/com/sczx/pay/thirdpart/dto/req/OrderCarImgDTO.java @@ -0,0 +1,16 @@ +package com.sczx.pay.thirdpart.dto.req; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@ApiModel(value = "OrderCarImgDTO对象", description = "订单租车车辆图片信息") +@Data +public class OrderCarImgDTO { + + @ApiModelProperty("图片类型:RENT租车,RETURN还车") + private String imgType; + + @ApiModelProperty("图片地址") + private String imgUrl; +} diff --git a/src/main/java/com/sczx/pay/thirdpart/dto/req/OrderDTO.java b/src/main/java/com/sczx/pay/thirdpart/dto/req/OrderDTO.java new file mode 100644 index 0000000..c5b2ff2 --- /dev/null +++ b/src/main/java/com/sczx/pay/thirdpart/dto/req/OrderDTO.java @@ -0,0 +1,144 @@ +package com.sczx.pay.thirdpart.dto.req; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * @Author: 张黎 + * @Date: 2025/07/25/17:18 + * @Description: + */ +@Data +@ApiModel(value = "订单dto", description = "租车订单信息") +public class OrderDTO { + + @ApiModelProperty("订单ID(主键)") + private Long orderId; + + @ApiModelProperty("订单编号") + private String orderNo; + + @ApiModelProperty("订单状态:下单未提车、租赁中、已结束-自动、已结束-手动") + private String orderStatus; + + @ApiModelProperty("所属运营商ID") + private Long operatorId; + + @ApiModelProperty("门店信息") + private CompanyStoreDTO companyStoreDTO; + + @ApiModelProperty("所属门店ID") + private Long storeId; + + @ApiModelProperty("车辆ID") + private Long vehicleId; + + @ApiModelProperty("车辆信息") + private CarDTO carDTO; + + @ApiModelProperty("车型ID") + private Long carModelId; + + @ApiModelProperty("车型信息") + private CarModelSimpleDTO carModelSimpleDTO; + + @ApiModelProperty("客户id") + private Long customerId; + + @ApiModelProperty("客户姓名") + private String customerName; + + @ApiModelProperty("客户联系电话") + private String customerPhone; + + @ApiModelProperty("选择的电池类型") + private String batteryType; + + @ApiModelProperty("租赁类型(时租/日租/按天数/以租代售)") + private String rentalType; + + @ApiModelProperty("租赁天数(当类型为\"按天数\"时使用)") + private Integer rentalDays; + + @ApiModelProperty("租车价格(元)") + private BigDecimal rentalPrice; + + @ApiModelProperty("押金价格(元)") + private BigDecimal depositPrice; + + @ApiModelProperty("逾期金额(元)") + private BigDecimal overdueFee; + + @ApiModelProperty("逾期计费类型(按日计费/按月计费)") + private String overdueType; + + @ApiModelProperty("是否开通免押") + private Boolean isDepositFree; + + @ApiModelProperty("是否开通代扣") + private Boolean isAutoDeduct; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("首次下单时间") + private LocalDateTime firstOrderTime; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("开始计费时间") + private LocalDateTime startRentTime; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("还车时间") + private LocalDateTime endRentTime; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("申请还车时间") + private LocalDateTime reqEndRentTime; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("实际还车时间") + private LocalDateTime actEndRentTime; + + @ApiModelProperty("逾期天数") + private Integer overdueDays; + + @ApiModelProperty("续租次数") + private Integer renewalTimes; + + @ApiModelProperty("充电次数") + private Integer chargeTimes; + + @ApiModelProperty("租车套餐id") + private Long rentCarRuleId; + + @ApiModelProperty("租电套餐id") + private Long rentBatteyRuleId; + + @ApiModelProperty("订单结束时间") + private LocalDateTime endOrderTime; + + @ApiModelProperty("订单总金额") + private BigDecimal orderAmount; + + @ApiModelProperty("租电套餐信息") + private RentBatteyRuleDTO rentBatteyRule; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("创建时间") + private LocalDateTime createTime; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("更新时间") + private LocalDateTime updateTime; + + @ApiModelProperty("租车子订单信息") + private List orderSubDTOList; + + @ApiModelProperty("订单车辆图片") + private List orderCarImgList; +} diff --git a/src/main/java/com/sczx/pay/thirdpart/dto/req/OrderDetailDTO.java b/src/main/java/com/sczx/pay/thirdpart/dto/req/OrderDetailDTO.java new file mode 100644 index 0000000..72db2c3 --- /dev/null +++ b/src/main/java/com/sczx/pay/thirdpart/dto/req/OrderDetailDTO.java @@ -0,0 +1,176 @@ +package com.sczx.pay.thirdpart.dto.req; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@ApiModel(value = "订单详情dto", description = "租车订单详情信息") +@Data +public class OrderDetailDTO { + + @ApiModelProperty("订单ID(主键)") + private Long orderId; + + @ApiModelProperty("订单编号") + private String orderNo; + + @ApiModelProperty("订单状态:下单未提车、租赁中、已结束-自动、已结束-手动") + private String orderStatus; + + @ApiModelProperty("门店名称") + private String storeName; + + @ApiModelProperty("详细地址") + private String detailedAddress; + + @ApiModelProperty("纬度") + private Double latitude; + + @ApiModelProperty("经度") + private Double longitude; + + @ApiModelProperty("车型名称") + private String modelName; + + @ApiModelProperty("品牌名称") + private String brandName; + + @ApiModelProperty("车型图片") + private String image; + + @ApiModelProperty("车辆id") + private String carId; + + @ApiModelProperty("车架号(VIN)") + private String vin; + + @ApiModelProperty("车牌号码") + private String licensePlate; + + @ApiModelProperty("客户id") + private Long customerId; + + @ApiModelProperty("客户姓名") + private String customerName; + + @ApiModelProperty("客户联系电话") + private String customerPhone; + + + @ApiModelProperty("租赁类型(时租/日租/按天数/以租代售)") + private String rentalType; + + @ApiModelProperty("租赁类型标签") + private String rentalTypeLabel; + + @ApiModelProperty("租赁天数(当类型为\"按天数\"时使用)") + private Integer rentalDays; + + @ApiModelProperty("租车价格(元)") + private BigDecimal rentalPrice; + + @ApiModelProperty("逾期金额(元)") + private BigDecimal overdueFee; + + @ApiModelProperty("逾期计费类型(按日计费/按月计费)") + private String overdueType; + + @ApiModelProperty("押金价格(元)") + private BigDecimal depositPrice; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("首次下单时间") + private LocalDateTime firstOrderTime; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("取车时间") + private LocalDateTime pickCarTime; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("开始计费时间") + private LocalDateTime startRentTime; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("预计还车时间") + private LocalDateTime endRentTime; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("申请还车时间") + private LocalDateTime reqEndRentTime; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("实际还车时间") + private LocalDateTime actEndRentTime; + + @ApiModelProperty("电池订单号") + private String rentBatteyOrderNo; + + @ApiModelProperty("电池类型名称") + private String categoryName; + + @ApiModelProperty("电池编码") + private String batteyNo; + + @ApiModelProperty("计时方式") + private Integer durationType; + + @ApiModelProperty("电池计时方式标签") + private String durationTypeLabel; + + @ApiModelProperty("租点套餐名称") + private String rentBatteyTitle; + + @ApiModelProperty("逾期天数") + private Integer overdueDays; + + @ApiModelProperty("逾期金额") + private BigDecimal overdueAmount; + + @ApiModelProperty("续租金额") + private BigDecimal rerentAmount; + + @ApiModelProperty("续租次数") + private Integer renewalTimes; + + @ApiModelProperty("充电次数") + private Integer chargeTimes; + + @ApiModelProperty("订单总金额") + private BigDecimal orderAmount; + + @ApiModelProperty("是否开通免押") + private Boolean isDepositFree; + + @ApiModelProperty("是否开通代扣") + private Boolean isAutoDeduct; + + + @ApiModelProperty("车损金额") + private BigDecimal damageAmount; + + @ApiModelProperty("车损说明") + private String damageDesc; + + @ApiModelProperty("预计到期间隔天数") + private Integer expectedDays; + + @ApiModelProperty("订单车辆图片-租车") + private List orderCarImgRentList; + + @ApiModelProperty("订单车辆图片-还车") + private List orderCarImgReturnList; + + @ApiModelProperty("订单车辆图片-车损") + private List orderCarImgDamageList; + + @ApiModelProperty("最新支付单号") + private String lastPayOrderNo; + + @ApiModelProperty("支付订单信息") + private List payOrderDTOList; +} diff --git a/src/main/java/com/sczx/pay/thirdpart/dto/req/OrderSubDTO.java b/src/main/java/com/sczx/pay/thirdpart/dto/req/OrderSubDTO.java new file mode 100644 index 0000000..81b276a --- /dev/null +++ b/src/main/java/com/sczx/pay/thirdpart/dto/req/OrderSubDTO.java @@ -0,0 +1,49 @@ +package com.sczx.pay.thirdpart.dto.req; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@ApiModel(value = "租车订单子表dto", description = "租车订单子表信息") +public class OrderSubDTO { + + @ApiModelProperty("子订单ID(主键)") + private Long suborderId; + + @ApiModelProperty("关联的订单ID(外键)") + private Long orderId; + + @ApiModelProperty("子订单编号") + private String suborderNo; + + @ApiModelProperty("子订单类型(首租、续租、逾期、押金、租电等)") + private String suborderType; + + @ApiModelProperty("订单金额") + private BigDecimal amount; + + @ApiModelProperty("支付方式") + private String paymentMethod; + + @ApiModelProperty("车架/电池编号") + private String vinBatteryNo; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("订单产生时间") + private LocalDateTime createdAt; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("支付ID") + private String paymentId; + + @ApiModelProperty("实际支付时间") + private LocalDateTime paidAt; + + @ApiModelProperty("备注") + private String remark; +} diff --git a/src/main/java/com/sczx/pay/thirdpart/dto/req/PayOrderDTO.java b/src/main/java/com/sczx/pay/thirdpart/dto/req/PayOrderDTO.java new file mode 100644 index 0000000..243b82f --- /dev/null +++ b/src/main/java/com/sczx/pay/thirdpart/dto/req/PayOrderDTO.java @@ -0,0 +1,30 @@ +package com.sczx.pay.thirdpart.dto.req; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@ApiModel(value = "支付订单dto", description = "支付订单信息") +public class PayOrderDTO { + + @ApiModelProperty("订单金额") + private BigDecimal amount; + + @ApiModelProperty("支付方式") + private String paymentMethod; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty("订单产生时间") + private LocalDateTime createdAt; + + @ApiModelProperty("支付ID") + private String paymentId; + + @ApiModelProperty("支付状态") + private String payStatus; +} diff --git a/src/main/java/com/sczx/pay/thirdpart/dto/req/RentBatteyRuleDTO.java b/src/main/java/com/sczx/pay/thirdpart/dto/req/RentBatteyRuleDTO.java new file mode 100644 index 0000000..28ffbd5 --- /dev/null +++ b/src/main/java/com/sczx/pay/thirdpart/dto/req/RentBatteyRuleDTO.java @@ -0,0 +1,91 @@ +package com.sczx.pay.thirdpart.dto.req; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; + +@ApiModel(value = "租电套餐计费规则") +@Data +public class RentBatteyRuleDTO { + + @ApiModelProperty("租电套餐id") + private Integer id; + + @ApiModelProperty("商品标题") + private String title; + + @ApiModelProperty("商品描述") + private String detail; + + @ApiModelProperty("图标") + private String icon; + + @ApiModelProperty("电压") + private String voltage; + + @ApiModelProperty("电容") + private String ah; + + @ApiModelProperty("最大里程") + private Integer maxMileage; + + @ApiModelProperty("最小里程 默认0") + private Integer minMileage; + + @ApiModelProperty("押金 ") + private BigDecimal depositPrice; + + @ApiModelProperty("租金") + private BigDecimal rentPrice; + + @ApiModelProperty("计时方式") + private Integer durationType; + + @ApiModelProperty("租赁时长") + private Integer duration; + + @ApiModelProperty("盗抢险") + private BigDecimal insurancePrice; + + @ApiModelProperty("是否强制投保 默认0 false") + private Boolean compulsoryInsurance; + + @ApiModelProperty("保险有效时长 默认12个月") + private Integer insuranceDuration; + + @ApiModelProperty("换电次数") + private Integer changeNum; + + @ApiModelProperty("1、有限次数 2无限次数") + private Integer changeType; + + private Boolean isDelete; + + private Integer cityId; + + private Integer operatorId; + + private Integer provinceId; + + @ApiModelProperty("电池类型") + private Integer categoryId; + + @ApiModelProperty("电池类型名称") + private String categoryName; + + @ApiModelProperty("套餐类型 换电/租电") + private Integer mealType; + + @ApiModelProperty("套餐排序规则") + private Integer mealSort; + + private Boolean isJoinInvite; + + @ApiModelProperty("套餐渠道租车默认 id号待定") + private Integer mealChannel; + + @ApiModelProperty("购买限制类型") + private Integer buyLimitType; +} diff --git a/src/main/java/com/sczx/pay/thirdpart/facade/OrderFacade.java b/src/main/java/com/sczx/pay/thirdpart/facade/OrderFacade.java new file mode 100644 index 0000000..032c89d --- /dev/null +++ b/src/main/java/com/sczx/pay/thirdpart/facade/OrderFacade.java @@ -0,0 +1,18 @@ +package com.sczx.pay.thirdpart.facade; + + +import com.sczx.pay.common.Result; +import com.sczx.pay.thirdpart.dto.req.CarModelSimpleDTO; +import com.sczx.pay.thirdpart.dto.req.OrderDetailDTO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +@FeignClient(name = "sczx-order", url = "${sczx-order.ribbon.listOfServers:}") +public interface OrderFacade { + + + @GetMapping("/pubOrder/getOrderDetailByOrderNo") + Result getOrderDetailByOrderNo(@RequestParam("orderNo") String orderNo); + + +} \ No newline at end of file diff --git a/src/main/java/com/sczx/pay/thirdpart/integration/OrderInteg.java b/src/main/java/com/sczx/pay/thirdpart/integration/OrderInteg.java new file mode 100644 index 0000000..dc9d04c --- /dev/null +++ b/src/main/java/com/sczx/pay/thirdpart/integration/OrderInteg.java @@ -0,0 +1,35 @@ +package com.sczx.pay.thirdpart.integration; + + +import com.sczx.pay.common.Result; +import com.sczx.pay.exception.InnerException; +import com.sczx.pay.thirdpart.dto.req.OrderDetailDTO; +import com.sczx.pay.thirdpart.facade.OrderFacade; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class OrderInteg { + + @Autowired + private OrderFacade orderFacade; + + + public OrderDetailDTO getOrderDetailByOrderNo(String orderNo){ + try{ + Result result = orderFacade.getOrderDetailByOrderNo(orderNo); + if(result.isSuccess()){ + return result.getData(); + } + } catch (Exception e){ + log.error("根据租车套餐id获取套餐信息失败",e); + throw new InnerException("根据租车套餐id获取套餐信息失败"); + } + return null; + } + + + +} diff --git a/src/main/java/com/sczx/pay/utils/AlipayApiCallback.java b/src/main/java/com/sczx/pay/utils/AlipayApiCallback.java new file mode 100644 index 0000000..43c1089 --- /dev/null +++ b/src/main/java/com/sczx/pay/utils/AlipayApiCallback.java @@ -0,0 +1,21 @@ +/** + * Alipay.com Inc. + * Copyright (c) 2004-2024 All Rights Reserved. + */ +package com.sczx.pay.utils; + +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayResponse; + +/** + * @author jishupei.jsp + * @version : AlipayApiCallback, v0.1 2024年03月27日 5:46 下午 jishupei.jsp Exp $ + */ +public interface AlipayApiCallback { + + R process() throws AlipayApiException; + + T getData(R response); + + String getApiName(); +} diff --git a/src/main/java/com/sczx/pay/utils/AlipayApiTemplate.java b/src/main/java/com/sczx/pay/utils/AlipayApiTemplate.java new file mode 100644 index 0000000..e6d1b62 --- /dev/null +++ b/src/main/java/com/sczx/pay/utils/AlipayApiTemplate.java @@ -0,0 +1,50 @@ +/** + * Alipay.com Inc. + * Copyright (c) 2004-2024 All Rights Reserved. + */ +package com.sczx.pay.utils; + +import com.alibaba.fastjson.JSON; +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayResponse; +import com.sczx.pay.alipay.vo.OpenResponse; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * @author jishupei.jsp + * @version : AlipayApiTemplate, v0.1 2024年03月27日 5:48 下午 jishupei.jsp Exp $ + */ +public class AlipayApiTemplate { + + private static final Logger logger = LogManager.getLogger(AlipayApiTemplate.class); + + public static OpenResponse execute(AlipayApiCallback callback) { + try { + //执行 + R response = callback.process(); + OpenResponse tOpenResponse = new OpenResponse<>(response); + if (response.isSuccess()) { + //获取data + T data = callback.getData(response); + tOpenResponse.setData(data); + logger.info(callback.getApiName() + "调用成功:" + response.getBody()); + } else { + logger.error(callback.getApiName() + "调用失败:" + JSON.toJSONString(response)); + } + return tOpenResponse; + } catch (AlipayApiException e) { + //异常处理 + logger.error(callback.getApiName() + "调用失败", e); + return new OpenResponse<>(e); + } catch (Exception e) { + //异常处理 + logger.error(callback.getApiName() + "调用失败", e); + OpenResponse response = new OpenResponse<>(); + response.setCode("SYSTEM_ERROR"); + response.setMsg("系统错误"); + response.setSubMsg(e.getMessage()); + return response; + } + } +} diff --git a/src/main/java/com/sczx/pay/utils/AlipaySdkUtil.java b/src/main/java/com/sczx/pay/utils/AlipaySdkUtil.java new file mode 100644 index 0000000..439b884 --- /dev/null +++ b/src/main/java/com/sczx/pay/utils/AlipaySdkUtil.java @@ -0,0 +1,242 @@ +package com.sczx.pay.utils; + +import com.alipay.api.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.HashMap; +import java.util.Map; + +@Component +@Configuration +public class AlipaySdkUtil { + + private AlipayClient alipayClient; + + @Value("${alipay.appid}") + private String appId; + + // 应用私钥 + @Value("${alipay.privatekey}") + private String privateKey; + + // 商户ID(alipay.pid) + @Value("${alipay.pid}") + private String pid; + + // 是否使用OpenId(alipay.use_open_id) + @Value("${alipay.use_open_id}") + private boolean useOpenId; + + // 使用OpenId时必填(alipay.openid) + @Value("${alipay.openid}") + private String openid; + + // 用户ID,和OpenId二选一(alipay.userid) + @Value("${alipay.userid}") + private String userid; + + // 是否是服务商(alipay.is_isv) + @Value("${alipay.is_isv}") + private boolean isIsv; + + // 服务商必填(alipay.app_auth_token) + @Value("${alipay.app_auth_token}") + private String appAuthToken; + + @Value("${alipay.alipay-public-cert-path}") + private String alipayPublicCertPath; + + @Value("${alipay.ali-public-cert-path}") + private String aliPublicCertPath; + + @Value("${alipay.alipay-root-cert-path}") + private String alipayRootCertPath; + + @PostConstruct + public void init() throws AlipayApiException { + // 初始化v2 SDK + this.alipayClient = new DefaultAlipayClient(getAlipayConfig()); + } + + public T execute(AlipayRequest request) throws AlipayApiException { + if (isIsv) { + return alipayClient.certificateExecute(request, null, appAuthToken); + } + return alipayClient.certificateExecute(request); + } + + private AlipayConfig getAlipayConfig() { + AlipayConfig alipayConfig = new AlipayConfig(); + alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do"); + alipayConfig.setAppId(appId); + alipayConfig.setPrivateKey(privateKey); + //设置应用公钥证书路径 + alipayConfig.setAppCertPath(alipayPublicCertPath); + //设置支付宝公钥证书路径 + alipayConfig.setAlipayPublicCertPath(aliPublicCertPath); + //设置支付宝根证书路径 + alipayConfig.setRootCertPath(alipayRootCertPath); + alipayConfig.setFormat("json"); + alipayConfig.setCharset("UTF-8"); + alipayConfig.setSignType("RSA2"); + Map headers = new HashMap<>(); + headers.put("alipay-sdk-demo", "app-item-0.0.1"); + alipayConfig.setCustomHeaders(headers); + + + + return alipayConfig; + + } + + + + /** + * Getter method for property pid. + * + * @return property value of pid + */ + public String getPid() { + return pid; + } + + /** + * Setter method for property pid. + * + * @param pid value to be assigned to property pid + */ + public void setPid(String pid) { + this.pid = pid; + } + + /** + * Getter method for property appId. + * + * @return property value of appId + */ + public String getAppId() { + return appId; + } + + /** + * Setter method for property appId. + * + * @param appId value to be assigned to property appId + */ + public void setAppId(String appId) { + this.appId = appId; + } + + /** + * Getter method for property openid. + * + * @return property value of openid + */ + public String getOpenid() { + return openid; + } + + /** + * Setter method for property openid. + * + * @param openid value to be assigned to property openid + */ + public void setOpenid(String openid) { + this.openid = openid; + } + + /** + * Getter method for property userid. + * + * @return property value of userid + */ + public String getUserid() { + return userid; + } + + /** + * Setter method for property userid. + * + * @param userid value to be assigned to property userid + */ + public void setUserid(String userid) { + this.userid = userid; + } + + /** + * Getter method for property useOpenId. + * + * @return property value of useOpenId + */ + public boolean isUseOpenId() { + return useOpenId; + } + + /** + * Setter method for property useOpenId. + * + * @param useOpenId value to be assigned to property useOpenId + */ + public void setUseOpenId(boolean useOpenId) { + this.useOpenId = useOpenId; + } + + /** + * Getter method for property isIsv. + * + * @return property value of isIsv + */ + public boolean isv() { + return isIsv; + } + + /** + * Setter method for property isIsv. + * + * @param isv value to be assigned to property isIsv + */ + public void setIsv(boolean isv) { + isIsv = isv; + } + + /** + * Getter method for property appAuthToken. + * + * @return property value of appAuthToken + */ + public String getAppAuthToken() { + return appAuthToken; + } + + /** + * Setter method for property appAuthToken. + * + * @param appAuthToken value to be assigned to property appAuthToken + */ + public void setAppAuthToken(String appAuthToken) { + this.appAuthToken = appAuthToken; + } + + /** + * Getter method for property privateKey. + * + * @return property value of privateKey + */ + public String getPrivateKey() { + return privateKey; + } + + /** + * Setter method for property privateKey. + * + * @param privateKey value to be assigned to property privateKey + */ + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + + +} diff --git a/src/main/java/com/sczx/pay/utils/ComputerInfo.java b/src/main/java/com/sczx/pay/utils/ComputerInfo.java new file mode 100644 index 0000000..e8afc29 --- /dev/null +++ b/src/main/java/com/sczx/pay/utils/ComputerInfo.java @@ -0,0 +1,159 @@ +package com.sczx.pay.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/* + * <取网卡物理地址-- + * 1.在Windows,Linux系统下均可用; + * 2.通过ipconifg,ifconfig获得计算机信息; + * 3.再用模式匹配方式查找MAC地址,与操作系统的语言无关> + * + * //* Description: <取计算机名--从环境变量中取> + * abstract 限制继承/创建实例 + */ +public abstract class ComputerInfo { + private static String macAddressStr = null; + private static String computerName = System.getenv().get("COMPUTERNAME"); + + private static final String[] windowsCommand = { "ipconfig", "/all" }; + private static final String[] linuxCommand = { "/sbin/ifconfig", "-a" }; + private static final String[] macCommand = { "ifconfig", "-a" }; + private static final Pattern macPattern = Pattern.compile(".*((:?[0-9a-f]{2}[-:]){5}[0-9a-f]{2}).*", + Pattern.CASE_INSENSITIVE); + + /** + * 获取多个网卡地址 + * + * @return + * @throws IOException + */ + private final static List getMacAddressList() throws IOException { + final ArrayList macAddressList = new ArrayList(); + final String os = System.getProperty("os.name"); + final String command[]; + + if (os.startsWith("Windows")) { + command = windowsCommand; + } else if (os.startsWith("Linux")) { + command = linuxCommand; + } else if (os.startsWith("Mac")){ + command = macCommand; + } + else { + throw new IOException("Unknow operating system:" + os); + } + // 执行命令 + final Process process = Runtime.getRuntime().exec(command); + + BufferedReader bufReader = new BufferedReader(new InputStreamReader(process.getInputStream())); + for (String line = null; (line = bufReader.readLine()) != null;) { + Matcher matcher = macPattern.matcher(line); + if (matcher.matches()) { + macAddressList.add(matcher.group(1)); + // macAddressList.add(matcher.group(1).replaceAll("[-:]", + // ""));//去掉MAC中的“-” + } + } + + process.destroy(); + bufReader.close(); + return macAddressList; + } + + /** + * 获取一个网卡地址(多个网卡时从中获取一个) + * + * @return + */ + public static String getMacAddress() { + if (macAddressStr == null || macAddressStr.equals("")) { + StringBuffer sb = new StringBuffer(); // 存放多个网卡地址用,目前只取一个非0000000000E0隧道的值 + try { + List macList = getMacAddressList(); + for (Iterator iter = macList.iterator(); iter.hasNext();) { + String amac = iter.next(); + if (!"0000000000E0".equals(amac)) { + sb.append(amac); + break; + } + } + } catch (IOException e) { + e.printStackTrace(); + } + + macAddressStr = sb.toString(); + + } + + return macAddressStr; + } + + /** + * 获取电脑名 + * + * @return + */ + public static String getComputerName() { + if (computerName == null || computerName.equals("")) { + computerName = System.getenv().get("COMPUTERNAME"); + } + return computerName; + } + + /** + * 获取客户端IP地址 + * + * @return + */ + public static String getIpAddrAndName() throws IOException { + return InetAddress.getLocalHost().toString(); + } + + /** + * 获取客户端IP地址 + * + * @return + */ + public static String getIpAddr() throws IOException { + return InetAddress.getLocalHost().getHostAddress().toString(); + } + + /** + * 获取电脑唯一标识 + * + * @return + */ + public static String getComputerID() { + String id = getMacAddress(); + if (id == null || id.equals("")) { + try { + id = getIpAddrAndName(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return computerName; + } + + /** + * 限制创建实例 + */ + private ComputerInfo() { + + } + + public static void main(String[] args) throws IOException { + System.out.println(ComputerInfo.getMacAddress()); + System.out.println(ComputerInfo.getComputerName()); + System.out.println(ComputerInfo.getIpAddr()); + System.out.println(ComputerInfo.getIpAddrAndName()); + } +} diff --git a/src/main/java/com/sczx/pay/utils/IPUtils.java b/src/main/java/com/sczx/pay/utils/IPUtils.java new file mode 100644 index 0000000..441b901 --- /dev/null +++ b/src/main/java/com/sczx/pay/utils/IPUtils.java @@ -0,0 +1,135 @@ +package com.sczx.pay.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.URL; +import java.net.UnknownHostException; + +/** + * IP地址工具类 + */ +public class IPUtils { + + private static final Logger logger = LoggerFactory.getLogger(IPUtils.class); + + // 多个IP查询服务,以防某个服务不可用 + private static final String[] IP_SERVICES = { + "https://api.ipify.org", + "https://icanhazip.com", + "https://ident.me" + }; + + private static String cachedPublicIP = null; + private static long lastFetchTime = 0; + private static final long CACHE_DURATION = 30 * 60 * 1000; // 30分钟缓存 + + /** + * 获取服务器公网IP地址 + * @return 公网IP地址 + */ + public static String getServerPublicIP() { + // 检查缓存 + if (cachedPublicIP != null && (System.currentTimeMillis() - lastFetchTime) < CACHE_DURATION) { + return cachedPublicIP; + } + + // 首先尝试从外部服务获取公网IP + String publicIP = fetchPublicIPFromExternalService(); + if (publicIP != null && !publicIP.isEmpty()) { + cachedPublicIP = publicIP; + lastFetchTime = System.currentTimeMillis(); + return publicIP; + } + + // 如果外部服务不可用,则获取本地IP + try { + String localIP = InetAddress.getLocalHost().getHostAddress(); + logger.warn("无法获取公网IP,使用本地IP: {}", localIP); + return localIP; + } catch (UnknownHostException e) { + logger.error("获取本地IP失败", e); + return "127.0.0.1"; + } + } + + /** + * 从外部服务获取公网IP + * @return 公网IP地址 + */ + private static String fetchPublicIPFromExternalService() { + for (String serviceUrl : IP_SERVICES) { + try { + String ip = fetchIPFromService(serviceUrl); + if (isValidIP(ip)) { + logger.info("成功从 {} 获取公网IP: {}", serviceUrl, ip); + return ip; + } + } catch (Exception e) { + logger.warn("从服务 {} 获取IP失败: {}", serviceUrl, e.getMessage()); + } + } + return null; + } + + /** + * 从指定服务获取IP + * @param serviceUrl 服务URL + * @return IP地址 + * @throws Exception 网络异常 + */ + private static String fetchIPFromService(String serviceUrl) throws Exception { + URL url = new URL(serviceUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(connection.getInputStream()))) { + return reader.readLine().trim(); + } finally { + connection.disconnect(); + } + } + + /** + * 验证IP地址是否有效 + * @param ip IP地址 + * @return 是否有效 + */ + private static boolean isValidIP(String ip) { + if (ip == null || ip.isEmpty()) { + return false; + } + + // 简单的IP格式验证 + String[] parts = ip.split("\\."); + if (parts.length != 4) { + return false; + } + + for (String part : parts) { + try { + int num = Integer.parseInt(part); + if (num < 0 || num > 255) { + return false; + } + } catch (NumberFormatException e) { + return false; + } + } + + // 排除私有IP和回环地址 + if (ip.startsWith("127.") || ip.startsWith("10.") || + ip.startsWith("192.168.") || ip.startsWith("172.")) { + // 私有地址也可以接受,因为可能在内网环境中使用 + return true; + } + + return true; + } +} diff --git a/src/main/java/com/sczx/pay/utils/WXPayUtil.java b/src/main/java/com/sczx/pay/utils/WXPayUtil.java new file mode 100644 index 0000000..6434741 --- /dev/null +++ b/src/main/java/com/sczx/pay/utils/WXPayUtil.java @@ -0,0 +1,243 @@ +package com.sczx.pay.utils; + +import org.apache.commons.codec.digest.DigestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.ByteArrayInputStream; +import java.io.StringWriter; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.*; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; +import java.io.StringReader; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.DigestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.crypto.spec.IvParameterSpec; +import java.security.Security; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.InvalidAlgorithmParameterException; + +public class WXPayUtil { + + private static final Logger logger = LoggerFactory.getLogger(WXPayUtil.class); + private static final String CHARSET = "UTF-8"; + private static final SecureRandom random = new SecureRandom(); + + /** + * 生成随机字符串 + */ + public static String generateNonceStr() { + return UUID.randomUUID().toString().replace("-", "").substring(0, 32); + } + + /** + * 生成签名 + */ + public static String generateSignature(Map data, String key) throws Exception { + return generateSignature(data, key, "MD5"); + } + + /** + * 生成签名 + */ + public static String generateSignature(Map data, String key, String signType) throws Exception { + Set keySet = data.keySet(); + String[] keyArray = keySet.toArray(new String[0]); + Arrays.sort(keyArray); + + StringBuilder sb = new StringBuilder(); + for (String k : keyArray) { + if (k.equals("sign")) { + continue; + } + String value = data.get(k); + if (value != null && value.trim().length() > 0) { + sb.append(k).append("=").append(value.trim()).append("&"); + } + } + + sb.append("key=").append(key); + + if ("MD5".equals(signType)) { + return DigestUtils.md5Hex(sb.toString()).toUpperCase(); + } else if ("HMAC-SHA256".equals(signType)) { + return HMACSHA256(sb.toString(), key); + } else { + throw new Exception("Invalid sign_type: " + signType); + } + } + + /** + * HMAC-SHA256签名 + */ + private static String HMACSHA256(String data, String key) throws Exception { + Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); + SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(CHARSET), "HmacSHA256"); + sha256_HMAC.init(secret_key); + byte[] array = sha256_HMAC.doFinal(data.getBytes(CHARSET)); + StringBuilder sb = new StringBuilder(); + for (byte item : array) { + sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); + } + return sb.toString().toUpperCase(); + } + + /** + * 验证签名 + */ + public static boolean isSignatureValid(Map data, String key) throws Exception { + if (!data.containsKey("sign")) { + return false; + } + + String sign = data.get("sign"); + String calculatedSign = generateSignature(data, key); + return sign.equals(calculatedSign); + } + + /** + * XML字符串转换为Map + */ + public static Map xmlToMap(String strXML) throws Exception { + Map data = new HashMap<>(); + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document doc = documentBuilder.parse(new ByteArrayInputStream(strXML.getBytes(CHARSET))); + + doc.getDocumentElement().normalize(); + NodeList nodeList = doc.getDocumentElement().getChildNodes(); + + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) node; + data.put(element.getNodeName(), element.getTextContent()); + } + } + + return data; + } + + /** + * Map转换为XML字符串 + */ + public static String mapToXml(Map data) throws Exception { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.newDocument(); + + Element root = document.createElement("xml"); + document.appendChild(root); + + for (String key : data.keySet()) { + String value = data.get(key); + if (value == null) { + value = ""; + } + + Element filed = document.createElement(key); + filed.appendChild(document.createTextNode(value)); + root.appendChild(filed); + } + + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + DOMSource source = new DOMSource(document); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + transformer.transform(source, result); + + return writer.getBuffer().toString(); + } + + /** + * 验证微信支付通知签名 + */ + public static boolean isSignatureValid(String xmlString, String key) throws Exception { + Map data = xmlToMap(xmlString); + return isSignatureValid(data, key); + } + + + /** + * 将XML字符串转换为指定类型的实体对象 + * + * @param xmlString XML字符串 + * @param clazz 实体类Class + * @param 实体类型 + * @return 实体对象 + * @throws Exception 转换异常 + */ + public static T xmlToEntity(String xmlString, Class clazz) throws Exception { + JAXBContext context = JAXBContext.newInstance(clazz); + Unmarshaller unmarshaller = context.createUnmarshaller(); + return clazz.cast(unmarshaller.unmarshal(new StringReader(xmlString))); + } + + + public static String decryptReqInfo(String reqInfo, String apiKey) throws Exception { + try { + // 步骤1: 对加密串A做base64解码,得到加密串B + byte[] encryptedData = Base64.decodeBase64(reqInfo); + + // 步骤2: 对商户key做md5,得到32位小写key* + String key = DigestUtils.md5Hex(apiKey).toLowerCase(); + + // 步骤3: 用key*对加密串B做AES-256-ECB解密(PKCS7Padding) + byte[] decryptedData = decrypt(encryptedData, key); + + return new String(decryptedData, "UTF-8"); + } catch (Exception e) { + logger.error("解密微信退款通知req_info失败", e); + throw new Exception("解密失败: " + e.getMessage(), e); + } + } + + /** + * AES解密方法 + * + * @param encryptedData 加密数据 + * @param key 解密密钥 + * @return 解密后的字节数组 + * @throws Exception 解密异常 + */ + private static byte[] decrypt(byte[] encryptedData, String key) throws Exception { + try { + SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + return cipher.doFinal(encryptedData); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | + IllegalBlockSizeException | BadPaddingException e) { + logger.error("AES解密失败", e); + throw new Exception("AES解密失败: " + e.getMessage(), e); + } + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..c4b27a9 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,67 @@ + +server: + port: 8019 + +spring: + application: + name: sczx-singlepay # 微服务名称 + http: + encoding: + charset: UTF-8 + enabled: true + force: true + mvc: + async: + request-timeout: -1 + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + cloud: + nacos: + discovery: + server-addr: 115.190.8.52:8848 # Nacos 地址 + group: DEFAULT_GROUP + metadata: + version: 1.0.0 + env: dev + lifecycle: + timeout-per-shutdown-phase: 30s # 设置优雅停机时间 + datasource: + url: jdbc:mysql://115.190.8.52:3306/sczx?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true + username: sczx_user + password: Sczx123@ + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 10 + auto-commit: true + +management: + endpoints: + web: + exposure: + include: "*" # 暴露所有监控端点 + endpoint: + health: + show-details: always + + + + +mybatis-plus: + mapper-locations: classpath*:mapper/**/*.xml + type-aliases-package: com.sczx.pay.po # 实体类包路径 + configuration: + mapUnderscoreToCamelCase: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印 SQL(调试用) + + +wechat: + pay: + app-id: wx25e1ad1a70c326de + mch-id: your_mch_id + key: your_api_key + notify-url: https://www.minbo.wang:8020/api/payment/notify + refund-notify-url: https://www.minbo.wang:8020/api/payment/refundNotify + + +cert-path: /root/cert/ \ No newline at end of file diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000..19e6b63 --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,67 @@ + +server: + port: 8019 + +spring: + application: + name: sczx-singlepay # 微服务名称 + http: + encoding: + charset: UTF-8 + enabled: true + force: true + mvc: + async: + request-timeout: -1 + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + cloud: + nacos: + discovery: + server-addr: 115.190.8.52:8848 # Nacos 地址 + group: DEFAULT_GROUP + metadata: + version: 1.0.0 + env: dev + lifecycle: + timeout-per-shutdown-phase: 30s # 设置优雅停机时间 + datasource: + url: jdbc:mysql://115.190.8.52:3306/sczx?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true + username: sczx_user + password: Sczx123@ + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 10 + auto-commit: true + +management: + endpoints: + web: + exposure: + include: "*" # 暴露所有监控端点 + endpoint: + health: + show-details: always + + + + +mybatis-plus: + mapper-locations: classpath*:mapper/**/*.xml + type-aliases-package: com.sczx.pay.po # 实体类包路径 + configuration: + mapUnderscoreToCamelCase: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印 SQL(调试用) + + +wechat: + pay: + app-id: wx25e1ad1a70c326de + mch-id: your_mch_id + key: your_api_key + notify-url: https://www.minbo.wang:8020/api/payment/notify + refund-notify-url: https://www.minbo.wang:8020/api/payment/refundNotify + + +cert-path: E:\cert\ \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..4672562 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,117 @@ + +server: + port: 8018 + +spring: + application: + name: sczx-notify # 微服务名称 + http: + encoding: + charset: UTF-8 + enabled: true + force: true + mvc: + async: + request-timeout: -1 + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + cloud: + nacos: + discovery: + server-addr: 115.190.8.52:8848 # Nacos 地址 + group: DEFAULT_GROUP + metadata: + version: 1.0.0 + env: dev + lifecycle: + timeout-per-shutdown-phase: 30s # 设置优雅停机时间 + datasource: + url: jdbc:mysql://115.190.8.52:3306/sczx?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true + username: sczx_user + password: Sczx123@ + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 10 + auto-commit: true + + +management: + endpoints: + web: + exposure: + include: "*" # 暴露所有监控端点 + endpoint: + health: + show-details: always + +feign: + client: + config: + default: + connectTimeout: 5000 + readTimeout: 5000 + hystrix: + enabled: true # 启用 Feign 的 Hystrix 支持 + +hystrix: + command: + default: + execution: + isolation: + thread: + timeoutInMilliseconds: 10000 # 默认熔断超时时间 + + + + +mybatis-plus: + mapper-locations: classpath*:mapper/**/*.xml + type-aliases-package: com.sczx.pay.po # 实体类包路径 + configuration: + mapUnderscoreToCamelCase: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印 SQL(调试用) + + +wechat: + pay: + app-id: wx25e1ad1a70c326de + mch-id: your_mch_id + key: your_api_key + notify-url: https://www.minbo.wang:8020/notify/api/payment/notify + refund-notify-url: https://www.minbo.wang:8020/notify/api/payment/refundNotify + +#ali: +# pay: +# app-id: 2021004169641281 +# gateway-url: https://openapi.alipay.com/gateway.do +# notify-url: https://www.minbo.wang:8020/api/alipay/notify +# refund-notify-url: https://www.minbo.wang:8020/api/alipay/refundNotify +# privateKey: MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCQke+mZsxNXJXMn2iUZRkhITWY9qPczwHMgRxl68JRuNzZE2AXhAc2ciF7EGob1y0al4hvJo5JUrDew9Zkv0BocAsPjLma3A9KFgj131xMuLxVvHtktkNSwwW9VIbOOhFAQAy3J9jt8PdEk40UONyu9J5Shjs6jbQ8RogdDuwuzKpNj0ZOTeUqgR6RME4xZ0VqvZe9XhVfq3QtVGttUzOpLPNg6boBy/5RqwC64lVFICMqjKkMW2NklVPBDFO46QwfWEbaA+APnd9bd+9WadyNySPb7kAK8GOlvZYfysy+QkOQdj/oebKWKzdKXpFnF+n52dYzeGTvckt1suDngwAFAgMBAAECggEAcPc39iTZe5HfE9d0FpCxBCw4/1+qRz4SP8SbdiC05Lq5B36WVYm7QRYJh2oaH8sR2XeTCpdsE2tj/Y21l1/Calkyq2HhHlraL5/sPIPeUa0ArVXsbZPmI2N0tq6376l8FWJ3DR876SGjdZh+YtDT0HogxIdS/LwBnB/Y+CvS7fWcqiMjcR4h28RU70CQqxpzYuPfqG6GZmByGyJAcdlhFAYgiksb+qZnkjoaKHsjb61xfUX4z+HGdQLqOlVKfNqIpiO7arXaeAb0RQiB8Lqi7fBvn8LLhlTmM1Hr4gyQO14lJmP5Vp2BIg9m5AC9vRkpZ9l79bUTIepDe9+Ovcx5eQKBgQDWZ5yoz8q8Z5VIfMSVTHO3wpfwOBM92S2c21hTsds7NezsMVqGcTuAcvbMW2i1MwxtS7kahXraRAxZnCDB9ZVJs+88xcgu5QJWZ1rJyrwEwB2gTQhColv+XZW6Ck/J6kk2/PG26rNQG6pg/xrKaMtPnXXyKCsdQTyEpr+mlNIJ2wKBgQCsnf4lIuwftCSm0jI73tO1Bb4U9TCIapKidnIhutbyg6u+hJMUTVDNkjsBnrTN24sSP7Cu4Zs/W4lkDqq86fHNEvnF+FouIfO/CwaGteDBNwXHSLM0D+zEBbK04bfoYi7lYZeDRPygFrM49Sxkp0MAmItDmXqbYRQBlhN1XVzznwKBgQCQ+72fFex1XOJBA0X345v4rlkKMxEn6J2EjXr8FbA3KO8OhQ0/DoD7CkLDzChRJ7UGj4cbXRnHUO69BevM3SHXlhp4ERKeS3Q6M2fcPwDHgZZHGPA2Bw6IQoaGKITt+EUMLx3Q4ILi7+JV9wwJxbV+H+9rEiidfsjKtuuwXMBvswKBgQChpElrCSrH/W8c8FSuD9l7+GapRXkvJW0uyW/S+h1yd66J96erKUNzXW339GAnLWErs4yGFynDyLn9gmaMBR6zBQP0SL5Z2N1hlreDyikvhZDZrtBw/kCexx8zlvMan2Z+0kaJXD6cwuUNfzkVADQUCMTQhpZzjhKn8ZtDGiyCzQKBgQCAhU0khG/ufZfQ089+1G7PAlErBr5MQaG40a2vfIWGtHkYyXq5N/3jow1bd2qsyLuz8mr2sWQeURK+B2xZridf6JtYz3gB+dLw0frlLKk4Q6jeehiRlE8H8tIYe/7KcgTmdIzEbo3lmyGMFAILvr/pSCWeUehQYR9PH91Qyi+Tog== +# # publicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArPG4JQ9YY9+tOeImQO0eNkp+NZkenJ9hMHEVVmwAHAI5BoJMCLvAm504BXSpgpqaiCJ6ARTwBkiDz4dyCMVfE3VE8+oMFtXqfHaZLhfd+X9VQNqRCz0HeR2IbjGnnbYxQOAEOM6z092UfNieG00HyU5yREfzjJkIcyuGeGgbIBG1gPpLhXsQKRhQYqp4Exgd4LX6qnKLPL90kJVkQnHcJkp+rCoQ5zS6ZUXE2d/2GzY7xi93uTU2CLMiZ7Vi2OZ1g7xY+yDQzTA/CseveJeW3rM6T8yBjlEJwsywEL5co3YqpJUIMIe/fLjhRRgj+JUq5w0sRqb3+CswQHIjgc71QwIDAQAB +# appCertPath: /root/cert/appCertPublicKey_2021005174658269.crt +# alipayCertPath: /root/cert/alipayCertPublicKey_RSA2.crt +# alipayRootCertPath: /root/cert/alipayRootCert.crt + +cert-path: /root/cert/ + +alipay: + pid: 2021004169641281 + appid: 2021005174658269 + privatekey: MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCQke+mZsxNXJXMn2iUZRkhITWY9qPczwHMgRxl68JRuNzZE2AXhAc2ciF7EGob1y0al4hvJo5JUrDew9Zkv0BocAsPjLma3A9KFgj131xMuLxVvHtktkNSwwW9VIbOOhFAQAy3J9jt8PdEk40UONyu9J5Shjs6jbQ8RogdDuwuzKpNj0ZOTeUqgR6RME4xZ0VqvZe9XhVfq3QtVGttUzOpLPNg6boBy/5RqwC64lVFICMqjKkMW2NklVPBDFO46QwfWEbaA+APnd9bd+9WadyNySPb7kAK8GOlvZYfysy+QkOQdj/oebKWKzdKXpFnF+n52dYzeGTvckt1suDngwAFAgMBAAECggEAcPc39iTZe5HfE9d0FpCxBCw4/1+qRz4SP8SbdiC05Lq5B36WVYm7QRYJh2oaH8sR2XeTCpdsE2tj/Y21l1/Calkyq2HhHlraL5/sPIPeUa0ArVXsbZPmI2N0tq6376l8FWJ3DR876SGjdZh+YtDT0HogxIdS/LwBnB/Y+CvS7fWcqiMjcR4h28RU70CQqxpzYuPfqG6GZmByGyJAcdlhFAYgiksb+qZnkjoaKHsjb61xfUX4z+HGdQLqOlVKfNqIpiO7arXaeAb0RQiB8Lqi7fBvn8LLhlTmM1Hr4gyQO14lJmP5Vp2BIg9m5AC9vRkpZ9l79bUTIepDe9+Ovcx5eQKBgQDWZ5yoz8q8Z5VIfMSVTHO3wpfwOBM92S2c21hTsds7NezsMVqGcTuAcvbMW2i1MwxtS7kahXraRAxZnCDB9ZVJs+88xcgu5QJWZ1rJyrwEwB2gTQhColv+XZW6Ck/J6kk2/PG26rNQG6pg/xrKaMtPnXXyKCsdQTyEpr+mlNIJ2wKBgQCsnf4lIuwftCSm0jI73tO1Bb4U9TCIapKidnIhutbyg6u+hJMUTVDNkjsBnrTN24sSP7Cu4Zs/W4lkDqq86fHNEvnF+FouIfO/CwaGteDBNwXHSLM0D+zEBbK04bfoYi7lYZeDRPygFrM49Sxkp0MAmItDmXqbYRQBlhN1XVzznwKBgQCQ+72fFex1XOJBA0X345v4rlkKMxEn6J2EjXr8FbA3KO8OhQ0/DoD7CkLDzChRJ7UGj4cbXRnHUO69BevM3SHXlhp4ERKeS3Q6M2fcPwDHgZZHGPA2Bw6IQoaGKITt+EUMLx3Q4ILi7+JV9wwJxbV+H+9rEiidfsjKtuuwXMBvswKBgQChpElrCSrH/W8c8FSuD9l7+GapRXkvJW0uyW/S+h1yd66J96erKUNzXW339GAnLWErs4yGFynDyLn9gmaMBR6zBQP0SL5Z2N1hlreDyikvhZDZrtBw/kCexx8zlvMan2Z+0kaJXD6cwuUNfzkVADQUCMTQhpZzjhKn8ZtDGiyCzQKBgQCAhU0khG/ufZfQ089+1G7PAlErBr5MQaG40a2vfIWGtHkYyXq5N/3jow1bd2qsyLuz8mr2sWQeURK+B2xZridf6JtYz3gB+dLw0frlLKk4Q6jeehiRlE8H8tIYe/7KcgTmdIzEbo3lmyGMFAILvr/pSCWeUehQYR9PH91Qyi+Tog== + use_open_id: false + openid: 111 + userid: 111 + is_isv: false + app_auth_token: 202502BBe237336d792f49cebda1f2d202d25A23 + alipay-public-cert-path: /root/cert/appCertPublicKey_2021005174658269.crt + ali-public-cert-path: /root/cert/alipayCertPublicKey_RSA2.crt + alipay-root-cert-path: /root/cert/alipayRootCert.crt +# alipay-public-cert-path: E:/cert/appCertPublicKey_2021005174658269.crt +# ali-public-cert-path: E:/cert/alipayCertPublicKey_RSA2.crt +# alipay-root-cert-path: E:/cert/alipayRootCert.crt + notify-url: http://115.190.8.52:8019/alipay/pay/notify + refund-notify-url: http://115.190.8.52:8019/alipay/refund/notify + + rent: + payNotifyUrl: http://115.190.8.52:8019/alipay/rent/notify/pay diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..e594345 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,53 @@ + + + + + + + + + ${PATTERN} + + + + + + ${LOG_FILE_PATH}/${hostname}/info.%d{yyyy-MM-dd}.log + 31 + + + + ${PATTERN} + + + + WARN + DENY + NEUTRAL + + + ERROR + DENY + NEUTRAL + + + + + + WARN + + ${LOG_FILE_PATH}/${hostname}/warn.log + true + + ${PATTERN} + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/RentRuleItemMapper.xml b/src/main/resources/mapper/RentRuleItemMapper.xml new file mode 100644 index 0000000..e4eadea --- /dev/null +++ b/src/main/resources/mapper/RentRuleItemMapper.xml @@ -0,0 +1,341 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id + , + out_item_id, + ali_item_id, + out_sku_id, + item_desc, + brand_name, + model_name, + category_name, + rental_days, + rent_type, + rule_name, + page_path, + image, + image_list1, + image_list2, + image_list3, + deposit_free, + deposit_price, + rental_price, + battery_rule_id, + car_rule_id, + car_model_id, + brand_id + + + + + + + + + + + + + + + + + + + insert into zc_rent_rule_item + + id, + out_item_id, + ali_item_id, + out_sku_id, + item_desc, + brand_name, + model_name, + category_name, + rental_days, + rent_type, + rule_name, + page_path, + image, + image_list1, + image_list2, + image_list3, + deposit_free, + deposit_price, + rental_price, + battery_rule_id, + car_rule_id, + car_model_id, + brand_id, + + + #{id}, + #{outItemId}, + #{aliItemId}, + #{outSkuId}, + #{itemDesc}, + #{brandName}, + #{modelName}, + #{categoryName}, + #{rentalDays}, + #{rentType}, + #{ruleName}, + #{pagePath}, + #{image}, + #{imageList1}, + #{imageList2}, + #{imageList3}, + #{depositFree}, + #{depositPrice}, + #{rentalPrice}, + #{batteryRuleId}, + #{carRuleId}, + #{carModelId}, + #{brandId}, + + + + + insert into zc_rent_rule_item + (out_item_id, + ali_item_id, + out_sku_id, + item_desc, + brand_name, + model_name, + category_name, + rental_days, + rent_type, + rule_name, + page_path, + image, + image_list1, + image_list2, + image_list3, + deposit_free, + deposit_price, + rental_price, + battery_rule_id,car_rule_id,car_model_id,brand_id) + SELECT + #{outItemId} AS out_item_id, + '' AS ali_item_id, + #{outSkuId} AS out_sku_id, + #{itemDesc} as item_desc, + zcm.brand_name AS brand_name, + zcm.model_name AS model_name, + zbc.category_name AS category_name, + zrcr.rental_days AS rental_days, + sdd.dict_label AS rent_type, + zrcr.rule_name AS rule_name, + #{pagePath} AS page_path, + zcm.image AS image, + '' as image_list1, + '' as image_list2, + '' as image_list3, + zrcr.deposit_free AS deposit_free, + zrcr.deposit_price AS deposit_price, + zrcr.rental_price AS rental_price, + zrcrb.battery_rule_id AS battery_rule_id, + zcmp.car_rule_id AS car_rule_id, + zcmp.car_model_id AS car_model_id, + zcb.id AS brand_id + FROM + zc_car_brand AS zcb, + zc_car_model AS zcm, + zc_car_model_package AS zcmp, + zc_rent_car_rule AS zrcr, + zc_rent_battey_rule AS zrbr, + zc_rent_car_rule_battery AS zrcrb, + sys_dict_data AS sdd, + zc_battery_category AS zbc + WHERE + zcb.id = zcm.brand_id + AND zcmp.car_model_id = zcm.id + AND zcmp.car_rule_id = zrcr.id + AND zrcrb.car_rule_id = zrcr.id + AND zrcrb.battery_rule_id = zrbr.id + AND sdd.dict_type = 'key_rent_type' + AND zrcr.rental_type = sdd.dict_value + AND zbc.id = zrbr.category_id + AND zcb.id = #{brandId} + AND zcmp.car_model_id = #{carModelId} + AND zcmp.car_rule_id = #{carRuleId} + AND zrcrb.battery_rule_id = #{batteryRuleId} + + + + update zc_rent_rule_item + + out_item_id = #{outItemId}, + ali_item_id = #{aliItemId}, + out_sku_id = #{outSkuId}, + item_desc = #{itemDesc}, + brand_name = #{brandName}, + model_name = #{modelName}, + category_name = #{categoryName}, + rental_days = #{rentalDays}, + rent_type = #{rentType}, + rule_name = #{ruleName}, + page_path = #{pagePath}, + image = #{image}, + image_list1 = #{imageList1}, + image_list2 = #{imageList2}, + image_list3 = #{imageList3}, + deposit_free = #{depositFree}, + deposit_price = #{depositPrice}, + rental_price = #{rentalPrice}, + battery_rule_id = #{batteryRuleId}, + car_rule_id = #{carRuleId}, + car_model_id = #{carModelId}, + brand_id = #{brandId}, + + where id = #{id} + + + + update zc_rent_rule_item + set ali_item_id = #{aliItemId} + where out_item_id = #{outItemId} + + + + + update zc_rent_rule_item + + out_item_id = #{outItemId}, + ali_item_id = #{aliItemId}, + out_sku_id = #{outSkuId}, + item_desc = #{itemDesc}, + brand_name = #{brandName}, + model_name = #{modelName}, + category_name = #{categoryName}, + rental_days = #{rentalDays}, + rent_type = #{rentType}, + rule_name = #{ruleName}, + page_path = #{pagePath}, + image = #{image}, + image_list1 = #{imageList1}, + image_list2 = #{imageList2}, + image_list3 = #{imageList3}, + deposit_free = #{depositFree}, + deposit_price = #{depositPrice}, + rental_price = #{rentalPrice}, + battery_rule_id = #{batteryRuleId}, + car_rule_id = #{carRuleId}, + car_model_id = #{carModelId}, + brand_id = #{brandId}, + + where out_item_id = #{outItemId} + + + + + + + + + delete + from zc_rent_rule_item + where id = #{id} + + + +