From 7c455f974e03d8153c2d3d8b2dcd594a543131f9 Mon Sep 17 00:00:00 2001
From: zjk <852185829@qq.com>
Date: 星期三, 30 九月 2026 16:05:41 +0800
Subject: [PATCH] fix(iap, datasource): 优化并修复苹果内购支付流程,更新开发数据源配置

---
 ruoyi-service/ruoyi-goods/src/main/java/com/ruoyi/goods/config/DatasourceModel.java     |    4 
 ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/controller/TUserController.java |  143 ++++++++++++++++++++++++++++++-----------------
 ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/config/DatasourceModel.java     |    2 
 3 files changed, 95 insertions(+), 54 deletions(-)

diff --git a/ruoyi-service/ruoyi-goods/src/main/java/com/ruoyi/goods/config/DatasourceModel.java b/ruoyi-service/ruoyi-goods/src/main/java/com/ruoyi/goods/config/DatasourceModel.java
index ab70a59..8c85675 100644
--- a/ruoyi-service/ruoyi-goods/src/main/java/com/ruoyi/goods/config/DatasourceModel.java
+++ b/ruoyi-service/ruoyi-goods/src/main/java/com/ruoyi/goods/config/DatasourceModel.java
@@ -23,9 +23,9 @@
     private Integer initialSize =2;
 
 
-//    private String url = "jdbc:mysql://127.0.0.1:3306/dolphin_goods?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai";
+//    private String url = "jdbc:mysql://192.168.110.111:3306/dolphin_goods?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai";
 //    private String username = "root";
-//    private String password = "root";
+//    private String password = "123456";
 //    private String driverClassName = "com.mysql.cj.jdbc.Driver";
 //    private Integer maxActive =20;
 //    private Long maxWait = 60000L;
diff --git a/ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/config/DatasourceModel.java b/ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/config/DatasourceModel.java
index 218e50a..30a40f8 100644
--- a/ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/config/DatasourceModel.java
+++ b/ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/config/DatasourceModel.java
@@ -21,7 +21,7 @@
     private Long maxWait = 60000L;
     private Integer minIdle =5;
     private Integer initialSize =2;
-//    private String url = "jdbc:mysql://127.0.0.1:3306/dolphin_study?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai";
+//    private String url = "jdbc:mysql://192.168.110.111:3306/dolphin_study?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai";
 //    private String username = "root";
 //    private String password = "123456";
 //    private String driverClassName = "com.mysql.cj.jdbc.Driver";
diff --git a/ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/controller/TUserController.java b/ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/controller/TUserController.java
index 2c83282..904d454 100644
--- a/ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/controller/TUserController.java
+++ b/ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/controller/TUserController.java
@@ -223,8 +223,27 @@
                                 tVipOrder.getMoney().toString(),
                                 "/base/user/aliPayBuyVip");
             case 3:
+                // 苹果内购:回传流水号后立即开通会员;苹果服务器通知(/testApple)作为兜底,两条链路共用 completeVipOrder
+                if (tVipOrder == null) {
+                    return AjaxResult.error("订单不存在");
+                }
+                // 已经支付或已退款,避免重复回传覆盖状态
+                if (tVipOrder.getPayState() != null && tVipOrder.getPayState() != 1) {
+                    System.err.println("苹果支付订单无需重复处理 orderId=" + tVipOrder.getId()
+                            + ", payState=" + tVipOrder.getPayState());
+                    return AjaxResult.success();
+                }
+                if (!StringUtils.hasText(dto.getTransactionIdentifier())) {
+                    return AjaxResult.error("苹果流水号不能为空");
+                }
+                System.err.println("苹果支付回传流水号 orderId=" + tVipOrder.getId()
+                        + ", transactionIdentifier=" + dto.getTransactionIdentifier());
                 tVipOrder.setTransactionId(dto.getTransactionIdentifier());
+                // 先落库流水号,保证后续苹果通知(/testApple)能按 transactionId 匹配到本订单
                 vipOrderService.updateById(tVipOrder);
