# 🔥🔥🔥 支付失败 - 事务回滚问题修复 **问题时间**: 2026-01-23 **问题描述**: 支付页面显示"支付成功",但后端返回 400 错误:"Transaction rolled back because it has been marked as rollback-only" --- ## 🔍 问题分析 ### 现象 1. 前端支付页面显示"支付成功" 2. 控制台显示后端返回 400 错误 3. 错误信息:`Transaction rolled back because it has been marked as rollback-only` ### 根本原因 **问题出在 `UserPackage` 实体类的时间字段配置**: ```java @TableField(value = "create_time", fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; ``` **冲突点**: 1. `@TableField` 注解指定了自动填充(`fill = FieldFill.INSERT`) 2. `PackageServiceImpl.purchasePackage()` 方法中手动设置了这些字段 3. 如果没有配置 `MetaObjectHandler`,自动填充会失败 4. 导致事务回滚 ### 调用链 ``` 前端支付 → OrderController.payOrder() → OrderServiceImpl.payOrder() → PackageServiceImpl.purchasePackage() ← 这里出错 → userPackageMapper.insert(userPackage) ← 事务回滚 ``` --- ## 🔧 解决方案 ### 方案1: 移除自动填充配置(推荐) **优点**: 简单直接,不需要额外配置 **缺点**: 需要手动设置时间字段 #### 修改 `UserPackage.java` ```java @Data @TableName("user_package") public class UserPackage { @TableId(type = IdType.AUTO) private Long id; private Long tenantId; private Long userId; private Long packageId; private String packageName; private String packageType; private BigDecimal totalHours; private BigDecimal usedHours; private BigDecimal remainingHours; private Integer totalCount; private Integer usedCount; private Integer remainingCount; private BigDecimal price; private Integer status; private LocalDate startDate; private LocalDate expireDate; // 🔥 移除 fill 属性,只保留字段映射 @TableField(value = "create_time") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; @TableField(value = "update_time") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updateTime; } ``` --- ### 方案2: 配置 MetaObjectHandler(完整方案) **优点**: 统一管理时间字段,其他实体也能受益 **缺点**: 需要额外配置类 #### 创建 `MyMetaObjectHandler.java` ```java package com.peidu.config; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("开始插入填充..."); // 自动填充 createTime this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 自动填充 updateTime this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill(MetaObject metaObject) { log.info("开始更新填充..."); // 自动填充 updateTime this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } } ``` #### 修改 `PackageServiceImpl.java` ```java @Transactional public UserPackage purchasePackage(Long userId, Long packageId) { Package pkg = packageMapper.selectById(packageId); if (pkg == null) { throw new RuntimeException("套餐不存在"); } if (pkg.getStatus() != 1) { throw new RuntimeException("套餐已下架"); } // 创建用户套餐 UserPackage userPackage = new UserPackage(); userPackage.setUserId(userId); userPackage.setPackageId(packageId); userPackage.setPackageName(pkg.getPackageName()); userPackage.setPackageType(pkg.getPackageType()); userPackage.setTotalHours(pkg.getTotalHours()); userPackage.setUsedHours(BigDecimal.ZERO); userPackage.setRemainingHours(pkg.getTotalHours()); userPackage.setTotalCount(pkg.getServiceCount()); userPackage.setUsedCount(0); userPackage.setRemainingCount(pkg.getServiceCount()); userPackage.setPrice(pkg.getPrice()); userPackage.setStatus(1); userPackage.setStartDate(LocalDate.now()); userPackage.setExpireDate(LocalDate.now().plusDays(pkg.getValidDays())); // 🔥 移除手动设置时间字段,让 MetaObjectHandler 自动填充 // userPackage.setCreateTime(LocalDateTime.now()); // userPackage.setUpdateTime(LocalDateTime.now()); userPackageMapper.insert(userPackage); return userPackage; } ``` --- ## 🚀 推荐实施步骤 ### 步骤1: 使用方案1(快速修复) 1. 修改 `UserPackage.java`,移除 `fill` 属性 2. 重新编译后端 3. 测试支付功能 ### 步骤2: 修复前端显示逻辑 **问题**: 前端在后端返回 400 错误时仍然显示"支付成功" **修改 `peidu/uniapp/src/pages/payment/index.vue`**: ```javascript // 处理支付 async handlePay() { if (this.paying) return // ... 验证逻辑 ... this.paying = true uni.showLoading({ title: '支付中...' }) try { console.log('=== 开始支付 ===') console.log('订单ID:', this.orderId) console.log('支付方式:', this.selectedMethod) console.log('支付金额:', this.finalAmount) // 构建支付参数 const paymentData = { orderId: this.orderId, paymentMethod: this.selectedMethod, amount: this.finalAmount } // 添加优惠券 if (this.selectedCoupon) { paymentData.couponId = this.selectedCoupon.id } // 添加次卡 if (this.selectedMethod === 'timecard' && this.selectedTimecard) { paymentData.timecardId = this.selectedTimecard.id } // 添加套餐 if (this.selectedMethod === 'package' && this.selectedPackage) { paymentData.packageId = this.selectedPackage.id } console.log('支付参数:', paymentData) // 调用支付接口 const response = await orderApi.payOrder(paymentData) console.log('支付响应:', response) // 🔥 修复:检查响应状态 if (response && response.code === 200) { // 支付成功 this.handlePaymentSuccess() } else { // 🔥 修复:支付失败,抛出错误 throw new Error(response.message || '支付失败') } } catch (error) { console.error('支付失败:', error) uni.hideLoading() this.paying = false // 🔥 修复:显示详细错误信息 uni.showModal({ title: '支付失败', content: error.message || '支付过程中出现错误,请重试', showCancel: false }) } } ``` **关键修改**: 1. 检查 `response.code === 200` 才认为支付成功 2. 如果 `code !== 200`,抛出错误 3. 在 `catch` 块中显示错误信息 --- ### 步骤3: 移除重复的 `handlePay` 方法 **问题**: `payment/index.vue` 中有两个 `handlePay()` 方法定义 **修改**: 删除第一个 `handlePay()` 方法(第 456-502 行),保留第二个完整的方法 --- ## 🧪 测试步骤 ### 1. 测试支付成功场景 ``` 1. 创建订单 2. 进入支付页面 3. 选择支付方式(微信/钱包/次卡/套餐) 4. 点击"立即支付" 5. 预期:显示"支付成功",跳转到"待服务"订单列表 ``` ### 2. 测试支付失败场景 ``` 1. 创建订单 2. 进入支付页面 3. 选择钱包支付(余额不足) 4. 点击"立即支付" 5. 预期:显示"钱包余额不足"错误提示 ``` ### 3. 测试事务回滚修复 ``` 1. 创建订单 2. 进入支付页面 3. 选择任意支付方式 4. 点击"立即支付" 5. 预期: - 不再出现 "Transaction rolled back" 错误 - 支付成功或失败都有明确提示 - 订单状态正确更新 ``` --- ## 📝 修改文件清单 ### 后端修改 1. `peidu/backend/src/main/java/com/peidu/entity/UserPackage.java` - 移除 `fill` 属性 2. (可选)`peidu/backend/src/main/java/com/peidu/config/MyMetaObjectHandler.java` - 创建自动填充处理器 ### 前端修改 1. `peidu/uniapp/src/pages/payment/index.vue` - 修复支付响应处理逻辑 2. `peidu/uniapp/src/pages/payment/index.vue` - 删除重复的 `handlePay()` 方法 --- ## ⚠️ 注意事项 1. **编译顺序**: 先修改后端,重新编译,再修改前端 2. **测试覆盖**: 测试所有支付方式(微信、钱包、次卡、套餐) 3. **数据库检查**: 确认 `user_package` 表的 `create_time` 和 `update_time` 字段存在 4. **日志监控**: 观察后端日志,确认没有事务回滚错误 --- ## 🎯 预期效果 ### 修复前 - ❌ 支付页面显示"支付成功" - ❌ 后端返回 400 错误 - ❌ 事务回滚,数据未保存 - ❌ 用户体验混乱 ### 修复后 - ✅ 支付成功时显示"支付成功" - ✅ 支付失败时显示具体错误信息 - ✅ 事务正常提交,数据正确保存 - ✅ 用户体验清晰明确 --- ## 🚀 立即执行 **下一步**: 按照步骤1修复 `UserPackage.java`,重新编译后端 **预计时间**: 10分钟 **风险**: 低 - 只修改字段配置,不影响业务逻辑