diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/client/ClientOrderInfoController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/client/ClientOrderInfoController.java
index 15e943d..a37d1b2 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/client/ClientOrderInfoController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/client/ClientOrderInfoController.java
@@ -73,18 +73,18 @@ public class ClientOrderInfoController extends BaseController
}
}
- /**
- * 确认出货
- */
- @Log(title = "确认出货", businessType = BusinessType.UPDATE)
- @PostMapping("/confirmShipment")
- public AjaxResult confirmShipment(@RequestParam("orderId") String orderId) {
- try {
- return orderInfoService.confirmShipment(orderId);
- } catch (Exception e) {
- return AjaxResult.error("确认出货失败: " + e.getMessage());
- }
- }
+ // /**
+ // * 确认出货
+ // */
+ // @Log(title = "确认出货", businessType = BusinessType.UPDATE)
+ // @PostMapping("/confirmShipment")
+ // public AjaxResult confirmShipment(@RequestParam("orderId") String orderId) {
+ // try {
+ // return orderInfoService.confirmShipment(orderId);
+ // } catch (Exception e) {
+ // return AjaxResult.error("确认出货失败: " + e.getMessage());
+ // }
+ // }
/**
* 申请退款
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
index c92759b..36159ab 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
@@ -1,18 +1,24 @@
package com.ruoyi.common.utils;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
import org.springframework.util.AntPathMatcher;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.text.StrFormatter;
/**
* 字符串工具类
- *
+ *
* @author ruoyi
*/
public class StringUtils extends org.apache.commons.lang3.StringUtils
@@ -28,7 +34,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 获取参数不为空值
- *
+ *
* @param value defaultValue 要判断的value
* @return value 返回值
*/
@@ -39,7 +45,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* * 判断一个Collection是否为空, 包含List,Set,Queue
- *
+ *
* @param coll 要判断的Collection
* @return true:为空 false:非空
*/
@@ -50,7 +56,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* * 判断一个Collection是否非空,包含List,Set,Queue
- *
+ *
* @param coll 要判断的Collection
* @return true:非空 false:空
*/
@@ -61,7 +67,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* * 判断一个对象数组是否为空
- *
+ *
* @param objects 要判断的对象数组
** @return true:为空 false:非空
*/
@@ -72,7 +78,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* * 判断一个对象数组是否非空
- *
+ *
* @param objects 要判断的对象数组
* @return true:非空 false:空
*/
@@ -83,7 +89,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* * 判断一个Map是否为空
- *
+ *
* @param map 要判断的Map
* @return true:为空 false:非空
*/
@@ -94,7 +100,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* * 判断一个Map是否为空
- *
+ *
* @param map 要判断的Map
* @return true:非空 false:空
*/
@@ -105,7 +111,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* * 判断一个字符串是否为空串
- *
+ *
* @param str String
* @return true:为空 false:非空
*/
@@ -116,7 +122,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* * 判断一个字符串是否为非空串
- *
+ *
* @param str String
* @return true:非空串 false:空串
*/
@@ -127,7 +133,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* * 判断一个对象是否为空
- *
+ *
* @param object Object
* @return true:为空 false:非空
*/
@@ -138,7 +144,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* * 判断一个对象是否非空
- *
+ *
* @param object Object
* @return true:非空 false:空
*/
@@ -149,7 +155,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* * 判断一个对象是否是数组类型(Java基本型别的数组)
- *
+ *
* @param object 对象
* @return true:是数组 false:不是数组
*/
@@ -211,7 +217,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 截取字符串
- *
+ *
* @param str 字符串
* @param start 开始
* @return 结果
@@ -242,7 +248,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 截取字符串
- *
+ *
* @param str 字符串
* @param start 开始
* @param end 结束
@@ -288,7 +294,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 在字符串中查找第一个出现的 `open` 和最后一个出现的 `close` 之间的子字符串
- *
+ *
* @param str 要截取的字符串
* @param open 起始字符串
* @param close 结束字符串
@@ -314,7 +320,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 判断是否为空,并且不是空白字符
- *
+ *
* @param str 要判断的value
* @return 结果
*/
@@ -344,7 +350,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
* 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
* 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
* 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
- *
+ *
* @param template 文本模板,被替换的部分用 {} 表示
* @param params 参数值
* @return 格式化后的文本
@@ -360,7 +366,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 是否为http(s)://开头
- *
+ *
* @param link 链接
* @return 结果
*/
@@ -371,7 +377,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 字符串转set
- *
+ *
* @param str 字符串
* @param sep 分隔符
* @return set集合
@@ -383,7 +389,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 字符串转list
- *
+ *
* @param str 字符串
* @param sep 分隔符
* @return list集合
@@ -395,7 +401,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 字符串转list
- *
+ *
* @param str 字符串
* @param sep 分隔符
* @param filterBlank 过滤纯空白
@@ -532,7 +538,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 是否包含字符串
- *
+ *
* @param str 验证字符串
* @param strs 字符串组
* @return 包含返回true
@@ -554,7 +560,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
- *
+ *
* @param name 转换前的下划线大写方式命名的字符串
* @return 转换后的驼峰式命名的字符串
*/
@@ -628,7 +634,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
- *
+ *
* @param str 指定字符串
* @param strs 需要检查的字符串数组
* @return 是否匹配
@@ -650,11 +656,11 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
}
/**
- * 判断url是否与规则配置:
- * ? 表示单个字符;
- * * 表示一层路径内的任意字符串,不可跨层级;
+ * 判断url是否与规则配置:
+ * ? 表示单个字符;
+ * * 表示一层路径内的任意字符串,不可跨层级;
* ** 表示任意层路径;
- *
+ *
* @param pattern 匹配规则
* @param url 需要匹配的url
* @return
@@ -673,7 +679,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
- *
+ *
* @param num 数字对象
* @param size 字符串指定长度
* @return 返回数字的字符串格式,该字符串为指定长度。
@@ -685,7 +691,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
/**
* 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。
- *
+ *
* @param s 原始字符串
* @param size 字符串指定长度
* @param c 用于补齐的字符
@@ -719,4 +725,42 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
}
return sb.toString();
}
-}
\ No newline at end of file
+
+ public static String convertToBeijingTime(String gmtTime) {
+ try {
+ // 移除时区标识
+ String timeStr = gmtTime.replace(" Etc/GMT", "");
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ // 解析为本地时间
+ LocalDateTime localDateTime = LocalDateTime.parse(timeStr, formatter);
+
+ // GMT时间加8小时就是北京时间
+ LocalDateTime beijingTime = localDateTime.plusHours(8);
+
+ return beijingTime.format(formatter);
+
+ } catch (Exception e) {
+ throw new RuntimeException("时间转换失败: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 将GMT时间字符串转换为时间戳(毫秒)
+ */
+ public static long convertToTimestampMillis(String gmtTime) {
+ try {
+ String timeStr = gmtTime.replace(" Etc/GMT", "");
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ LocalDateTime localDateTime = LocalDateTime.parse(timeStr, formatter);
+ ZonedDateTime gmtDateTime = localDateTime.atZone(ZoneId.of("GMT"));
+
+ return gmtDateTime.toInstant().toEpochMilli();
+
+ } catch (Exception e) {
+ throw new RuntimeException("时间戳转换失败: " + e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml
index 077672c..f855995 100644
--- a/ruoyi-system/pom.xml
+++ b/ruoyi-system/pom.xml
@@ -30,6 +30,12 @@
* 支持的订阅产品ID:
* - com.mingyue.product.month (月付)
* - com.mingyue.product.quarterly (季付)
* - com.mingyue.product.semiAnnual (半年付)
* - com.mingyue.product.annual (年付)
*
- * @param url 支付的环境校验地址
- * @param code 接口传递的 receipt
+ * @param url 支付的环境校验地址
+ * @param code 接口传递的 receipt
* @param userId 用户ID
* @return 结果
*/
@@ -177,14 +177,14 @@ public class AppleyPay {
// 处理 latest_receipt_info(可能是数组或单个对象)
InAppTransaction latestTransaction = null;
if (latestReceiptInfoObj instanceof JSONArray) {
- // 如果是数组,取最后一个(最新的交易)
+ // 如果是数组,取第一个(最新的交易)
JSONArray receiptArray = (JSONArray) latestReceiptInfoObj;
if (receiptArray.isEmpty()) {
log.error("latest_receipt_info 数组为空");
return null;
}
// 获取最新的交易记录(数组最后一个元素)
- Object lastReceipt = receiptArray.get(receiptArray.size() - 1);
+ Object lastReceipt = receiptArray.get(0);
latestTransaction = JSONUtil.toBean(lastReceipt.toString(), InAppTransaction.class);
} else {
// 如果是单个对象
@@ -200,11 +200,12 @@ public class AppleyPay {
String transactionId = latestTransaction.getTransactionId();
String productId = latestTransaction.getProductId();
// 获取过期时间(北京时间)
- Long expiresDateMsInBeijing = latestTransaction.getExpiresDateMsInBeijing();
+ String expiresData = latestTransaction.getExpiresDate();
+ //long expiresDateMsInBeijing = StringUtils.convertToTimestampMillis(expiresData);
// 查询交易信息是否已经存在
OrderInfo selectOrderInfo = orderInfoMapper.selectByTradeNo(transactionId);
- if (selectOrderInfo != null){
+ if (selectOrderInfo != null) {
log.warn("交易ID {} 已存在,可能重复处理。用户ID: {}", transactionId, userId);
// 交易信息已存在
return null;
@@ -230,15 +231,14 @@ public class AppleyPay {
}
}
- // 验证订阅是否过期(对于订阅类型,使用北京时间)
- if (expiresDateMsInBeijing != null) {
- long currentTimestamp = System.currentTimeMillis();
- if (expiresDateMsInBeijing < currentTimestamp) {
- log.warn("订阅已过期。交易ID: {}, 过期时间(北京时间): {}, 当前时间: {}",
- transactionId, new Date(expiresDateMsInBeijing), new Date(currentTimestamp));
- return null;
- }
- }
+ // 验证订阅是否过期(对于订阅类型,使用北京时间)TODO:// 待修改
+
+ long currentTimestamp = System.currentTimeMillis();
+ // if (expiresDateMsInBeijing < currentTimestamp) {
+ // log.warn("订阅已过期。交易ID: {}, 过期时间(北京时间): {}, 当前时间: {}",
+ // transactionId, new Date(expiresDateMsInBeijing), new Date(currentTimestamp));
+ // return null;
+ // }
// 根据产品ID处理不同的订阅类型
String packageType = null;
@@ -251,7 +251,7 @@ public class AppleyPay {
// 创建或更新订单记录
OrderInfo orderInfo = null;
boolean isNewOrder = false; // 标记是否为新订单
-
+
// 如果 orderParam 为 null 或 orderId 为空,需要创建新订单
if (orderParam == null || orderParam.getOrderId() == null) {
// 创建新订单
@@ -286,7 +286,7 @@ public class AppleyPay {
orderInfo.setPayTime(DateUtils.getNowDate());
orderInfo.setStartTime(startTime);
orderInfo.setTradeNo(transactionId);
- orderInfo.setCallbackContent(transactionId); // 保存交易ID
+ orderInfo.setCallbackContent(latestTransaction.getOriginalTransactionId()); // 保存交易ID
orderInfo.setCallTime(DateUtils.getNowDate());
orderInfo.setUpdateTime(DateUtils.getNowDate());
@@ -301,7 +301,7 @@ public class AppleyPay {
Date endTime = calculateEndTime(startTime, packageType);
orderInfo.setEndTime(endTime);
orderInfo.setPackageType(packageType);
- } else if ("com.mingyue.product.quarterly".equals(productId)) {
+ } else if ("com.mingyue.item.quarterly".equals(productId)) {
// 季付
packageType = "2";
orderName = "VIP会员包季度";
@@ -312,7 +312,7 @@ public class AppleyPay {
orderInfo.setOrderName(orderName);
orderInfo.setEndTime(endTime);
orderInfo.setPackageType(packageType);
- } else if ("com.mingyue.product.semiAnnual".equals(productId)) {
+ } else if ("com.mingyue.item.semiAnnual".equals(productId)) {
// 半年付
packageType = "3";
orderName = "VIP会员包半年";
@@ -323,7 +323,7 @@ public class AppleyPay {
orderInfo.setOrderName(orderName);
orderInfo.setEndTime(endTime);
orderInfo.setPackageType(packageType);
- } else if ("com.mingyue.product.annual".equals(productId)) {
+ } else if ("com.mingyue.item.annual".equals(productId)) {
// 年付
packageType = "4";
orderName = "VIP会员包年";
@@ -338,14 +338,14 @@ public class AppleyPay {
log.error("用户ID:{}, 未知的产品ID: {}", userId, productId);
return null;
}
-
+
// 根据是否为新订单选择插入或更新
if (isNewOrder) {
orderInfoService.insertOrderInfo(orderInfo);
} else {
orderInfoService.updateOrderInfo(orderInfo);
}
-
+
// 更新用户VIP时间
return shopUserService.updateShopUserVipTime(userId, packageType);
} catch (Exception e) {
@@ -357,7 +357,7 @@ public class AppleyPay {
/**
* 根据套餐类型计算结束时间
*
- * @param startTime 开始时间
+ * @param startTime 开始时间
* @param packageType 套餐类型:1-月付, 3-季付, 6-半年付, 12-年付
* @return 结束时间
*/
@@ -423,7 +423,6 @@ public class AppleyPay {
}
}
-
/**
* 注意:下面代码跟苹果支付业务无关。
* 这里的code 是前端请求苹果,苹果给前端的一个密钥(如果我们通过base64解密后,可获得signature、purchase-info、environment、pod、signing-status)这个密钥用于告诉Java服务器 想苹果服务器校验订单是否成功的参数
@@ -474,8 +473,12 @@ public class AppleyPay {
// 添加padding
switch (base64.length() % 4) {
- case 2: base64 += "=="; break;
- case 3: base64 += "="; break;
+ case 2:
+ base64 += "==";
+ break;
+ case 3:
+ base64 += "=";
+ break;
}
byte[] decodedBytes = Base64.decode(base64);
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/util/InAppTransaction.java b/ruoyi-system/src/main/java/com/ruoyi/system/util/InAppTransaction.java
index e3f2e92..026e023 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/util/InAppTransaction.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/util/InAppTransaction.java
@@ -1,5 +1,6 @@
package com.ruoyi.system.util;
+import com.ruoyi.common.utils.StringUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -129,6 +130,11 @@ public class InAppTransaction {
}
}
+ public static void main(String[] args) {
+ String expiresDateMs = "2025-11-06 13:54:04 Etc/GMT";
+ System.out.println(StringUtils.convertToTimestampMillis(expiresDateMs));
+ }
+
/**
* 获取购买日期时间戳(北京时间,毫秒)
* 将太平洋时间转换为北京时间
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/util/WeChatAppServiceExtensionExample.java b/ruoyi-system/src/main/java/com/ruoyi/system/util/WeChatAppServiceExtensionExample.java
new file mode 100644
index 0000000..bc3b12b
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/util/WeChatAppServiceExtensionExample.java
@@ -0,0 +1,106 @@
+package com.ruoyi.system.util;
+
+import com.wechat.pay.java.core.Config;
+import com.wechat.pay.java.core.RSAAutoCertificateConfig;
+import com.wechat.pay.java.core.exception.HttpException;
+import com.wechat.pay.java.core.exception.MalformedMessageException;
+import com.wechat.pay.java.core.exception.ServiceException;
+import com.wechat.pay.java.service.payments.app.AppServiceExtension;
+import com.wechat.pay.java.service.payments.app.model.CloseOrderRequest;
+import com.wechat.pay.java.service.payments.app.model.PrepayRequest;
+import com.wechat.pay.java.service.payments.app.model.PrepayWithRequestPaymentResponse;
+import com.wechat.pay.java.service.payments.app.model.QueryOrderByIdRequest;
+import com.wechat.pay.java.service.payments.app.model.QueryOrderByOutTradeNoRequest;
+import com.wechat.pay.java.service.payments.model.Transaction;
+
+public class WeChatAppServiceExtensionExample {
+ /**
+ * 商户号
+ */
+ public static String merchantId = "190000****";
+
+ /**
+ * 商户API私钥路径
+ */
+ public static String privateKeyPath = "/Users/yourname/your/path/apiclient_key.pem";
+
+ /**
+ * 商户证书序列号
+ */
+ public static String merchantSerialNumber = "5157F09EFDC096DE15EBE81A47057A72********";
+
+ /**
+ * 商户APIV3密钥
+ */
+ public static String apiV3Key = "...";
+
+ public static AppServiceExtension service;
+
+ public static void main(String[] args) {
+ // 初始化商户配置
+ Config config =
+ new RSAAutoCertificateConfig.Builder()
+ .merchantId(merchantId)
+ // 使用 com.wechat.pay.java.core.util 中的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
+ .privateKeyFromPath(privateKeyPath)
+ .merchantSerialNumber(merchantSerialNumber)
+ .apiV3Key(apiV3Key)
+ .build();
+ // 初始化服务
+ service = new AppServiceExtension.Builder().config(config).build();
+ try {
+ // ... 调用接口
+ PrepayWithRequestPaymentResponse response = prepayWithRequestPayment();
+ System.out.println(response);
+ } catch (HttpException e) { // 发送HTTP请求失败
+ // 调用e.getHttpRequest()获取请求打印日志或上报监控,更多方法见HttpException定义
+ } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
+ // 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义
+ } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
+ // 调用e.getMessage()获取信息打印日志或上报监控,更多方法见MalformedMessageException定义
+ }
+ }
+
+ /**
+ * 关闭订单
+ */
+ public static void closeOrder() {
+
+ CloseOrderRequest request = new CloseOrderRequest();
+ // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
+ // 调用接口
+ service.closeOrder(request);
+ }
+
+ /**
+ * APP支付下单,并返回APP调起支付数据
+ */
+ public static PrepayWithRequestPaymentResponse prepayWithRequestPayment() {
+ PrepayRequest request = new PrepayRequest();
+ // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
+ // 调用接口
+ return service.prepayWithRequestPayment(request);
+ }
+
+ /**
+ * 微信支付订单号查询订单
+ */
+ public static Transaction queryOrderById() {
+
+ QueryOrderByIdRequest request = new QueryOrderByIdRequest();
+ // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
+ // 调用接口
+ return service.queryOrderById(request);
+ }
+
+ /**
+ * 商户订单号查询订单
+ */
+ public static Transaction queryOrderByOutTradeNo() {
+
+ QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
+ // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
+ // 调用接口
+ return service.queryOrderByOutTradeNo(request);
+ }
+}
diff --git a/ruoyi-system/src/main/resources/mapper/system/ShopUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/ShopUserMapper.xml
index 3731d9b..2c0c4b8 100644
--- a/ruoyi-system/src/main/resources/mapper/system/ShopUserMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/ShopUserMapper.xml
@@ -69,6 +69,8 @@