+                // 不依赖苹果通知的到达顺序:回传流水号后立即开通会员
+                // 苹果通知(/testApple)作为兜底,重复调用时由 completeVipOrder 的幂等保护避免重复开通
+                completeVipOrder(tVipOrder);
         }
         return AjaxResult.success();
     }
@@ -376,10 +395,22 @@
             System.err.println("data"+data);
             // 苹果流水号
             String string = transactionInfo.getString("originalTransactionId");
+            // 订单匹配:优先按本次交易的 transactionId 匹配(客户端支付成功后已回传 /pay 落库),
+            // 匹配不到再退回 originalTransactionId(同一订阅的自动续订没有新订单,需挂到首单上)
             TVipOrder one = vipOrderService.getOne(new QueryWrapper<TVipOrder>()
-                    .eq("transactionId", string)
-                    .eq("payType", 3));
-            System.err.println("回调通知类型"+notificationType);
+                    .eq("payType", 3)
+                    .eq("transactionId", transactionId)
+                    .last("limit 1"));
+            if (one == null) {
+                one = vipOrderService.getOne(new QueryWrapper<TVipOrder>()
+                        .eq("payType", 3)
+                        .eq("transactionId", string)
+                        .last("limit 1"));
+            }
+            System.err.println("回调通知类型"+notificationType
+                    + ", transactionId=" + transactionId
+                    + ", originalTransactionId=" + string
+                    + ", 匹配订单=" + (one == null ? "无" : one.getId()));
             if ("REFUND".equals(notificationType)){
                 if (one!=null && one.getPayState() == 2){
                     one.setPayState(3);
@@ -413,57 +444,16 @@
                         byId1.setVipPayTime(tVipOrder.getPayTime());
                         userService.updateById(byId1);
                     }
+                    // 退款成功,更新订单状态
+                    vipOrderService.updateById(one);
                 }
-                vipOrderService.updateById(one);
             }else{
                 if (one!=null){
-                    one.setPayState(2);
-                    one.setPayTime(new Date());
-
-                    TUser byId1 = userService.getById(one.getUserId());
-                    if (byId1.getVipPayTime() == null) {
-                        // 是否是首次充值会员
-                        byId1.setVipPayTime(new Date());
-                        Calendar calendar = Calendar.getInstance();
-                        calendar.setTime(new Date());
-                        calendar.add(Calendar.MONTH, one.getCount());
-                        Date dateAfterOneMonth = calendar.getTime();
-                        dateAfterOneMonth.setHours(23);
-                        dateAfterOneMonth.setMinutes(59);
-                        dateAfterOneMonth.setSeconds(59);
-                        byId1.setVipEndTime(dateAfterOneMonth);
-                        one.setTime(dateAfterOneMonth);
-                        userService.updateById(byId1);
-                    } else {
-                        // 不是首次 判断vipEndTime 是否到期 如果没有 加指定月份时间 如果到期了 将会员到期时间从当前增加指定月份
-                        if (byId1.getVipEndTime().getTime() < new Date().getTime()) {
-                            Calendar calendar = Calendar.getInstance();
-                            calendar.setTime(new Date());
-                            calendar.add(Calendar.MONTH, one.getCount());
-                            Date dateAfterOneMonth = calendar.getTime();
-                            dateAfterOneMonth.setHours(23);
-                            dateAfterOneMonth.setMinutes(59);
-                            dateAfterOneMonth.setSeconds(59);
-                            one.setTime(dateAfterOneMonth);
-                            byId1.setVipEndTime(dateAfterOneMonth);
-                            userService.updateById(byId1);
-                        } else {
-                            Calendar calendar = Calendar.getInstance();
-                            calendar.setTime(byId1.getVipEndTime());
-                            calendar.add(Calendar.MONTH, one.getCount());
-                            Date dateAfterOneMonth = calendar.getTime();
-                            dateAfterOneMonth.setHours(23);
-                            dateAfterOneMonth.setMinutes(59);
-                            dateAfterOneMonth.setSeconds(59);
-                            one.setTime(dateAfterOneMonth);
-                            byId1.setVipEndTime(dateAfterOneMonth);
-                            userService.updateById(byId1);
-                        }
-                    }
+                    // 与客户端回传流水号(/pay)共用同一段开通逻辑,保证两条链路行为一致
+                    completeVipOrder(one);
                 }
             }
 
