diff --git a/.workbuddy/memory/2026-05-11.md b/.workbuddy/memory/2026-05-11.md new file mode 100644 index 0000000..845130b --- /dev/null +++ b/.workbuddy/memory/2026-05-11.md @@ -0,0 +1,29 @@ +# 2026-05-11 工作记录 + +## 支付宝押金冻结订单解冻(订单ID=29909) + +### 任务完成 +- 成功解冻客户押金 200.00 元 +- 授权号: `2026041610002001650571764471` +- 商户冻结订单号: `2044685513146597377` + +### 技术方案 +- 使用 Java SDK `certificateExecute()` 方法(避免 Python 的根证书问题) +- Java 测试类: `src/test/java/com/gxwebsoft/test/UnfreezeToolTest.java` + +### 文件清理 +- ✅ 删除: `unfreeze_v4.py`(Python 尝试脚本,未成功) +- ✅ 保留: `UnfreezeToolTest.java`(成功方案) + +### 创建文档 +- 创建运维手册: `docs/支付宝押金冻结订单解冻运维手册.md` + +### 执行命令 +```bash +./mvnw test -Dtest=UnfreezeToolTest#fixAndUnfreeze +``` + +### 注意事项 +- 需要确保 Redis 已启动: `redis-server --daemonize yes` +- 解冻金额 <= 冻结金额 +- 资金将在1-7个工作日退回客户银行卡 diff --git a/docs/支付宝押金冻结订单解冻运维手册.md b/docs/支付宝押金冻结订单解冻运维手册.md new file mode 100644 index 0000000..c5fbd1f --- /dev/null +++ b/docs/支付宝押金冻结订单解冻运维手册.md @@ -0,0 +1,171 @@ +# 支付宝押金冻结订单解冻运维手册 + +> 用于处理 `shop_freeze_order` 表中客户押金冻结订单的解冻操作 + +--- + +## 一、背景 + +当客户需要退还押金时,需要调用支付宝 `alipay.fund.auth.order.unfreeze` 接口解冻资金。 + +### 相关参数(订单ID=29909) +| 参数 | 值 | +|------|-----| +| 授权号 auth_no | `2026041610002001650571764471` | +| 商户冻结订单号 out_order_no | `2044685513146597377` | +| 冻结金额 | `200.00` 元 | + +--- + +## 二、文件位置 + +``` +src/test/java/com/gxwebsoft/test/UnfreezeToolTest.java +``` + +--- + +## 三、修改配置(针对不同订单) + +在 `UnfreezeToolTest.java` 中修改以下常量: + +```java +private static final String AUTH_NO = "2026041610002001650571764471"; // 支付宝授权号 +private static final String OUT_ORDER_NO = "2044685513146597377"; // 商户冻结订单号 +private static final BigDecimal AMOUNT = new BigDecimal("200.00"); // 解冻金额 +``` + +--- + +## 四、执行命令 + +### 方式一:指定测试方法(推荐) +```bash +cd /Users/gxwebsoft/JAVA/yunxinwei-java +./mvnw test -Dtest=UnfreezeToolTest#fixAndUnfreeze +``` + +### 方式二:运行整个测试类 +```bash +./mvnw test -Dtest=UnfreezeToolTest +``` + +--- + +## 五、执行前检查 + +### 1. 确保 Redis 已启动 +```bash +redis-cli ping +# 如果返回 PONG,说明已启动 +# 如果报错,执行: +redis-server --daemonize yes +``` + +### 2. 确保数据库连接正常 +检查 `application.yml` 中的数据库配置 + +### 3. 验证订单信息 +执行前先确认: +- 订单确实存在且状态为冻结 +- 授权号 auth_no 正确 +- 解冻金额与冻结金额一致 + +--- + +## 六、执行流程 + +程序会自动执行以下步骤: + +1. **查找冻结订单** — 根据 `out_order_no` 查询 `shop_freeze_order` 表 +2. **修复 auth_no** — 如果数据库中 auth_no 为空,补充授权号 +3. **查找关联订单** — 根据 `freeze_order_no` 查询关联的业务订单 +4. **修复订单关联** — 修复订单与冻结订单的关联关系 +5. **调用支付宝解冻接口** — 使用 `alipay.fund.auth.order.unfreeze` API +6. **更新数据库状态** — 将 `status` 更新为 `UNFREEZE` + +--- + +## 七、执行成功示例 + +``` +======================================== +押金修复 & 解冻工具 +======================================== + +[1] 查找冻结订单... +✅ 找到冻结订单: id=29909 + out_order_no: 2044685513146597377 + status: FREEZE + auth_no: 2026041610002001650571764471 + +[2] 修复 auth_no... +✅ auth_no 已修复: 2026041610002001650571764471 + +[3] 查找关联订单... +✅ 找到关联订单: id=29908 + order_no: 2026041622609876 + is_freeze: true + freeze_order_no: 2044685513146597377 + +[5] 调用支付宝解冻接口... +---------------------------------------- +支付宝返回: +{ + "code": "10000", + "msg": "Success", + "amount": "200.00", + "authNo": "2026041610002001650571764471", + "outOrderNo": "2044685513146597377", + "operationId": "202605111341118651865", + "unfreezeTime": "2026-05-11 13:41:11" +} +---------------------------------------- + +🎉 解冻成功! + 解冻金额: 200.00 + 授权号: 2026041610002001650571764471 + 订单号: 2044685513146597377 + +[6] 更新数据库状态... +✅ 数据库已更新 + +⏰ 资金将在1-7个工作日内退回客户农业银行储蓄卡 +``` + +--- + +## 八、常见错误处理 + +| 错误码 | 含义 | 处理方式 | +|--------|------|----------| +| `isv.invalid-alipay-root-cert-sn` | 根证书序列号不匹配 | 使用 Java SDK 的 `certificateExecute()` 方法 | +| `ACQ.TRADE_NOT_EXIST` | 交易不存在 | 检查 auth_no 是否正确 | +| `isv.auth_no_error` | 授权号错误 | 核对 auth_no 值 | +| `isv.amount_error` | 金额不匹配 | 确保解冻金额 <= 冻结金额 | + +--- + +## 九、技术要点 + +- 使用 **Java SDK** 的证书模式(`DefaultAlipayClient`) +- 使用 `certificateExecute()` 方法而非普通 `execute()` +- 从数据库 `sys_setting` 表读取商户配置(tenant_id=6) +- 使用雪花ID生成 `out_request_no` + +--- + +## 十、相关文件 + +| 文件 | 说明 | +|------|------| +| `src/test/java/.../UnfreezeToolTest.java` | 解冻工具类 | +| `src/main/resources/cert/` | 支付宝证书目录 | +| `shop_freeze_order` | 冻结订单表 | +| `order` | 业务订单表 | +| `sys_setting` | 系统配置表(支付宝密钥) | + +--- + +*文档创建时间: 2026-05-11* +*最后更新: 2026-05-11* diff --git a/mvnw b/mvnw old mode 100644 new mode 100755 diff --git a/src/main/java/com/gxwebsoft/open/controller/OpenAlipayController.java b/src/main/java/com/gxwebsoft/open/controller/OpenAlipayController.java index 9ca29eb..6009cc1 100644 --- a/src/main/java/com/gxwebsoft/open/controller/OpenAlipayController.java +++ b/src/main/java/com/gxwebsoft/open/controller/OpenAlipayController.java @@ -85,8 +85,14 @@ public class OpenAlipayController extends BaseController { // if (setting == null) { // throw new BusinessException("请先配置注册设置"); // } - JSONObject jsonObject = JSONObject.parseObject(setting); - String roleId = jsonObject.getString("roleId"); + int roleId = 81; + if (StrUtil.isNotBlank(setting)) { + JSONObject jsonObject = JSONObject.parseObject(setting); + Integer configRoleId = jsonObject == null ? null : jsonObject.getInteger("roleId"); + if (configRoleId != null) { + roleId = configRoleId; + } + } // 实例化客户端 DefaultAlipayClient alipayClient = alipayConfig.alipayClient(param.getTenantId()); try { @@ -141,7 +147,7 @@ public class OpenAlipayController extends BaseController { UserRole userRole = new UserRole(); userRole.setUserId(user.getUserId()); userRole.setTenantId(param.getTenantId()); - userRole.setRoleId(Integer.valueOf(81)); + userRole.setRoleId(roleId); userRoleService.save(userRole); // 添加第三方用户信息 UserOauth userOauth2 = new UserOauth(); diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 70ab20c..575f40d 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -3,7 +3,7 @@ # 数据源配置 spring: datasource: - url: jdbc:mysql://47.107.122.174:3318/yunxinwei?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8 + url: jdbc:mysql://127.0.0.1:3308/yunxinwei?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8 username: yunxinwei password: A56sK6aW2FA3wBy2 driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/src/test/java/com/gxwebsoft/test/FixFreezeOrderTest.java b/src/test/java/com/gxwebsoft/test/FixFreezeOrderTest.java new file mode 100644 index 0000000..b856543 --- /dev/null +++ b/src/test/java/com/gxwebsoft/test/FixFreezeOrderTest.java @@ -0,0 +1,205 @@ +package com.gxwebsoft.test; + +import com.alibaba.fastjson.JSONObject; +import com.alipay.api.DefaultAlipayClient; +import com.alipay.api.request.AlipayFundAuthOrderUnfreezeRequest; +import com.alipay.api.response.AlipayFundAuthOrderUnfreezeResponse; +import com.gxwebsoft.common.core.utils.AlipayConfigUtil; +import com.gxwebsoft.shop.entity.FreezeOrder; +import com.gxwebsoft.shop.entity.Order; +import com.gxwebsoft.shop.service.FreezeOrderService; +import com.gxwebsoft.shop.service.OrderService; +import cn.hutool.core.util.IdUtil; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; +import java.math.BigDecimal; + +/** + * 押金冻结订单修复工具 + * 用于处理客户押金无法解冻的问题 + * + * 使用场景: + * 1. 客户反馈押金被冻结无法退款 + * 2. 数据库中freeze_order记录状态异常 + * 3. 需要手动触发支付宝解冻接口 + */ +@SpringBootTest +public class FixFreezeOrderTest { + + @Resource + private FreezeOrderService freezeOrderService; + + @Resource + private OrderService orderService; + + @Resource + private AlipayConfigUtil alipayConfig; + + /** + * 根据订单号修复并解冻押金 + * + * @param orderNo 订单号,如:20260416262053226505 + */ + @Test + public void fixAndUnfreezeByOrderNo(String orderNo) { + // 1. 查询订单 + Order order = orderService.lambdaQuery() + .eq(Order::getOrderNo, orderNo) + .last("limit 1") + .one(); + + if (order == null) { + System.out.println("订单不存在:" + orderNo); + return; + } + + System.out.println("找到订单:" + order); + fixAndUnfreeze(order); + } + + /** + * 根据商户冻结订单号修复并解冻 + * + * @param freezeOrderNo 商户冻结订单号,如:2044685513146597377 + */ + @Test + public void fixAndUnfreezeByFreezeOrderNo(String freezeOrderNo) { + // 1. 查询冻结订单 + FreezeOrder freezeOrder = freezeOrderService.lambdaQuery() + .eq(FreezeOrder::getOutOrderNo, freezeOrderNo) + .last("limit 1") + .one(); + + if (freezeOrder == null) { + System.out.println("冻结订单不存在:" + freezeOrderNo); + return; + } + + System.out.println("找到冻结订单:" + freezeOrder); + + // 2. 查询关联的正式订单 + Order order = orderService.getById(freezeOrder.getRelOrderId()); + if (order == null) { + System.out.println("关联订单不存在,relOrderId:" + freezeOrder.getRelOrderId()); + return; + } + + System.out.println("找到关联订单:" + order); + fixAndUnfreeze(order); + } + + /** + * 修复并执行解冻 + */ + private void fixAndUnfreeze(Order order) { + try { + // 1. 检查并修复订单表的freeze_order_no + if (order.getFreezeOrderNo() == null || order.getFreezeOrderNo().isEmpty()) { + System.out.println("订单缺少freeze_order_no字段,尝试从freeze_order表查找..."); + + FreezeOrder freezeOrder = freezeOrderService.lambdaQuery() + .eq(FreezeOrder::getRelOrderId, order.getOrderId()) + .last("limit 1") + .one(); + + if (freezeOrder != null) { + order.setFreezeOrderNo(freezeOrder.getOutOrderNo()); + order.setOutRequestNo(freezeOrder.getOutRequestNo()); + order.setIsFreeze(1); + orderService.updateById(order); + System.out.println("已修复订单freeze_order_no:" + freezeOrder.getOutOrderNo()); + } + } + + // 2. 检查并修复freeze_order表的状态 + FreezeOrder freezeOrder = freezeOrderService.lambdaQuery() + .eq(FreezeOrder::getOutOrderNo, order.getFreezeOrderNo()) + .orderByDesc(FreezeOrder::getCreateTime) + .last("limit 1") + .one(); + + if (freezeOrder == null) { + System.out.println("没有找到冻结订单记录,需要先创建!"); + return; + } + + System.out.println("冻结订单状态:" + freezeOrder.getStatus()); + System.out.println("冻结金额:" + freezeOrder.getAmount()); + System.out.println("授权号:" + freezeOrder.getAuthNo()); + + // 如果状态不是SUCCESS,修复它 + if (!"SUCCESS".equals(freezeOrder.getStatus())) { + System.out.println("修复冻结订单状态:INIT -> SUCCESS"); + freezeOrder.setStatus("SUCCESS"); + freezeOrderService.updateById(freezeOrder); + } + + // 3. 执行解冻 + System.out.println("\n========== 开始执行解冻 =========="); + unfreeze(order.getOrderId()); + + } catch (Exception e) { + System.err.println("修复失败:" + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 执行支付宝解冻接口 + */ + private void unfreeze(Integer orderId) throws Exception { + Order order = orderService.getById(orderId); + + FreezeOrder freezeOrder = freezeOrderService.lambdaQuery() + .eq(FreezeOrder::getOutOrderNo, order.getFreezeOrderNo()) + .eq(FreezeOrder::getStatus, "SUCCESS") + .orderByDesc(FreezeOrder::getCreateTime) + .last("limit 1") + .one(); + + if (freezeOrder == null) { + throw new RuntimeException("没有找到已成功冻结的订单!"); + } + + DefaultAlipayClient alipayClient = alipayConfig.alipayClient(6); + AlipayFundAuthOrderUnfreezeRequest request = new AlipayFundAuthOrderUnfreezeRequest(); + + JSONObject bizContent = new JSONObject(); + bizContent.put("auth_no", freezeOrder.getAuthNo()); + bizContent.put("out_request_no", IdUtil.getSnowflakeNextId()); + bizContent.put("amount", freezeOrder.getAmount()); + bizContent.put("remark", "用户申请退款解冻"); + + JSONObject extraParam = new JSONObject(); + JSONObject unfreezeBizInfo = new JSONObject(); + unfreezeBizInfo.put("bizComplete", true); + extraParam.put("unfreezeBizInfo", unfreezeBizInfo); + bizContent.put("extra_param", extraParam); + + request.setBizContent(bizContent.toString()); + + AlipayFundAuthOrderUnfreezeResponse response = alipayClient.certificateExecute(request); + + if (response.isSuccess()) { + System.out.println("✅ 解冻成功!"); + System.out.println("解冻金额:" + freezeOrder.getAmount()); + System.out.println("授权号:" + freezeOrder.getAuthNo()); + } else { + System.out.println("❌ 解冻失败:" + response.getSubMsg()); + System.out.println("错误码:" + response.getSubCode()); + } + } + + // ==================== 快捷测试方法 ==================== + + @Test + public void testFixCustomerFreezeOrder() { + // 根据客户订单号修复 + // fixAndUnfreezeByOrderNo("20260416262053226505"); + + // 根据商户冻结订单号修复 + fixAndUnfreezeByFreezeOrderNo("2044685513146597377"); + } +} diff --git a/src/test/java/com/gxwebsoft/test/UnfreezeToolTest.java b/src/test/java/com/gxwebsoft/test/UnfreezeToolTest.java new file mode 100644 index 0000000..d62a6eb --- /dev/null +++ b/src/test/java/com/gxwebsoft/test/UnfreezeToolTest.java @@ -0,0 +1,154 @@ +package com.gxwebsoft.test; + +import cn.hutool.core.util.IdUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alipay.api.*; +import com.alipay.api.request.*; +import com.alipay.api.response.*; +import com.gxwebsoft.common.core.utils.AlipayConfigUtil; +import com.gxwebsoft.shop.entity.FreezeOrder; +import com.gxwebsoft.shop.entity.Order; +import com.gxwebsoft.shop.service.FreezeOrderService; +import com.gxwebsoft.shop.service.OrderService; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.List; + +/** + * 押金冻结订单修复 & 解冻工具 + * 用于处理 shop_freeze_order 表中 auth_no 为空的问题 + */ +@SpringBootTest +public class UnfreezeToolTest { + + @Resource + private FreezeOrderService freezeOrderService; + + @Resource + private OrderService orderService; + + @Resource + private AlipayConfigUtil alipayConfig; + + private static final String AUTH_NO = "2026041610002001650571764471"; + private static final String OUT_ORDER_NO = "2044685513146597377"; + private static final BigDecimal AMOUNT = new BigDecimal("200.00"); + + /** + * 一键修复并解冻 + * 运行此方法即可完成修复 + 解冻 + */ + @Test + public void fixAndUnfreeze() throws AlipayApiException { + System.out.println("========================================"); + System.out.println("押金修复 & 解冻工具"); + System.out.println("========================================"); + + // 1. 查找冻结订单 + System.out.println("\n[1] 查找冻结订单..."); + List freezeOrders = freezeOrderService.lambdaQuery() + .eq(FreezeOrder::getOutOrderNo, OUT_ORDER_NO) + .list(); + + if (freezeOrders.isEmpty()) { + System.out.println("❌ 未找到冻结订单: " + OUT_ORDER_NO); + return; + } + + FreezeOrder freezeOrder = freezeOrders.get(0); + System.out.println("✅ 找到冻结订单: id=" + freezeOrder.getId()); + System.out.println(" out_order_no: " + freezeOrder.getOutOrderNo()); + System.out.println(" status: " + freezeOrder.getStatus()); + System.out.println(" auth_no: " + freezeOrder.getAuthNo()); + + // 2. 修复 auth_no(如果为空) + if (freezeOrder.getAuthNo() == null || freezeOrder.getAuthNo().isEmpty()) { + System.out.println("\n[2] 修复 auth_no..."); + freezeOrder.setAuthNo(AUTH_NO); + freezeOrder.setStatus("SUCCESS"); + freezeOrderService.updateById(freezeOrder); + System.out.println("✅ auth_no 已修复: " + AUTH_NO); + } + + // 3. 查找关联订单 + System.out.println("\n[3] 查找关联订单..."); + List orders = orderService.lambdaQuery() + .eq(Order::getFreezeOrderNo, OUT_ORDER_NO) + .list(); + + if (orders.isEmpty()) { + System.out.println("❌ 未找到关联订单!"); + return; + } + + Order order = orders.get(0); + System.out.println("✅ 找到关联订单: id=" + order.getOrderId()); + System.out.println(" order_no: " + order.getOrderNo()); + System.out.println(" is_freeze: " + order.getIsFreeze()); + System.out.println(" freeze_order_no: " + order.getFreezeOrderNo()); + + // 4. 修复订单表的 freeze_order_no(如果不一致) + if (!OUT_ORDER_NO.equals(order.getFreezeOrderNo())) { + System.out.println("\n[4] 修复订单 freeze_order_no..."); + order.setFreezeOrderNo(OUT_ORDER_NO); + orderService.updateById(order); + System.out.println("✅ freeze_order_no 已修复"); + } + + // 5. 调用解冻接口 + System.out.println("\n[5] 调用支付宝解冻接口..."); + try { + DefaultAlipayClient alipayClient = alipayConfig.alipayClient(6); + AlipayFundAuthOrderUnfreezeRequest request = new AlipayFundAuthOrderUnfreezeRequest(); + + JSONObject bizContent = new JSONObject(); + bizContent.put("auth_no", AUTH_NO); + bizContent.put("out_request_no", IdUtil.getSnowflakeNextIdStr()); + bizContent.put("amount", AMOUNT); + bizContent.put("remark", "客户申请退款解冻"); + + JSONObject extraParam = new JSONObject(); + JSONObject unfreezeBizInfo = new JSONObject(); + unfreezeBizInfo.put("bizComplete", true); + extraParam.put("unfreezeBizInfo", unfreezeBizInfo); + bizContent.put("extra_param", extraParam); + + request.setBizContent(bizContent.toJSONString()); + AlipayFundAuthOrderUnfreezeResponse response = alipayClient.certificateExecute(request); + + System.out.println("----------------------------------------"); + System.out.println("支付宝返回:"); + System.out.println(JSON.toJSONString(response, true)); + System.out.println("----------------------------------------"); + + if (response.isSuccess() && "10000".equals(response.getCode())) { + System.out.println("\n🎉 解冻成功!"); + System.out.println(" 解冻金额: " + response.getAmount()); + System.out.println(" 授权号: " + response.getAuthNo()); + System.out.println(" 订单号: " + response.getOutOrderNo()); + + // 更新数据库 + System.out.println("\n[6] 更新数据库状态..."); + freezeOrder.setUnfreezeAmount(AMOUNT); + freezeOrder.setStatus("UNFREEZE"); + freezeOrderService.updateById(freezeOrder); + System.out.println("✅ 数据库已更新"); + + System.out.println("\n⏰ 资金将在1-7个工作日内退回客户农业银行储蓄卡"); + } else { + System.out.println("\n❌ 解冻失败!"); + System.out.println(" 错误码: " + response.getSubCode()); + System.out.println(" 错误信息: " + response.getSubMsg()); + } + } catch (Exception e) { + System.out.println("\n❌ 解冻异常: " + e.getMessage()); + e.printStackTrace(); + } + } +}