更新代码:添加支付功能、任务队列系统和相关文档

This commit is contained in:
AIGC Developer
2025-11-04 18:18:49 +08:00
parent 6d834d3385
commit 0b0ad442a0
41 changed files with 904 additions and 85 deletions

View File

@@ -212,3 +212,4 @@ ngrok http 8080

110
demo/CODE_REVIEW_REPORT.md Normal file
View 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 配置初始化
- 异步通知参数获取方式
- 类型转换警告

View File

@@ -273,3 +273,4 @@ if (result.success) {

229
demo/IJPAY_USAGE_GUIDE.md Normal file
View 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

View File

@@ -293,3 +293,4 @@ grep "img2vid_abc123def456" logs/application.log

View File

@@ -294,3 +294,4 @@ public TaskQueue addTextToVideoTask(String username, String taskId) {

View File

@@ -37,5 +37,6 @@ public class PasswordChecker {

View File

@@ -285,3 +285,4 @@ ResourceNotFound.TemplateNotFound

View File

@@ -303,3 +303,4 @@ const startPolling = (taskId) => {

View File

@@ -173,3 +173,4 @@ const updateWork = async (workId, updateData) => {

View File

@@ -437,5 +437,6 @@ MIT License

View File

@@ -35,3 +35,4 @@ console.log('App.vue 加载成功')

View File

@@ -61,3 +61,4 @@ export const getWorkStats = () => {

View File

@@ -96,5 +96,6 @@

View File

@@ -9,6 +9,8 @@
:close-on-press-escape="true" :close-on-press-escape="true"
@close="handleClose" @close="handleClose"
center center
:show-close="true"
custom-class="payment-modal-dialog"
> >
<div class="payment-content"> <div class="payment-content">
<!-- 支付方式选择 --> <!-- 支付方式选择 -->
@@ -206,11 +208,46 @@ const showAgreement = () => {
background: #0a0a0a; background: #0a0a0a;
} }
.payment-modal :deep(.el-dialog) { /* 移除所有可能的白边和边框 */
background: #0a0a0a; .payment-modal :deep(.el-dialog),
.payment-modal-dialog {
background: #0a0a0a !important;
border-radius: 12px; border-radius: 12px;
border: none; border: none !important;
border-width: 0 !important;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6); 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) { .payment-modal :deep(.el-dialog__header) {
@@ -237,6 +274,7 @@ const showAgreement = () => {
padding: 24px; padding: 24px;
background: #0a0a0a; background: #0a0a0a;
color: white; color: white;
margin: 0;
} }
/* 支付方式选择 */ /* 支付方式选择 */

View File

@@ -11,6 +11,12 @@ import org.springframework.scheduling.annotation.EnableScheduling;
public class DemoApplication { public class DemoApplication {
public static void main(String[] args) { 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); SpringApplication.run(DemoApplication.class, args);
} }

View File

@@ -1,17 +1,27 @@
package com.example.demo.config; package com.example.demo.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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支付模块配置
*
* 初始化IJPay的AliPayApiConfigKit确保AliPayApi可以正常使用
*/ */
@Configuration @Configuration
@PropertySource("classpath:payment.properties") public class PaymentConfig implements ApplicationListener<ContextRefreshedEvent> {
public class PaymentConfig {
private static final Logger logger = LoggerFactory.getLogger(PaymentConfig.class);
@Bean @Bean
@ConfigurationProperties(prefix = "alipay") @ConfigurationProperties(prefix = "alipay")
@@ -19,6 +29,263 @@ public class PaymentConfig {
return new AliPayConfig(); 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 privateKey;
private String publicKey; private String publicKey;
private String serverUrl; private String serverUrl;
private String gatewayUrl;
private String domain; private String domain;
private String charset;
private String signType;
private String notifyUrl;
private String returnUrl;
private String appCertPath; private String appCertPath;
private String aliPayCertPath; private String aliPayCertPath;
private String aliPayRootCertPath; private String aliPayRootCertPath;
@@ -45,9 +317,24 @@ public class PaymentConfig {
public String getServerUrl() { return serverUrl; } public String getServerUrl() { return serverUrl; }
public void setServerUrl(String serverUrl) { this.serverUrl = 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 String getDomain() { return domain; }
public void setDomain(String domain) { this.domain = 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 String getAppCertPath() { return appCertPath; }
public void setAppCertPath(String appCertPath) { this.appCertPath = appCertPath; } public void setAppCertPath(String appCertPath) { this.appCertPath = appCertPath; }

View File

@@ -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("/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/stats").permitAll() // 统计接口允许匿名访问
.requestMatchers("/api/orders/**").authenticated() // 订单接口需要认证 .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/image-to-video/**").authenticated() // 图生视频接口需要认证
.requestMatchers("/api/text-to-video/**").authenticated() // 文生视频接口需要认证 .requestMatchers("/api/text-to-video/**").authenticated() // 文生视频接口需要认证
.requestMatchers("/api/dashboard/**").hasRole("ADMIN") // 仪表盘API需要管理员权限 .requestMatchers("/api/dashboard/**").hasRole("ADMIN") // 仪表盘API需要管理员权限

View File

@@ -101,11 +101,55 @@ public class PaymentController {
@ResponseBody @ResponseBody
public String alipayNotify(HttpServletRequest request) { public String alipayNotify(HttpServletRequest request) {
try { try {
Map<String, String> params = request.getParameterMap().entrySet().stream() // 支付宝异步通知参数获取
.collect(java.util.stream.Collectors.toMap( // 注意IJPay的AliPayApi.toMap()使用javax.servlet但Spring Boot 3使用jakarta.servlet
Map.Entry::getKey, // 所以手动获取参数
entry -> entry.getValue()[0] 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); boolean success = alipayService.handleNotify(params);
return success ? "success" : "fail"; return success ? "success" : "fail";
@@ -122,11 +166,13 @@ public class PaymentController {
@GetMapping("/alipay/return") @GetMapping("/alipay/return")
public String alipayReturn(HttpServletRequest request, Model model) { public String alipayReturn(HttpServletRequest request, Model model) {
try { try {
Map<String, String> params = request.getParameterMap().entrySet().stream() // 支付宝同步返回参数获取GET请求从URL参数获取
.collect(java.util.stream.Collectors.toMap( Map<String, String> params = new java.util.HashMap<>();
Map.Entry::getKey, request.getParameterMap().forEach((key, values) -> {
entry -> entry.getValue()[0] if (values != null && values.length > 0) {
)); params.put(key, values[0]);
}
});
boolean success = alipayService.handleReturn(params); boolean success = alipayService.handleReturn(params);

View File

@@ -68,3 +68,4 @@ public class MailMessage {

View File

@@ -202,3 +202,4 @@ public class PointsFreezeRecord {

View File

@@ -270,3 +270,4 @@ public class TaskQueue {

View File

@@ -262,3 +262,4 @@ public class TaskStatus {

View File

@@ -70,3 +70,4 @@ public interface TaskStatusRepository extends JpaRepository<TaskStatus, Long> {

View File

@@ -39,5 +39,6 @@ public class PlainTextPasswordEncoder implements PasswordEncoder {

View File

@@ -10,16 +10,18 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; 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.domain.AlipayTradePrecreateModel;
import com.alipay.api.internal.util.AlipaySignature; 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.Payment;
import com.example.demo.model.PaymentMethod; import com.example.demo.model.PaymentMethod;
import com.example.demo.model.PaymentStatus; import com.example.demo.model.PaymentStatus;
import com.example.demo.repository.PaymentRepository; 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 @Service
public class AlipayService { public class AlipayService {
@@ -52,8 +54,11 @@ public class AlipayService {
@Value("${alipay.return-url}") @Value("${alipay.return-url}")
private String returnUrl; private String returnUrl;
private final ObjectMapper objectMapper;
public AlipayService(PaymentRepository paymentRepository) { public AlipayService(PaymentRepository paymentRepository) {
this.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 { private Map<String, Object> callRealAlipayAPI(Payment payment) throws Exception {
// 记录配置信息 // 记录配置信息
logger.info("=== 支付宝API配置信息 ==="); logger.info("=== 使用IJPay调用支付宝API ===");
logger.info("网关地址: {}", gatewayUrl); logger.info("网关地址: {}", gatewayUrl);
logger.info("应用ID: {}", appId); logger.info("应用ID: {}", appId);
logger.info("字符集: {}", charset); logger.info("字符集: {}", charset);
@@ -100,64 +130,112 @@ public class AlipayService {
logger.info("通知URL: {}", notifyUrl); logger.info("通知URL: {}", notifyUrl);
logger.info("返回URL: {}", returnUrl); 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(); AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
model.setOutTradeNo(payment.getOrderId()); model.setOutTradeNo(payment.getOrderId());
model.setTotalAmount(payment.getAmount().toString()); model.setTotalAmount(payment.getAmount().toString());
model.setSubject(payment.getDescription() != null ? payment.getDescription() : "AIGC会员订阅"); model.setSubject(payment.getDescription() != null ? payment.getDescription() : "AIGC会员订阅");
model.setBody(payment.getDescription() != null ? payment.getDescription() : "AIGC平台会员订阅服务"); model.setBody(payment.getDescription() != null ? payment.getDescription() : "AIGC平台会员订阅服务");
model.setTimeoutExpress("5m");
request.setBizModel(model); logger.info("调用支付宝预创建APIIJPay订单号{},金额:{},商品名称:{}",
logger.info("调用支付宝预创建API订单号{},金额:{},商品名称:{}",
model.getOutTradeNo(), model.getTotalAmount(), model.getSubject()); model.getOutTradeNo(), model.getTotalAmount(), model.getSubject());
// 调用API增加重试机制 // 使用IJPay调用API增加重试机制
AlipayTradePrecreateResponse response = null; String qrCode = null;
int maxRetries = 3; int maxRetries = 5; // 最大重试次数
int retryCount = 0; 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) { while (retryCount < maxRetries) {
try { try {
logger.info("正在调用支付宝API... (第{}次尝试)", retryCount + 1); logger.info("正在调用支付宝APIIJPay... (第{}次尝试,共{}次)", retryCount + 1, maxRetries);
response = alipayClient.execute(request); logger.info("API方法: alipay.trade.precreate (由AlipayTradePrecreateRequest自动设置)");
break; // 成功则跳出循环 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) { } catch (Exception e) {
retryCount++; retryCount++;
String errorType = e.getClass().getSimpleName(); String errorType = e.getClass().getSimpleName();
String errorMessage = e.getMessage(); String errorMessage = e.getMessage();
logger.warn("支付宝API调用失败第{}次重试", retryCount); logger.warn("支付宝API调用失败IJPay,第{}次尝试失败", retryCount);
logger.warn("错误类型: {}", errorType); logger.warn("错误类型: {}", errorType);
logger.warn("错误信息: {}", errorMessage); logger.warn("错误信息: {}", errorMessage);
// 根据错误类型提供诊断建议 // 根据错误类型提供诊断建议和决定重试策略
boolean isTimeoutError = false;
if (errorMessage != null) { 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("可能的原因:"); logger.error("可能的原因:");
logger.error("1. 网络连接不稳定或延迟过高"); logger.error("1. 网络连接不稳定或延迟过高");
logger.error("2. 支付宝沙箱环境响应慢openapi.alipaydev.com"); logger.error("2. 支付宝沙箱环境响应慢openapi-sandbox.dl.alipaydev.com");
logger.error("3. 防火墙或代理服务器阻止连接"); logger.error("3. 防火墙或代理服务器阻止连接");
logger.error("4. ngrok隧道可能已过期或不可用"); logger.error("4. ngrok隧道可能已过期或不可用");
logger.error("解决方案:"); 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("2. 检查ngrok是否正常运行: {}", notifyUrl);
logger.error("3. 考虑使用代理服务器或VPN"); logger.error("3. 考虑使用代理服务器或VPN");
logger.error("4. 增加超时时间或重试次数");
} else if (errorMessage.contains("Connection refused") || errorMessage.contains("ConnectException")) { } else if (errorMessage.contains("Connection refused") || errorMessage.contains("ConnectException")) {
logger.error("=== 连接拒绝错误诊断 ==="); logger.error("=== 连接拒绝错误诊断 ===");
logger.error("无法连接到支付宝服务器: {}", gatewayUrl); logger.error("无法连接到支付宝服务器: {}", gatewayUrl);
@@ -170,12 +248,16 @@ public class AlipayService {
} }
if (retryCount >= maxRetries) { if (retryCount >= maxRetries) {
logger.error("支付宝API调用失败已达到最大重试次数{}次)", maxRetries); logger.error("支付宝API调用失败IJPay,已达到最大重试次数({}次)", maxRetries);
logger.error("最终失败原因: {}", errorMessage); 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 { try {
Thread.sleep(2000); // 等待2秒后重试 Thread.sleep(waitTime);
} catch (InterruptedException ie) { } catch (InterruptedException ie) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new RuntimeException("重试被中断", ie); throw new RuntimeException("重试被中断", ie);
@@ -183,29 +265,20 @@ public class AlipayService {
} }
} }
logger.info("支付宝API响应状态{}", response.isSuccess()); // 检查二维码是否为空
logger.info("支付宝API响应码{}", response.getCode()); if (qrCode == null || qrCode.isEmpty()) {
logger.info("支付宝API响应消息:{}", response.getMsg()); logger.error("支付宝API调用失败IJPay二维码为空");
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());
payment.setStatus(PaymentStatus.FAILED); payment.setStatus(PaymentStatus.FAILED);
paymentRepository.save(payment); 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;
} }

View File

@@ -46,7 +46,8 @@ ai.image.api.key=sk-jp1O4h5lfN3WWZReF48SDa2osm0o9alC9qetkgq3M7XUjJ4R
alipay.app-id=9021000157616562 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.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.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.charset=UTF-8
alipay.sign-type=RSA2 alipay.sign-type=RSA2
alipay.notify-url=https://curtly-aphorismatic-ginger.ngrok-free.dev/api/payments/alipay/notify alipay.notify-url=https://curtly-aphorismatic-ginger.ngrok-free.dev/api/payments/alipay/notify

View File

@@ -29,3 +29,4 @@ CREATE TABLE IF NOT EXISTS task_queue (

View File

@@ -28,3 +28,4 @@ CREATE TABLE IF NOT EXISTS points_freeze_records (

View File

@@ -31,3 +31,4 @@ CREATE TABLE task_status (

View File

@@ -3,16 +3,13 @@
alipay.app-id=您的APPID alipay.app-id=您的APPID
alipay.private-key=您的应用私钥 alipay.private-key=您的应用私钥
alipay.public-key=支付宝公钥 alipay.public-key=支付宝公钥
alipay.server-url=https://openapi.alipaydev.com/gateway.do alipay.server-url=https://openapi-sandbox.dl.alipaydev.com/gateway.do
alipay.domain=http://您的域名:8080 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.app-cert-path=classpath:cert/alipay/appCertPublicKey.crt
alipay.ali-pay-cert-path=classpath:cert/alipay/alipayCertPublicKey_RSA2.crt alipay.ali-pay-cert-path=classpath:cert/alipay/alipayCertPublicKey_RSA2.crt
alipay.ali-pay-root-cert-path=classpath:cert/alipay/alipayRootCert.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

View File

@@ -575,5 +575,6 @@

View File

@@ -491,5 +491,6 @@

View File

@@ -530,5 +530,6 @@

View File

@@ -32,3 +32,4 @@ pause > nul

View File

@@ -59,3 +59,4 @@ public class TestApiConnection {

View File

@@ -18,3 +18,4 @@ except Exception as e:

View File

@@ -26,3 +26,4 @@ else:

View File

@@ -62,3 +62,4 @@ if __name__ == "__main__":

View File

@@ -83,3 +83,4 @@ if __name__ == "__main__":

View File

@@ -18,3 +18,4 @@ except Exception as e: