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