-            vipOrderService.updateById(one);
             System.err.println("苹果流水号"+string);
             PrintWriter out = response.getWriter();
             out.write("success");
@@ -472,6 +462,47 @@
         } catch (Exception e) {
             e.printStackTrace();
         }
+    }
+
+    /**
+     * 苹果内购:将订单置为已支付,并开通/顺延会员
+     * 客户端回传流水号(/pay)与苹果服务器通知(/testApple)共用此方法,保证两条链路行为一致
+     *
+     * @param one 待支付的会员订单(非空)
+     */
+    private void completeVipOrder(TVipOrder one) {
+        // 幂等保护:已支付(2)或已退款(3)的订单直接返回,避免 /pay 与苹果通知两条链路重复顺延会员
+        if (one.getPayState() != null && one.getPayState() != 1) {
+            System.err.println("苹果订单已处理,跳过开通 orderId=" + one.getId()
+                    + ", payState=" + one.getPayState());
+            return;
+        }
+        one.setPayState(2);
+        one.setPayTime(new Date());
+
+        TUser user = userService.getById(one.getUserId());
+        Calendar calendar = Calendar.getInstance();
+        if (user.getVipPayTime() == null) {
+            // 首次充值会员:记录首次充值时间,会员到期时间从当前时间起算
+            user.setVipPayTime(new Date());
+            calendar.setTime(new Date());
+        } else if (user.getVipEndTime().getTime() < System.currentTimeMillis()) {
+            // 非首次且会员已到期:从当前时间重新起算
+            calendar.setTime(new Date());
+        } else {
+            // 非首次且会员未到期:在原到期时间基础上顺延
+            calendar.setTime(user.getVipEndTime());
+        }
+        calendar.add(Calendar.MONTH, one.getCount());
+        Date vipEndTime = calendar.getTime();
+        vipEndTime.setHours(23);
+        vipEndTime.setMinutes(59);
+        vipEndTime.setSeconds(59);
+        one.setTime(vipEndTime);
+        user.setVipEndTime(vipEndTime);
+        userService.updateById(user);
+        // 支付成功,订单状态置为已支付
+        vipOrderService.updateById(one);
     }
 
     /**
@@ -506,9 +537,18 @@
             System.err.println("data"+data);
             // 苹果流水号
             String string = transactionInfo.getString("originalTransactionId");
+            String transactionId = transactionInfo.getString("transactionId");
+            // 订单匹配:优先按本次交易的 transactionId,匹配不到再退回 originalTransactionId
             TVipOrder one = vipOrderService.getOne(new QueryWrapper<TVipOrder>()
-                    .eq("transactionId", string)
-                    .eq("payType", 3));
+                    .eq("payType", 3)
+                    .eq("transactionId", transactionId)
+                    .last("limit 1"));
+            if (one == null) {
+                one = vipOrderService.getOne(new QueryWrapper<TVipOrder>()
+                        .eq("payType", 3)
+                        .eq("transactionId", string)
+                        .last("limit 1"));
+            }
             if (one!=null && one.getPayState() == 2){
                 one.setPayState(3);
                 one.setBackTime(new Date());
@@ -540,8 +580,9 @@
                     byId1.setVipPayTime(tVipOrder.getPayTime());
                     userService.updateById(byId1);
                 }
+                // 退款成功,更新订单状态
+                vipOrderService.updateById(one);
             }
-            vipOrderService.updateById(one);
             System.err.println("苹果流水号"+string);
             PrintWriter out = response.getWriter();
             out.write("success");

--
Gitblit v1.7.1