更新代码:添加支付功能、任务队列系统和相关文档
This commit is contained in:
@@ -212,3 +212,4 @@ ngrok http 8080
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
110
demo/CODE_REVIEW_REPORT.md
Normal file
110
demo/CODE_REVIEW_REPORT.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# 代码检查报告
|
||||
|
||||
## 发现的问题
|
||||
|
||||
### 1. IJPay配置问题 ⚠️
|
||||
|
||||
**问题**:`AlipayService` 使用 IJPay 的 `AliPayApi`,但 IJPay 可能没有正确配置。
|
||||
|
||||
**位置**:`AlipayService.java`
|
||||
|
||||
**说明**:
|
||||
- `AlipayService` 使用 `@Value` 注入配置,但 IJPay 的 `AliPayApi` 可能不会自动读取这些配置
|
||||
- IJPay 的 `AliPayApi` 需要显式配置或通过系统属性/配置文件读取
|
||||
|
||||
**建议**:
|
||||
- 检查 IJPay 是否需要初始化配置
|
||||
- 或者使用 `PaymentConfig.AliPayConfig` 来配置 IJPay
|
||||
|
||||
### 2. 配置文件不一致 ⚠️
|
||||
|
||||
**问题**:`payment.properties` 中有模板值,但实际配置在 `application-dev.properties` 中。
|
||||
|
||||
**位置**:
|
||||
- `payment.properties` - 包含模板值("您的APPID")
|
||||
- `application-dev.properties` - 包含实际配置
|
||||
|
||||
**说明**:
|
||||
- `PaymentConfig` 使用 `@PropertySource("classpath:payment.properties")`
|
||||
- 但 `AlipayService` 使用 `@Value` 从 `application-dev.properties` 读取配置
|
||||
- 这可能导致配置不一致
|
||||
|
||||
**建议**:
|
||||
- 统一配置文件路径
|
||||
- 或者在 `payment.properties` 中也配置实际值
|
||||
|
||||
### 3. 异步通知参数获取 ⚠️
|
||||
|
||||
**问题**:`PaymentController.alipayNotify()` 中参数获取方式可能不正确。
|
||||
|
||||
**位置**:`PaymentController.java` 第 100-117 行
|
||||
|
||||
**说明**:
|
||||
- 使用 `request.getParameterMap()` 获取参数
|
||||
- 但支付宝异步通知可能使用 POST 请求体,而不是 URL 参数
|
||||
- 应该使用 `AliPayApi.toMap(request)` 来获取参数(如 `AlipayController` 中所示)
|
||||
|
||||
**建议**:
|
||||
- 使用 IJPay 的 `AliPayApi.toMap(request)` 方法获取参数
|
||||
- 或者检查支付宝异步通知的实际参数格式
|
||||
|
||||
### 4. 未使用的变量警告 ⚠️
|
||||
|
||||
**问题**:`AlipayService` 中的 `privateKey` 变量未使用。
|
||||
|
||||
**位置**:`AlipayService.java` 第 33 行
|
||||
|
||||
**说明**:
|
||||
- `privateKey` 被注入但未在代码中直接使用
|
||||
- IJPay 内部可能需要使用它,但当前代码中未显式使用
|
||||
|
||||
**建议**:
|
||||
- 如果 IJPay 内部使用,可以保留
|
||||
- 或者添加注释说明
|
||||
|
||||
### 5. 类型转换警告 ⚠️
|
||||
|
||||
**问题**:JSON 解析时存在类型转换警告。
|
||||
|
||||
**位置**:`AlipayService.java` 第 136-137 行
|
||||
|
||||
**说明**:
|
||||
- 使用 `objectMapper.readValue(responseBody, Map.class)` 时存在类型转换警告
|
||||
- 应该使用 `TypeReference` 或 `MapType` 来避免警告
|
||||
|
||||
**建议**:
|
||||
- 使用 `TypeReference` 或 `MapType` 来明确类型
|
||||
|
||||
## 修复建议
|
||||
|
||||
### 1. 修复 IJPay 配置
|
||||
|
||||
检查 IJPay 是否需要显式配置,如果需要,可以创建一个配置类来初始化 IJPay。
|
||||
|
||||
### 2. 统一配置文件
|
||||
|
||||
确保所有配置文件中的配置一致,或者统一使用一个配置文件。
|
||||
|
||||
### 3. 修复异步通知参数获取
|
||||
|
||||
使用 IJPay 的 `AliPayApi.toMap(request)` 方法获取参数。
|
||||
|
||||
### 4. 修复类型转换警告
|
||||
|
||||
使用 `TypeReference` 或 `MapType` 来明确类型。
|
||||
|
||||
## 代码质量
|
||||
|
||||
### 优点
|
||||
- 代码结构清晰
|
||||
- 有良好的日志记录
|
||||
- 有重试机制和错误处理
|
||||
- 使用了 IJPay 封装
|
||||
|
||||
### 需要改进
|
||||
- 配置文件一致性
|
||||
- IJPay 配置初始化
|
||||
- 异步通知参数获取方式
|
||||
- 类型转换警告
|
||||
|
||||
|
||||
@@ -273,3 +273,4 @@ if (result.success) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
229
demo/IJPAY_USAGE_GUIDE.md
Normal file
229
demo/IJPAY_USAGE_GUIDE.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# IJPay 使用指南
|
||||
|
||||
## 一、IJPay 简介
|
||||
|
||||
IJPay 是一个简洁易用的 Java 聚合支付 SDK,支持微信支付、支付宝支付、银联支付等多种支付方式。
|
||||
|
||||
项目已集成 IJPay-AliPay 模块,用于支付宝支付功能。
|
||||
|
||||
## 二、项目中的 IJPay 使用方式
|
||||
|
||||
### 1. 依赖配置
|
||||
|
||||
在 `pom.xml` 中已添加 IJPay 依赖:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.github.javen205</groupId>
|
||||
<artifactId>IJPay-AliPay</artifactId>
|
||||
<version>2.9.12.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 2. 代码使用方式
|
||||
|
||||
项目中主要通过 `AlipayController.java` 使用 IJPay 的 `AliPayApi` 类进行支付操作。
|
||||
|
||||
#### 2.1 扫码支付(QR码支付)
|
||||
|
||||
```java
|
||||
@PostMapping("/qr-pay")
|
||||
public ResponseEntity<Map<String, Object>> qrPay(...) {
|
||||
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
|
||||
model.setOutTradeNo(outTradeNo);
|
||||
model.setTotalAmount(totalAmount);
|
||||
model.setSubject(subject);
|
||||
model.setBody(body);
|
||||
|
||||
// 使用 IJPay 的 AliPayApi 调用预创建接口
|
||||
String qrCode = AliPayApi.tradePrecreatePayToResponse(model, notifyUrl).getBody();
|
||||
// 返回二维码字符串
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 PC网页支付
|
||||
|
||||
```java
|
||||
@PostMapping("/pc-pay")
|
||||
public void pcPay(...) {
|
||||
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
|
||||
model.setOutTradeNo(outTradeNo);
|
||||
model.setProductCode("FAST_INSTANT_TRADE_PAY");
|
||||
model.setTotalAmount(totalAmount);
|
||||
model.setSubject(subject);
|
||||
|
||||
// 使用 IJPay 的 AliPayApi 进行页面跳转
|
||||
AliPayApi.tradePage(response, model, notifyUrl, returnUrl);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 手机网页支付
|
||||
|
||||
```java
|
||||
@PostMapping("/wap-pay")
|
||||
public void wapPay(...) {
|
||||
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
|
||||
model.setOutTradeNo(outTradeNo);
|
||||
model.setProductCode("QUICK_WAP_PAY");
|
||||
model.setTotalAmount(totalAmount);
|
||||
|
||||
// 使用 IJPay 的 AliPayApi 进行手机支付
|
||||
AliPayApi.wapPay(response, model, returnUrl, notifyUrl);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 APP支付
|
||||
|
||||
```java
|
||||
@PostMapping("/app-pay")
|
||||
public ResponseEntity<Map<String, Object>> appPay(...) {
|
||||
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
|
||||
model.setOutTradeNo(outTradeNo);
|
||||
model.setProductCode("QUICK_MSECURITY_PAY");
|
||||
model.setTotalAmount(totalAmount);
|
||||
|
||||
// 使用 IJPay 的 AliPayApi 获取 APP 支付订单信息
|
||||
String orderInfo = AliPayApi.appPayToResponse(model, notifyUrl).getBody();
|
||||
// 返回订单信息给 APP
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 订单查询
|
||||
|
||||
```java
|
||||
@GetMapping("/query")
|
||||
public ResponseEntity<Map<String, Object>> queryOrder(...) {
|
||||
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
|
||||
model.setOutTradeNo(outTradeNo);
|
||||
|
||||
// 使用 IJPay 的 AliPayApi 查询订单
|
||||
String result = AliPayApi.tradeQueryToResponse(model).getBody();
|
||||
// 返回查询结果
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.6 退款
|
||||
|
||||
```java
|
||||
@PostMapping("/refund")
|
||||
public ResponseEntity<Map<String, Object>> refund(...) {
|
||||
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
|
||||
model.setOutTradeNo(outTradeNo);
|
||||
model.setRefundAmount(refundAmount);
|
||||
model.setRefundReason(refundReason);
|
||||
|
||||
// 使用 IJPay 的 AliPayApi 发起退款
|
||||
String result = AliPayApi.tradeRefundToResponse(model).getBody();
|
||||
// 返回退款结果
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.7 异步通知处理
|
||||
|
||||
```java
|
||||
@PostMapping("/notify")
|
||||
public String notifyUrl(HttpServletRequest request) {
|
||||
// 使用 IJPay 的 AliPayApi 将请求参数转换为 Map
|
||||
Map<String, String> params = AliPayApi.toMap(request);
|
||||
|
||||
// 验证签名
|
||||
boolean verifyResult = AlipaySignature.rsaCertCheckV1(
|
||||
params, aliPayConfig.getAliPayCertPath(), "UTF-8", "RSA2");
|
||||
|
||||
if (verifyResult) {
|
||||
// 处理支付成功逻辑
|
||||
return "success";
|
||||
} else {
|
||||
return "failure";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 三、IJPay 配置说明
|
||||
|
||||
### 3.1 配置方式
|
||||
|
||||
IJPay 的 `AliPayApi` 类内部会自动读取系统属性或配置文件中的支付宝配置。
|
||||
|
||||
需要配置的参数包括:
|
||||
- `appId`: 支付宝应用ID
|
||||
- `privateKey`: 应用私钥
|
||||
- `publicKey`: 支付宝公钥
|
||||
- `gatewayUrl`: 支付宝网关地址
|
||||
- `charset`: 字符编码(通常为 UTF-8)
|
||||
- `signType`: 签名类型(通常为 RSA2)
|
||||
|
||||
### 3.2 配置文件
|
||||
|
||||
配置在 `application-dev.properties` 或 `application-prod.properties` 中:
|
||||
|
||||
```properties
|
||||
# 支付宝配置
|
||||
alipay.app-id=你的APPID
|
||||
alipay.private-key=你的应用私钥
|
||||
alipay.public-key=支付宝公钥
|
||||
alipay.gateway-url=https://openapi.alipaydev.com/gateway.do
|
||||
alipay.charset=UTF-8
|
||||
alipay.sign-type=RSA2
|
||||
alipay.notify-url=回调通知地址
|
||||
alipay.return-url=同步返回地址
|
||||
```
|
||||
|
||||
### 3.3 IJPay 初始化(可选)
|
||||
|
||||
如果需要显式初始化 IJPay,可以在应用启动时配置:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class IJPayConfig {
|
||||
|
||||
@Autowired
|
||||
private AliPayConfig aliPayConfig;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// IJPay 的 AliPayApi 会自动读取配置
|
||||
// 如果需要显式配置,可以使用 AliPayApiConfigKit
|
||||
// 注意:具体初始化方式取决于 IJPay 版本
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 四、IJPay vs 原生 Alipay SDK
|
||||
|
||||
项目中同时使用了两种方式:
|
||||
|
||||
1. **IJPay (AliPayApi)** - 在 `AlipayController.java` 中使用
|
||||
- 优点:封装更简洁,使用更方便
|
||||
- 缺点:需要额外依赖 IJPay 库
|
||||
|
||||
2. **原生 Alipay SDK (DefaultAlipayClient)** - 在 `AlipayService.java` 中使用
|
||||
- 优点:官方 SDK,功能完整
|
||||
- 缺点:需要手动创建客户端和处理响应
|
||||
|
||||
## 五、常见问题
|
||||
|
||||
### Q1: IJPay 如何初始化?
|
||||
|
||||
A: IJPay 的 `AliPayApi` 类内部会自动读取配置,通常不需要显式初始化。如果遇到配置问题,可以检查:
|
||||
1. 配置文件中的参数是否正确
|
||||
2. 系统属性是否设置
|
||||
3. 环境变量是否配置
|
||||
|
||||
### Q2: 如何切换到 IJPay?
|
||||
|
||||
A: 如果当前使用原生 Alipay SDK,可以:
|
||||
1. 修改 `AlipayService.java`,使用 `AliPayApi` 替代 `DefaultAlipayClient`
|
||||
2. 或者直接使用 `AlipayController` 中的接口
|
||||
|
||||
### Q3: IJPay 和原生 SDK 的区别?
|
||||
|
||||
A: IJPay 是对原生 SDK 的封装,提供了更简洁的 API。底层实现仍然使用支付宝官方 SDK。
|
||||
|
||||
## 六、参考文档
|
||||
|
||||
- IJPay 官方文档:https://github.com/Javen205/IJPay
|
||||
- IJPay 示例项目:https://github.com/Javen205/IJPay-Demo
|
||||
- 支付宝开放平台:https://open.alipay.com
|
||||
|
||||
|
||||
@@ -293,3 +293,4 @@ grep "img2vid_abc123def456" logs/application.log
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -294,3 +294,4 @@ public TaskQueue addTextToVideoTask(String username, String taskId) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -37,5 +37,6 @@ public class PasswordChecker {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -285,3 +285,4 @@ ResourceNotFound.TemplateNotFound
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -303,3 +303,4 @@ const startPolling = (taskId) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -173,3 +173,4 @@ const updateWork = async (workId, updateData) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -437,5 +437,6 @@ MIT License
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -35,3 +35,4 @@ console.log('App.vue 加载成功')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -61,3 +61,4 @@ export const getWorkStats = () => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -96,5 +96,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
:close-on-press-escape="true"
|
||||
@close="handleClose"
|
||||
center
|
||||
:show-close="true"
|
||||
custom-class="payment-modal-dialog"
|
||||
>
|
||||
<div class="payment-content">
|
||||
<!-- 支付方式选择 -->
|
||||
@@ -206,11 +208,46 @@ const showAgreement = () => {
|
||||
background: #0a0a0a;
|
||||
}
|
||||
|
||||
.payment-modal :deep(.el-dialog) {
|
||||
background: #0a0a0a;
|
||||
/* 移除所有可能的白边和边框 */
|
||||
.payment-modal :deep(.el-dialog),
|
||||
.payment-modal-dialog {
|
||||
background: #0a0a0a !important;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
border: none !important;
|
||||
border-width: 0 !important;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
outline: none !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.payment-modal :deep(.el-dialog__body) {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: #0a0a0a !important;
|
||||
border: none !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.payment-modal :deep(.el-dialog__wrapper) {
|
||||
background: transparent !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.payment-modal :deep(.el-overlay) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.payment-modal :deep(.el-overlay-dialog) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* 确保遮罩层没有白边 */
|
||||
.payment-modal :deep(.el-overlay.is-message-box) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.payment-modal :deep(.el-dialog__header) {
|
||||
@@ -237,6 +274,7 @@ const showAgreement = () => {
|
||||
padding: 24px;
|
||||
background: #0a0a0a;
|
||||
color: white;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 支付方式选择 */
|
||||
|
||||
@@ -11,6 +11,12 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
public class DemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 在应用启动时立即设置HTTP超时时间(支付宝API调用可能需要更长时间)
|
||||
// 连接超时:30秒,读取超时:120秒
|
||||
// 必须在SpringApplication.run()之前设置,确保在所有HTTP客户端创建之前生效
|
||||
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");
|
||||
System.setProperty("sun.net.client.defaultReadTimeout", "120000");
|
||||
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
package com.example.demo.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import com.ijpay.alipay.AliPayApiConfig;
|
||||
import com.ijpay.alipay.AliPayApiConfigKit;
|
||||
|
||||
/**
|
||||
* 支付配置类
|
||||
* 集成IJPay支付模块配置
|
||||
*
|
||||
* 初始化IJPay的AliPayApiConfigKit,确保AliPayApi可以正常使用
|
||||
*/
|
||||
@Configuration
|
||||
@PropertySource("classpath:payment.properties")
|
||||
public class PaymentConfig {
|
||||
public class PaymentConfig implements ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PaymentConfig.class);
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties(prefix = "alipay")
|
||||
@@ -19,6 +29,263 @@ public class PaymentConfig {
|
||||
return new AliPayConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化IJPay配置
|
||||
* 在应用上下文完全初始化后执行,确保所有bean都已创建完成
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
|
||||
// 确保只在根上下文触发时初始化,避免子上下文重复触发
|
||||
if (event.getApplicationContext().getParent() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从ApplicationContext获取已创建的bean(此时所有bean都已创建完成)
|
||||
AliPayConfig aliPayConfig;
|
||||
try {
|
||||
aliPayConfig = event.getApplicationContext().getBean(AliPayConfig.class);
|
||||
} catch (Exception e) {
|
||||
logger.error("无法从ApplicationContext获取AliPayConfig bean", e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info("=== 初始化IJPay配置 ===");
|
||||
|
||||
// 详细的配置验证
|
||||
boolean configValid = validateAlipayConfig(aliPayConfig);
|
||||
if (!configValid) {
|
||||
logger.error("支付宝配置验证失败,请检查配置");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("应用ID: {}", aliPayConfig.getAppId());
|
||||
logger.info("网关地址: {}", aliPayConfig.getServerUrl());
|
||||
logger.info("字符集: {}", aliPayConfig.getCharset());
|
||||
logger.info("签名类型: {}", aliPayConfig.getSignType());
|
||||
logger.info("通知URL: {}", aliPayConfig.getNotifyUrl());
|
||||
logger.info("返回URL: {}", aliPayConfig.getReturnUrl());
|
||||
|
||||
// HTTP超时时间已在应用启动时设置(DemoApplication.main方法中)
|
||||
// 连接超时:30秒,读取超时:120秒
|
||||
logger.info("HTTP超时设置:连接超时30秒,读取超时120秒(已在应用启动时设置)");
|
||||
|
||||
// 构建AliPayApiConfig对象
|
||||
// 注意:DefaultAlipayClient会在build()方法中创建,此时会使用上面设置的系统属性
|
||||
AliPayApiConfig apiConfig = AliPayApiConfig.builder()
|
||||
.setAppId(aliPayConfig.getAppId())
|
||||
.setPrivateKey(aliPayConfig.getPrivateKey())
|
||||
.setAliPayPublicKey(aliPayConfig.getPublicKey())
|
||||
.setServiceUrl(aliPayConfig.getServerUrl())
|
||||
.setCharset(aliPayConfig.getCharset() != null ? aliPayConfig.getCharset() : "UTF-8")
|
||||
.setSignType(aliPayConfig.getSignType() != null ? aliPayConfig.getSignType() : "RSA2")
|
||||
.build();
|
||||
|
||||
// 验证配置中的serviceUrl是否正确
|
||||
logger.info("=== 验证IJPay配置中的网关地址 ===");
|
||||
logger.info("配置中的serviceUrl: {}", apiConfig.getServiceUrl());
|
||||
logger.info("配置中的appId: {}", apiConfig.getAppId());
|
||||
|
||||
// 保存配置到静态变量,供AlipayService使用(备用方案)
|
||||
AliPayApiConfigHolder.setConfig(apiConfig);
|
||||
|
||||
// 设置到AliPayApiConfigKit中,供AliPayApi使用
|
||||
// 根据IJPay源码,正确的方法是 putApiConfig
|
||||
try {
|
||||
AliPayApiConfigKit.putApiConfig(apiConfig);
|
||||
logger.info("IJPay配置已成功设置到AliPayApiConfigKit");
|
||||
|
||||
// 验证从AliPayApiConfigKit获取的配置
|
||||
AliPayApiConfig retrievedConfig = AliPayApiConfigKit.getAliPayApiConfig();
|
||||
logger.info("从AliPayApiConfigKit获取的serviceUrl: {}", retrievedConfig.getServiceUrl());
|
||||
logger.info("从AliPayApiConfigKit获取的appId: {}", retrievedConfig.getAppId());
|
||||
|
||||
logger.info("IJPay配置初始化完成");
|
||||
} catch (Exception e) {
|
||||
logger.error("设置IJPay配置到AliPayApiConfigKit时发生异常", e);
|
||||
// 不抛出异常,允许应用启动,配置将在调用时动态设置
|
||||
logger.warn("IJPay配置设置失败,但应用将继续启动,配置将在调用时动态设置");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("IJPay配置初始化失败", e);
|
||||
// 不抛出异常,允许应用启动,配置将在调用时动态设置
|
||||
logger.warn("IJPay配置初始化失败,但应用将继续启动,配置将在调用时动态设置");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证支付宝配置
|
||||
* @param config 支付宝配置
|
||||
* @return 验证结果
|
||||
*/
|
||||
private boolean validateAlipayConfig(AliPayConfig config) {
|
||||
logger.info("=== 开始验证支付宝配置 ===");
|
||||
|
||||
boolean isValid = true;
|
||||
StringBuilder errors = new StringBuilder();
|
||||
|
||||
// 1. 验证应用ID
|
||||
if (config.getAppId() == null || config.getAppId().isEmpty()) {
|
||||
logger.error("❌ 应用ID (app-id) 为空");
|
||||
errors.append("应用ID为空; ");
|
||||
isValid = false;
|
||||
} else {
|
||||
// 验证appId格式(应该是16位数字)
|
||||
String appId = config.getAppId().trim();
|
||||
if (!appId.matches("^\\d{16}$")) {
|
||||
logger.warn("⚠️ 应用ID格式可能不正确: {} (应该是16位数字)", appId);
|
||||
} else {
|
||||
logger.info("✅ 应用ID格式正确: {}", appId);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 验证私钥
|
||||
if (config.getPrivateKey() == null || config.getPrivateKey().isEmpty()) {
|
||||
logger.error("❌ 应用私钥 (private-key) 为空");
|
||||
errors.append("应用私钥为空; ");
|
||||
isValid = false;
|
||||
} else {
|
||||
String privateKey = config.getPrivateKey().trim();
|
||||
// 验证私钥格式(RSA私钥通常以BEGIN PRIVATE KEY或BEGIN RSA PRIVATE KEY开头)
|
||||
if (!privateKey.startsWith("MII") && !privateKey.contains("BEGIN")) {
|
||||
logger.warn("⚠️ 私钥格式可能不正确(应该以MII开头或包含BEGIN PRIVATE KEY)");
|
||||
logger.debug("私钥前20个字符: {}", privateKey.substring(0, Math.min(20, privateKey.length())));
|
||||
}
|
||||
// 验证私钥长度(RSA私钥通常至少1000字符)
|
||||
if (privateKey.length() < 500) {
|
||||
logger.error("❌ 私钥长度过短: {} 字符(RSA私钥通常至少500字符)", privateKey.length());
|
||||
errors.append("私钥长度过短; ");
|
||||
isValid = false;
|
||||
} else {
|
||||
logger.info("✅ 私钥长度: {} 字符", privateKey.length());
|
||||
// 隐藏敏感信息,只显示前后部分
|
||||
String maskedKey = privateKey.substring(0, 20) + "..." + privateKey.substring(privateKey.length() - 20);
|
||||
logger.debug("私钥摘要: {}", maskedKey);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 验证公钥
|
||||
if (config.getPublicKey() == null || config.getPublicKey().isEmpty()) {
|
||||
logger.error("❌ 支付宝公钥 (public-key) 为空");
|
||||
errors.append("支付宝公钥为空; ");
|
||||
isValid = false;
|
||||
} else {
|
||||
String publicKey = config.getPublicKey().trim();
|
||||
// 验证公钥格式(RSA公钥通常以MII开头)
|
||||
if (!publicKey.startsWith("MII") && !publicKey.contains("BEGIN")) {
|
||||
logger.warn("⚠️ 公钥格式可能不正确(应该以MII开头或包含BEGIN PUBLIC KEY)");
|
||||
}
|
||||
// 验证公钥长度(RSA公钥通常至少200字符)
|
||||
if (publicKey.length() < 200) {
|
||||
logger.error("❌ 公钥长度过短: {} 字符(RSA公钥通常至少200字符)", publicKey.length());
|
||||
errors.append("公钥长度过短; ");
|
||||
isValid = false;
|
||||
} else {
|
||||
logger.info("✅ 公钥长度: {} 字符", publicKey.length());
|
||||
// 隐藏敏感信息,只显示前后部分
|
||||
String maskedKey = publicKey.substring(0, 20) + "..." + publicKey.substring(publicKey.length() - 20);
|
||||
logger.debug("公钥摘要: {}", maskedKey);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 验证网关地址
|
||||
if (config.getServerUrl() == null || config.getServerUrl().isEmpty()) {
|
||||
logger.error("❌ 网关地址 (server-url) 为空");
|
||||
errors.append("网关地址为空; ");
|
||||
isValid = false;
|
||||
} else {
|
||||
String serverUrl = config.getServerUrl().trim();
|
||||
if (!serverUrl.startsWith("https://")) {
|
||||
logger.error("❌ 网关地址格式不正确: {} (应该以https://开头)", serverUrl);
|
||||
errors.append("网关地址格式不正确; ");
|
||||
isValid = false;
|
||||
} else if (!serverUrl.contains("alipay")) {
|
||||
logger.warn("⚠️ 网关地址可能不正确: {} (应该包含alipay)", serverUrl);
|
||||
} else {
|
||||
logger.info("✅ 网关地址: {}", serverUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 验证字符集
|
||||
String charset = config.getCharset();
|
||||
if (charset == null || charset.isEmpty()) {
|
||||
logger.warn("⚠️ 字符集未设置,将使用默认值: UTF-8");
|
||||
} else if (!charset.equalsIgnoreCase("UTF-8")) {
|
||||
logger.warn("⚠️ 字符集不是UTF-8: {} (建议使用UTF-8)", charset);
|
||||
} else {
|
||||
logger.info("✅ 字符集: {}", charset);
|
||||
}
|
||||
|
||||
// 6. 验证签名类型
|
||||
String signType = config.getSignType();
|
||||
if (signType == null || signType.isEmpty()) {
|
||||
logger.warn("⚠️ 签名类型未设置,将使用默认值: RSA2");
|
||||
} else if (!signType.equalsIgnoreCase("RSA2")) {
|
||||
logger.warn("⚠️ 签名类型不是RSA2: {} (建议使用RSA2)", signType);
|
||||
} else {
|
||||
logger.info("✅ 签名类型: {}", signType);
|
||||
}
|
||||
|
||||
// 7. 验证通知URL
|
||||
if (config.getNotifyUrl() == null || config.getNotifyUrl().isEmpty()) {
|
||||
logger.error("❌ 通知URL (notify-url) 为空");
|
||||
errors.append("通知URL为空; ");
|
||||
isValid = false;
|
||||
} else {
|
||||
String notifyUrl = config.getNotifyUrl().trim();
|
||||
if (!notifyUrl.startsWith("https://")) {
|
||||
logger.error("❌ 通知URL格式不正确: {} (应该以https://开头)", notifyUrl);
|
||||
errors.append("通知URL格式不正确; ");
|
||||
isValid = false;
|
||||
} else {
|
||||
logger.info("✅ 通知URL: {}", notifyUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// 8. 验证返回URL
|
||||
if (config.getReturnUrl() == null || config.getReturnUrl().isEmpty()) {
|
||||
logger.error("❌ 返回URL (return-url) 为空");
|
||||
errors.append("返回URL为空; ");
|
||||
isValid = false;
|
||||
} else {
|
||||
String returnUrl = config.getReturnUrl().trim();
|
||||
if (!returnUrl.startsWith("https://")) {
|
||||
logger.error("❌ 返回URL格式不正确: {} (应该以https://开头)", returnUrl);
|
||||
errors.append("返回URL格式不正确; ");
|
||||
isValid = false;
|
||||
} else {
|
||||
logger.info("✅ 返回URL: {}", returnUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// 总结验证结果
|
||||
if (isValid) {
|
||||
logger.info("=== 支付宝配置验证通过 ===");
|
||||
} else {
|
||||
logger.error("=== 支付宝配置验证失败 ===");
|
||||
logger.error("错误详情: {}", errors.toString());
|
||||
logger.error("请检查 application-dev.properties 或 payment.properties 文件中的支付宝配置");
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* IJPay配置持有者
|
||||
* 用于保存配置,供AlipayService在调用时动态设置
|
||||
*/
|
||||
public static class AliPayApiConfigHolder {
|
||||
private static AliPayApiConfig config;
|
||||
|
||||
public static void setConfig(AliPayApiConfig config) {
|
||||
AliPayApiConfigHolder.config = config;
|
||||
}
|
||||
|
||||
public static AliPayApiConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝配置
|
||||
*/
|
||||
@@ -27,7 +294,12 @@ public class PaymentConfig {
|
||||
private String privateKey;
|
||||
private String publicKey;
|
||||
private String serverUrl;
|
||||
private String gatewayUrl;
|
||||
private String domain;
|
||||
private String charset;
|
||||
private String signType;
|
||||
private String notifyUrl;
|
||||
private String returnUrl;
|
||||
private String appCertPath;
|
||||
private String aliPayCertPath;
|
||||
private String aliPayRootCertPath;
|
||||
@@ -45,9 +317,24 @@ public class PaymentConfig {
|
||||
public String getServerUrl() { return serverUrl; }
|
||||
public void setServerUrl(String serverUrl) { this.serverUrl = serverUrl; }
|
||||
|
||||
public String getGatewayUrl() { return gatewayUrl; }
|
||||
public void setGatewayUrl(String gatewayUrl) { this.gatewayUrl = gatewayUrl; }
|
||||
|
||||
public String getDomain() { return domain; }
|
||||
public void setDomain(String domain) { this.domain = domain; }
|
||||
|
||||
public String getCharset() { return charset; }
|
||||
public void setCharset(String charset) { this.charset = charset; }
|
||||
|
||||
public String getSignType() { return signType; }
|
||||
public void setSignType(String signType) { this.signType = signType; }
|
||||
|
||||
public String getNotifyUrl() { return notifyUrl; }
|
||||
public void setNotifyUrl(String notifyUrl) { this.notifyUrl = notifyUrl; }
|
||||
|
||||
public String getReturnUrl() { return returnUrl; }
|
||||
public void setReturnUrl(String returnUrl) { this.returnUrl = returnUrl; }
|
||||
|
||||
public String getAppCertPath() { return appCertPath; }
|
||||
public void setAppCertPath(String appCertPath) { this.appCertPath = appCertPath; }
|
||||
|
||||
|
||||
@@ -45,7 +45,8 @@ public class SecurityConfig {
|
||||
.requestMatchers("/login", "/register", "/api/public/**", "/api/auth/**", "/api/verification/**", "/api/email/**", "/api/tencent/**", "/api/test/**", "/api/polling/**", "/api/diagnostic/**", "/api/polling-diagnostic/**", "/api/monitor/**", "/css/**", "/js/**", "/h2-console/**").permitAll()
|
||||
.requestMatchers("/api/orders/stats").permitAll() // 统计接口允许匿名访问
|
||||
.requestMatchers("/api/orders/**").authenticated() // 订单接口需要认证
|
||||
.requestMatchers("/api/payments/**").authenticated() // 支付接口需要认证
|
||||
.requestMatchers("/api/payments/alipay/notify", "/api/payments/alipay/return").permitAll() // 支付宝回调接口允许匿名访问(外部调用)
|
||||
.requestMatchers("/api/payments/**").authenticated() // 其他支付接口需要认证
|
||||
.requestMatchers("/api/image-to-video/**").authenticated() // 图生视频接口需要认证
|
||||
.requestMatchers("/api/text-to-video/**").authenticated() // 文生视频接口需要认证
|
||||
.requestMatchers("/api/dashboard/**").hasRole("ADMIN") // 仪表盘API需要管理员权限
|
||||
|
||||
@@ -101,11 +101,55 @@ public class PaymentController {
|
||||
@ResponseBody
|
||||
public String alipayNotify(HttpServletRequest request) {
|
||||
try {
|
||||
Map<String, String> params = request.getParameterMap().entrySet().stream()
|
||||
.collect(java.util.stream.Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
entry -> entry.getValue()[0]
|
||||
));
|
||||
// 支付宝异步通知参数获取
|
||||
// 注意:IJPay的AliPayApi.toMap()使用javax.servlet,但Spring Boot 3使用jakarta.servlet
|
||||
// 所以手动获取参数
|
||||
Map<String, String> params = new java.util.HashMap<>();
|
||||
|
||||
// 获取URL参数
|
||||
request.getParameterMap().forEach((key, values) -> {
|
||||
if (values != null && values.length > 0) {
|
||||
params.put(key, values[0]);
|
||||
}
|
||||
});
|
||||
|
||||
// 如果是POST请求,尝试从请求体获取参数
|
||||
if ("POST".equalsIgnoreCase(request.getMethod())) {
|
||||
try {
|
||||
// 读取请求体
|
||||
StringBuilder body = new StringBuilder();
|
||||
try (java.io.BufferedReader reader = request.getReader()) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
body.append(line);
|
||||
}
|
||||
}
|
||||
|
||||
// 解析请求体参数(如果存在)
|
||||
if (body.length() > 0) {
|
||||
String bodyStr = body.toString();
|
||||
// 支付宝可能使用form-urlencoded格式
|
||||
if (bodyStr.contains("=")) {
|
||||
String[] pairs = bodyStr.split("&");
|
||||
for (String pair : pairs) {
|
||||
String[] keyValue = pair.split("=", 2);
|
||||
if (keyValue.length == 2) {
|
||||
try {
|
||||
params.put(
|
||||
java.net.URLDecoder.decode(keyValue[0], "UTF-8"),
|
||||
java.net.URLDecoder.decode(keyValue[1], "UTF-8")
|
||||
);
|
||||
} catch (Exception e) {
|
||||
logger.warn("解析参数失败: {}", pair, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("读取请求体失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
boolean success = alipayService.handleNotify(params);
|
||||
return success ? "success" : "fail";
|
||||
@@ -122,11 +166,13 @@ public class PaymentController {
|
||||
@GetMapping("/alipay/return")
|
||||
public String alipayReturn(HttpServletRequest request, Model model) {
|
||||
try {
|
||||
Map<String, String> params = request.getParameterMap().entrySet().stream()
|
||||
.collect(java.util.stream.Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
entry -> entry.getValue()[0]
|
||||
));
|
||||
// 支付宝同步返回参数获取(GET请求,从URL参数获取)
|
||||
Map<String, String> params = new java.util.HashMap<>();
|
||||
request.getParameterMap().forEach((key, values) -> {
|
||||
if (values != null && values.length > 0) {
|
||||
params.put(key, values[0]);
|
||||
}
|
||||
});
|
||||
|
||||
boolean success = alipayService.handleReturn(params);
|
||||
|
||||
|
||||
@@ -68,3 +68,4 @@ public class MailMessage {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -202,3 +202,4 @@ public class PointsFreezeRecord {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -270,3 +270,4 @@ public class TaskQueue {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -262,3 +262,4 @@ public class TaskStatus {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -70,3 +70,4 @@ public interface TaskStatusRepository extends JpaRepository<TaskStatus, Long> {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -39,5 +39,6 @@ public class PlainTextPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,16 +10,18 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.alipay.api.AlipayClient;
|
||||
import com.alipay.api.DefaultAlipayClient;
|
||||
import com.alipay.api.domain.AlipayTradePrecreateModel;
|
||||
import com.alipay.api.internal.util.AlipaySignature;
|
||||
import com.alipay.api.request.AlipayTradePrecreateRequest;
|
||||
import com.alipay.api.response.AlipayTradePrecreateResponse;
|
||||
import com.example.demo.model.Payment;
|
||||
import com.example.demo.model.PaymentMethod;
|
||||
import com.example.demo.model.PaymentStatus;
|
||||
import com.example.demo.repository.PaymentRepository;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ijpay.alipay.AliPayApi;
|
||||
import com.ijpay.alipay.AliPayApiConfig;
|
||||
import com.ijpay.alipay.AliPayApiConfigKit;
|
||||
import com.example.demo.config.PaymentConfig.AliPayApiConfigHolder;
|
||||
|
||||
@Service
|
||||
public class AlipayService {
|
||||
@@ -52,8 +54,11 @@ public class AlipayService {
|
||||
@Value("${alipay.return-url}")
|
||||
private String returnUrl;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public AlipayService(PaymentRepository paymentRepository) {
|
||||
this.paymentRepository = paymentRepository;
|
||||
this.objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,11 +93,36 @@ public class AlipayService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用真实的支付宝API
|
||||
* 确保AliPayApiConfigKit中已设置配置
|
||||
* 如果未设置,从AliPayApiConfigHolder获取并设置
|
||||
* 根据IJPay源码,正确的方法是 putApiConfig
|
||||
*/
|
||||
private void ensureAliPayConfigSet() {
|
||||
try {
|
||||
// 从AliPayApiConfigHolder获取配置
|
||||
AliPayApiConfig config = AliPayApiConfigHolder.getConfig();
|
||||
if (config != null) {
|
||||
// 根据IJPay源码,使用 putApiConfig 方法设置配置
|
||||
try {
|
||||
AliPayApiConfigKit.putApiConfig(config);
|
||||
logger.debug("IJPay配置已动态设置到AliPayApiConfigKit");
|
||||
} catch (Exception e) {
|
||||
logger.warn("动态设置IJPay配置到AliPayApiConfigKit时发生异常: {}", e.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.warn("AliPayApiConfigHolder中没有配置,IJPay配置可能未初始化");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("动态设置IJPay配置时发生异常: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用真实的支付宝API(使用IJPay)
|
||||
*/
|
||||
private Map<String, Object> callRealAlipayAPI(Payment payment) throws Exception {
|
||||
// 记录配置信息
|
||||
logger.info("=== 支付宝API配置信息 ===");
|
||||
logger.info("=== 使用IJPay调用支付宝API ===");
|
||||
logger.info("网关地址: {}", gatewayUrl);
|
||||
logger.info("应用ID: {}", appId);
|
||||
logger.info("字符集: {}", charset);
|
||||
@@ -100,64 +130,112 @@ public class AlipayService {
|
||||
logger.info("通知URL: {}", notifyUrl);
|
||||
logger.info("返回URL: {}", returnUrl);
|
||||
|
||||
// 设置连接和读取超时时间(60秒),需要在创建客户端之前设置
|
||||
System.setProperty("sun.net.client.defaultConnectTimeout", "60000");
|
||||
System.setProperty("sun.net.client.defaultReadTimeout", "60000");
|
||||
logger.info("超时配置: 连接超时=60秒, 读取超时=60秒");
|
||||
|
||||
// 创建支付宝客户端
|
||||
AlipayClient alipayClient = new DefaultAlipayClient(
|
||||
gatewayUrl, appId, privateKey, "json", charset, publicKey, signType);
|
||||
|
||||
// 使用预创建API生成二维码
|
||||
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
|
||||
request.setNotifyUrl(notifyUrl);
|
||||
|
||||
// 设置业务参数
|
||||
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
|
||||
model.setOutTradeNo(payment.getOrderId());
|
||||
model.setTotalAmount(payment.getAmount().toString());
|
||||
model.setSubject(payment.getDescription() != null ? payment.getDescription() : "AIGC会员订阅");
|
||||
model.setBody(payment.getDescription() != null ? payment.getDescription() : "AIGC平台会员订阅服务");
|
||||
model.setTimeoutExpress("5m");
|
||||
|
||||
request.setBizModel(model);
|
||||
|
||||
logger.info("调用支付宝预创建API,订单号:{},金额:{},商品名称:{}",
|
||||
logger.info("调用支付宝预创建API(IJPay),订单号:{},金额:{},商品名称:{}",
|
||||
model.getOutTradeNo(), model.getTotalAmount(), model.getSubject());
|
||||
|
||||
// 调用API,增加重试机制
|
||||
AlipayTradePrecreateResponse response = null;
|
||||
int maxRetries = 3;
|
||||
// 使用IJPay调用API,增加重试机制
|
||||
String qrCode = null;
|
||||
int maxRetries = 5; // 最大重试次数
|
||||
int retryCount = 0;
|
||||
|
||||
// 在调用前确保配置已设置
|
||||
ensureAliPayConfigSet();
|
||||
|
||||
// 验证配置中的网关地址
|
||||
try {
|
||||
AliPayApiConfig config = AliPayApiConfigKit.getAliPayApiConfig();
|
||||
logger.info("=== API调用前配置验证 ===");
|
||||
logger.info("配置中的serviceUrl: {}", config.getServiceUrl());
|
||||
logger.info("配置中的appId: {}", config.getAppId());
|
||||
logger.info("配置中的charset: {}", config.getCharset());
|
||||
logger.info("配置中的signType: {}", config.getSignType());
|
||||
} catch (Exception e) {
|
||||
logger.warn("获取IJPay配置时发生异常: {}", e.getMessage());
|
||||
}
|
||||
|
||||
while (retryCount < maxRetries) {
|
||||
try {
|
||||
logger.info("正在调用支付宝API... (第{}次尝试)", retryCount + 1);
|
||||
response = alipayClient.execute(request);
|
||||
break; // 成功则跳出循环
|
||||
logger.info("正在调用支付宝API(IJPay)... (第{}次尝试,共{}次)", retryCount + 1, maxRetries);
|
||||
logger.info("API方法: alipay.trade.precreate (由AlipayTradePrecreateRequest自动设置)");
|
||||
logger.info("通知URL: {}", notifyUrl);
|
||||
|
||||
// 使用IJPay的AliPayApi调用预创建接口
|
||||
// AlipayTradePrecreateRequest会自动设置method参数为"alipay.trade.precreate"
|
||||
String responseBody = AliPayApi.tradePrecreatePayToResponse(model, notifyUrl).getBody();
|
||||
|
||||
if (responseBody == null || responseBody.isEmpty()) {
|
||||
throw new RuntimeException("IJPay API响应为空");
|
||||
}
|
||||
|
||||
logger.info("IJPay API响应: {}", responseBody);
|
||||
|
||||
// 解析JSON响应
|
||||
// IJPay返回的响应体是JSON字符串,格式为: {"alipay_trade_precreate_response":{"code":"10000","msg":"Success","qr_code":"..."},"sign":"..."}
|
||||
try {
|
||||
Map<String, Object> responseMap = objectMapper.readValue(
|
||||
responseBody,
|
||||
new TypeReference<Map<String, Object>>() {}
|
||||
);
|
||||
Map<String, Object> precreateResponse = (Map<String, Object>) responseMap.get("alipay_trade_precreate_response");
|
||||
|
||||
if (precreateResponse != null) {
|
||||
String code = (String) precreateResponse.get("code");
|
||||
String msg = (String) precreateResponse.get("msg");
|
||||
|
||||
if ("10000".equals(code)) {
|
||||
qrCode = (String) precreateResponse.get("qr_code");
|
||||
if (qrCode != null && !qrCode.isEmpty()) {
|
||||
logger.info("支付宝二维码生成成功(IJPay),订单号:{},二维码:{}", payment.getOrderId(), qrCode);
|
||||
break; // 成功则跳出循环
|
||||
} else {
|
||||
throw new RuntimeException("二维码为空,响应消息:" + msg);
|
||||
}
|
||||
} else {
|
||||
String subCode = (String) precreateResponse.get("sub_code");
|
||||
String subMsg = (String) precreateResponse.get("sub_msg");
|
||||
throw new RuntimeException("二维码生成失败:" + msg + (subMsg != null ? " - " + subMsg : "") + " (code: " + code + (subCode != null ? ", sub_code: " + subCode : "") + ")");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("无法解析响应,响应体:" + responseBody);
|
||||
}
|
||||
} catch (com.fasterxml.jackson.core.JsonProcessingException e) {
|
||||
logger.error("JSON解析失败", e);
|
||||
throw new RuntimeException("JSON解析失败:" + e.getMessage() + ",响应体:" + responseBody);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
retryCount++;
|
||||
String errorType = e.getClass().getSimpleName();
|
||||
String errorMessage = e.getMessage();
|
||||
|
||||
logger.warn("支付宝API调用失败,第{}次重试", retryCount);
|
||||
logger.warn("支付宝API调用失败(IJPay),第{}次尝试失败", retryCount);
|
||||
logger.warn("错误类型: {}", errorType);
|
||||
logger.warn("错误信息: {}", errorMessage);
|
||||
|
||||
// 根据错误类型提供诊断建议
|
||||
// 根据错误类型提供诊断建议和决定重试策略
|
||||
boolean isTimeoutError = false;
|
||||
if (errorMessage != null) {
|
||||
if (errorMessage.contains("Read timed out") || errorMessage.contains("timeout")) {
|
||||
if (errorMessage.contains("Read timed out") || errorMessage.contains("timeout")
|
||||
|| errorMessage.contains("SocketTimeoutException")) {
|
||||
isTimeoutError = true;
|
||||
logger.error("=== 网络超时错误诊断 ===");
|
||||
logger.error("可能的原因:");
|
||||
logger.error("1. 网络连接不稳定或延迟过高");
|
||||
logger.error("2. 支付宝沙箱环境响应慢(openapi.alipaydev.com)");
|
||||
logger.error("2. 支付宝沙箱环境响应慢(openapi-sandbox.dl.alipaydev.com)");
|
||||
logger.error("3. 防火墙或代理服务器阻止连接");
|
||||
logger.error("4. ngrok隧道可能已过期或不可用");
|
||||
logger.error("解决方案:");
|
||||
logger.error("1. 检查网络连接,尝试ping openapi.alipaydev.com");
|
||||
logger.error("1. 检查网络连接,尝试ping openapi-sandbox.dl.alipaydev.com");
|
||||
logger.error("2. 检查ngrok是否正常运行: {}", notifyUrl);
|
||||
logger.error("3. 考虑使用代理服务器或VPN");
|
||||
logger.error("4. 增加超时时间或重试次数");
|
||||
} else if (errorMessage.contains("Connection refused") || errorMessage.contains("ConnectException")) {
|
||||
logger.error("=== 连接拒绝错误诊断 ===");
|
||||
logger.error("无法连接到支付宝服务器: {}", gatewayUrl);
|
||||
@@ -170,12 +248,16 @@ public class AlipayService {
|
||||
}
|
||||
|
||||
if (retryCount >= maxRetries) {
|
||||
logger.error("支付宝API调用失败,已达到最大重试次数({}次)", maxRetries);
|
||||
logger.error("支付宝API调用失败(IJPay),已达到最大重试次数({}次)", maxRetries);
|
||||
logger.error("最终失败原因: {}", errorMessage);
|
||||
throw new RuntimeException("支付宝API调用失败:" + errorMessage);
|
||||
throw new RuntimeException("支付宝API调用失败(IJPay),已重试" + maxRetries + "次:" + errorMessage);
|
||||
}
|
||||
|
||||
// 根据错误类型决定等待时间:超时错误等待更长时间
|
||||
int waitTime = isTimeoutError ? 5000 : 3000; // 超时错误等待5秒,其他错误等待3秒
|
||||
logger.info("等待{}秒后重试...", waitTime / 1000);
|
||||
try {
|
||||
Thread.sleep(2000); // 等待2秒后重试
|
||||
Thread.sleep(waitTime);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("重试被中断", ie);
|
||||
@@ -183,29 +265,20 @@ public class AlipayService {
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("支付宝API响应状态:{}", response.isSuccess());
|
||||
logger.info("支付宝API响应码:{}", response.getCode());
|
||||
logger.info("支付宝API响应消息:{}", response.getMsg());
|
||||
logger.info("支付宝API响应子码:{}", response.getSubCode());
|
||||
logger.info("支付宝API响应子消息:{}", response.getSubMsg());
|
||||
|
||||
if (response.isSuccess()) {
|
||||
String qrCode = response.getQrCode();
|
||||
logger.info("支付宝二维码生成成功,订单号:{},二维码:{}", payment.getOrderId(), qrCode);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("qrCode", qrCode);
|
||||
result.put("outTradeNo", response.getOutTradeNo());
|
||||
result.put("success", true);
|
||||
|
||||
return result;
|
||||
} else {
|
||||
logger.error("支付宝二维码生成失败,订单号:{},错误信息:{},子码:{},子消息:{}",
|
||||
payment.getOrderId(), response.getMsg(), response.getSubCode(), response.getSubMsg());
|
||||
// 检查二维码是否为空
|
||||
if (qrCode == null || qrCode.isEmpty()) {
|
||||
logger.error("支付宝API调用失败(IJPay),二维码为空");
|
||||
payment.setStatus(PaymentStatus.FAILED);
|
||||
paymentRepository.save(payment);
|
||||
throw new RuntimeException("二维码生成失败:" + response.getMsg() + " - " + response.getSubMsg());
|
||||
throw new RuntimeException("支付宝API调用失败(IJPay),二维码为空");
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("qrCode", qrCode);
|
||||
result.put("outTradeNo", payment.getOrderId());
|
||||
result.put("success", true);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ ai.image.api.key=sk-jp1O4h5lfN3WWZReF48SDa2osm0o9alC9qetkgq3M7XUjJ4R
|
||||
alipay.app-id=9021000157616562
|
||||
alipay.private-key=MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCH7wPeptkJlJuoKwDqxvfJJLTOAWVkHa/TLh+wiy1tEtmwcrOwEU3GuqfkUlhij71WJIZi8KBytCwbax1QGZA/oLXvTCGJJrYrsEL624X5gGCCPKWwHRDhewsQ5W8jFxaaMXxth8GKlSW61PZD2cOQClRVEm2xnWFZ+6/7WBI7082g7ayzGCD2eowXsJyWyuEBCUSbHXkSgxVhqj5wUGIXhr8ly+pdUlJmDX5K8UG2rjJYx+0AU5UZJbOAND7d3iyDsOulHDvth50t8MOWDnDCVJ2aAgUB5FZKtOFxOmzNTsMjvzYldFztF0khbypeeMVL2cxgioIgTvjBkUwd55hZAgMBAAECggEAUjk3pARUoEDt7skkYt87nsW/QCUECY0Tf7AUpxtovON8Hgkju8qbuyvIxokwwV2k72hkiZB33Soyy9r8/iiYYoR5yGfKmUV7R+30df03ivYmamD48BCE138v8GZ31Ufv+hEY7MADSCpzihGrbNtaOdSlslfVVmyWKHHfvy9EyD6yHJGYswLpHXC/QX1TuLRRxk6Uup8qENOG/6zjGWMfxoRZFwTt80ml1mKy32YZGyJqDaQpJcdYwAHOPcnJl1emw4E+oVjiLyksl643npuTkgnZXs1iWcWSS8ojF1w/0kVDzcNh9toLg+HDuQlIHOis01VQ7lYcG4oiMOnhX1QHIQKBgQC9fgBuILjBhuCI9fHvLRdzoNC9heD54YK7xGvEV/mv90k8xcmNx+Yg5C57ASaMRtOq3b7muPiCv5wOtMT4tUCcMIwSrTNlcBM6EoTagnaGfpzOMaHGMXO4vbaw+MIynHnvXFj1rZjG1lzkV/9K36LAaHD9ZKVJaBQ9mK+0CIq/3QKBgQC3pL5GbvXj6/4ahTraXzNDQQpPGVgbHxcOioEXL4ibaOPC58puTW8HDbRvVuhl/4EEOBRVX81BSgkN8XHwTSiZdih2iOqByg+o9kixs7nlFn3Iw9BBP2/g+Wqiyi2N+9g17kfWXXVOKYz/eMXLBeOo4KhQE9wqNGyZldYzX2ywrQKBgApJmvBfqmgnUG1fHOFlS06lvm9ro0ktqxFSmp8wP4gEHt/DxSuDXMUQXk2jRFp9ReSS4VhZVnSSvoA15DO0c2uHXzNsX8v0B7cxZjEOwCyRFyZCn4vJB4VSF2cIOlLRF/Wcx9+eqxqwbJ6hAGUqOwXDJc879ZVEp0So03EsvYupAoGAAnI+Wp/VxLB7FQ1bSFdmTmoKYh1bUBks7HOp3o4yiqduCUWfK7L6XKSxF56Xv+wUYuMAWlbJXCpJTpc9xk6w0MKDLXkLbqkrZjvJohxbyJJxIICDQKtAqUWJRxvcWXzWV3mSGWfrTRw+lZSdReQRMUm01EQ/dYx3OeCGFu8Zeo0CgYAlH5YSYdJxZSoDCJeoTrkxUlFoOg8UQ7SrsaLYLwpwcwpuiWJaTrg6jwFocj+XhjQ9RtRbSBHz2wKSLdl+pXbTbqECKk85zMFl6zG3etXtTJU/dD750Ty4i8zt3+JGhvglPrQBY1CfItgml2oXa/VUVMnLCUS0WSZuPRmPYZD8dg==
|
||||
alipay.public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAksEwzuR3ASrKtTzaANqdQYKOoD44itA1TWG/6onvQr8PHNEMgcguLuJNrdeuT2PDg23byzZ9qKfEM2D5U4zbpt0/uCYLfZQyAnAWWyMvnKPoSIgrtBjnxYK6HE6fuQV3geJTcZxvP/z8dGZB0V0s6a53rzbKSLh0p4w0hWfVXlQihq3Xh4vSKB+ojdhEkIblhpWPT42NPbjVNdwPzIhUGpRy3/nsgNqVBu+ZacQ5/rCvzXU1RE0allBbjcvjymKQTS7bAE0i1Mgo1eX8njvElsfQUv5P7xQdrvZagqtIuTdP19cmsSNGdIC9Z5Po3j0z3KWPR7MrKgDuJfzkWtJR4wIDAQAB
|
||||
alipay.gateway-url=https://openapi.alipaydev.com/gateway.do
|
||||
alipay.server-url=https://openapi-sandbox.dl.alipaydev.com/gateway.do
|
||||
alipay.gateway-url=https://openapi-sandbox.dl.alipaydev.com/gateway.do
|
||||
alipay.charset=UTF-8
|
||||
alipay.sign-type=RSA2
|
||||
alipay.notify-url=https://curtly-aphorismatic-ginger.ngrok-free.dev/api/payments/alipay/notify
|
||||
|
||||
@@ -29,3 +29,4 @@ CREATE TABLE IF NOT EXISTS task_queue (
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -28,3 +28,4 @@ CREATE TABLE IF NOT EXISTS points_freeze_records (
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -31,3 +31,4 @@ CREATE TABLE task_status (
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,16 +3,13 @@
|
||||
alipay.app-id=您的APPID
|
||||
alipay.private-key=您的应用私钥
|
||||
alipay.public-key=支付宝公钥
|
||||
alipay.server-url=https://openapi.alipaydev.com/gateway.do
|
||||
alipay.domain=http://您的域名:8080
|
||||
alipay.server-url=https://openapi-sandbox.dl.alipaydev.com/gateway.do
|
||||
alipay.gateway-url=https://openapi-sandbox.dl.alipaydev.com/gateway.do
|
||||
alipay.domain=https://curtly-aphorismatic-ginger.ngrok-free.dev
|
||||
alipay.charset=UTF-8
|
||||
alipay.sign-type=RSA2
|
||||
alipay.notify-url=https://curtly-aphorismatic-ginger.ngrok-free.dev/api/payments/alipay/notify
|
||||
alipay.return-url=https://curtly-aphorismatic-ginger.ngrok-free.dev/api/payments/alipay/return
|
||||
alipay.app-cert-path=classpath:cert/alipay/appCertPublicKey.crt
|
||||
alipay.ali-pay-cert-path=classpath:cert/alipay/alipayCertPublicKey_RSA2.crt
|
||||
alipay.ali-pay-root-cert-path=classpath:cert/alipay/alipayRootCert.crt
|
||||
|
||||
# PayPal支付配置
|
||||
paypal.client-id=your_paypal_client_id_here
|
||||
paypal.client-secret=your_paypal_client_secret_here
|
||||
paypal.mode=sandbox
|
||||
paypal.return-url=http://localhost:8080/api/payments/paypal/return
|
||||
paypal.cancel-url=http://localhost:8080/api/payments/paypal/cancel
|
||||
paypal.domain=http://localhost:8080
|
||||
|
||||
@@ -575,5 +575,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -491,5 +491,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -530,5 +530,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -32,3 +32,4 @@ pause > nul
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -59,3 +59,4 @@ public class TestApiConnection {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -18,3 +18,4 @@ except Exception as e:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -26,3 +26,4 @@ else:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -62,3 +62,4 @@ if __name__ == "__main__":
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user