diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/util/AppleyPay.java b/ruoyi-system/src/main/java/com/ruoyi/system/util/AppleyPay.java index a3532bd..95b215a 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/util/AppleyPay.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/util/AppleyPay.java @@ -5,10 +5,13 @@ import cn.hutool.json.JSONUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.core.domain.entity.OrderInfo; +import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; -import org.junit.Test; +import com.ruoyi.system.service.IOrderInfoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.net.ssl.HttpsURLConnection; @@ -22,7 +25,8 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.util.HashMap; +import java.util.Calendar; +import java.util.Date; import java.util.List; /** @@ -35,6 +39,9 @@ public class AppleyPay { private static final Logger log = LoggerFactory.getLogger(AppleyPay.class); + @Autowired + private IOrderInfoService orderInfoService; + //购买凭证验证地址 private static final String certificateUrl = "https://buy.itunes.apple.com/verifyReceipt"; @@ -86,17 +93,21 @@ public class AppleyPay { /** * 发送请求 向苹果发起验证支付请求是否有效:本方法有认证方法进行调用 * - * com.mingyue.product.annual - * com.mingyue.product.semiAnnual - * com.mingyue.product.quarterly - * com.mingyue.product.month - * @param url 支付的环境校验 + * 支持的订阅产品ID: + * - com.mingyue.product.month (月付) + * - com.mingyue.product.quarterly (季付) + * - com.mingyue.product.semiAnnual (半年付) + * - com.mingyue.product.annual (年付) + * + * @param url 支付的环境校验地址 * @param code 接口传递的 receipt + * @param userId 用户ID * @return 结果 */ private String sendHttpsCoon(String url, String code, String userId) { - if (url.isEmpty()) { - return null; + if (url == null || url.isEmpty()) { + log.error("验证URL为空"); + return "支付失败,验证URL为空"; } try { //设置SSLContext @@ -111,61 +122,259 @@ public class AppleyPay { conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setRequestProperty("Content-type", "application/json"); - + conn.setConnectTimeout(10000); // 10秒连接超时 + conn.setReadTimeout(10000); // 10秒读取超时 JSONObject obj = new JSONObject(); obj.put("receipt-data", code); - // 添加共享密钥 - 这里需要补充 + // 添加共享密钥 obj.put("password", "96f11cd1f6714d1fb4206fcad92854bf"); BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream()); - buffOutStr.write(obj.toString().getBytes()); + buffOutStr.write(obj.toString().getBytes(StandardCharsets.UTF_8)); buffOutStr.flush(); buffOutStr.close(); + //获取输入流 - BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); String line = null; StringBuffer sb = new StringBuffer(); while ((line = reader.readLine()) != null) { sb.append(line); } - System.out.println(sb); - // 错误的 sb对象是:{"status":21002},苹果官网写的错误也都是2XXXX 具体含义可查:https://developer.apple.com/documentation/appstorereceipts/status - // 所以 通过长度等于16,我们就可确定苹果支付成功是否有效 - if (sb.length() == 16) { - return "支付失败,苹果说status异常"; - } - // 将一个Json对象转成一个HashMap - JSONObject alljsoncode = JSON.parseObject(sb.toString()); - Object receipt = alljsoncode.get("latest_receipt_info"); - // receipt 转成LinkList - List inAppTransactions = JSONUtil.toList(receipt.toString(), InAppTransaction.class); + reader.close(); - HashMap hashMap = JSONUtil.toBean(receipt.toString(), HashMap.class); - // 苹果给的订单号 - String original_transaction_id = (String) hashMap.get("original_transaction_id"); - // 唯一ID - String transactionId = (String) hashMap.get("transaction_id"); - //TODO 存储订单ID,并检查此订单ID是否存在,如果存在就证明已经发货了(避免二次发货) - if ("com.mingyue.product.month".equals(hashMap.get("product_id"))) { - //TODO 月付 - log.info("用户ID:{},执行12元钻石追加",userId); - } else if ("com.mingyue.product.quarterly".equals(hashMap.get("product_id"))) { - //TODO 季付 - log.info("用户ID:{},执行30元钻石追加",userId); - }else if ("com.mingyue.product.semiAnnual".equals(hashMap.get("product_id"))){ - // TODo 半年付 - }else if ("com.mingyue.product.annual".equals(hashMap.get("product_id"))){ - // TODo 年付 - } else { - log.info("用户ID:{},向苹果发起验证支付请求没有次product_id",userId); - return "支付失败,当前没有次product_id"; + log.info("苹果验证响应: {}", sb.toString()); + + // 解析响应 + JSONObject alljsoncode = JSON.parseObject(sb.toString()); + Integer status = alljsoncode.getInteger("status"); + + // 检查状态码 (0表示成功,其他都是错误) + if (status == null || status != 0) { + String errorMsg = getStatusErrorMessage(status); + log.error("苹果验证失败,状态码: {}, 错误信息: {}", status, errorMsg); + return "支付失败,苹果验证失败: " + errorMsg; } - return "支付成功"; + + // 获取 latest_receipt_info,可能是数组或单个对象 + Object latestReceiptInfoObj = alljsoncode.get("latest_receipt_info"); + if (latestReceiptInfoObj == null) { + log.error("latest_receipt_info 为空"); + return "支付失败,未找到交易信息"; + } + + // 处理 latest_receipt_info(可能是数组或单个对象) + InAppTransaction latestTransaction = null; + if (latestReceiptInfoObj instanceof JSONArray) { + // 如果是数组,取最后一个(最新的交易) + JSONArray receiptArray = (JSONArray) latestReceiptInfoObj; + if (receiptArray.isEmpty()) { + log.error("latest_receipt_info 数组为空"); + return "支付失败,交易信息数组为空"; + } + // 获取最新的交易记录(数组最后一个元素) + Object lastReceipt = receiptArray.get(receiptArray.size() - 1); + latestTransaction = JSONUtil.toBean(lastReceipt.toString(), InAppTransaction.class); + } else { + // 如果是单个对象 + latestTransaction = JSONUtil.toBean(latestReceiptInfoObj.toString(), InAppTransaction.class); + } + + if (latestTransaction == null) { + log.error("无法解析交易信息"); + return "支付失败,无法解析交易信息"; + } + + // 获取关键信息 + String transactionId = latestTransaction.getTransactionId(); + String productId = latestTransaction.getProductId(); + String expiresDateMs = latestTransaction.getExpiresDateMs(); + + if (StringUtils.isEmpty(transactionId)) { + log.error("transaction_id 为空"); + return "支付失败,交易ID为空"; + } + + // 检查订单是否已处理(防重复发货) + OrderInfo queryOrder = new OrderInfo(); + queryOrder.setTradeNo(transactionId); + queryOrder.setPayType("applePay"); + List existingOrders = orderInfoService.selectOrderInfoList(queryOrder); + if (existingOrders != null && !existingOrders.isEmpty()) { + log.warn("交易ID {} 已存在,可能重复处理。用户ID: {}", transactionId, userId); + // 如果订单已存在且已支付,返回成功(幂等性处理) + OrderInfo existingOrder = existingOrders.get(0); + if (existingOrder.getPayStatus() != null && existingOrder.getPayStatus() >= 2) { + log.info("订单已处理,返回成功。订单ID: {}, 交易ID: {}", existingOrder.getOrderId(), transactionId); + return "支付成功"; + } + } + + // 验证订阅是否过期(对于订阅类型) + if (StringUtils.isNotEmpty(expiresDateMs)) { + try { + long expiresTimestamp = Long.parseLong(expiresDateMs); + long currentTimestamp = System.currentTimeMillis(); + if (expiresTimestamp < currentTimestamp) { + log.warn("订阅已过期。交易ID: {}, 过期时间: {}, 当前时间: {}", + transactionId, new Date(expiresTimestamp), new Date(currentTimestamp)); + return "支付失败,订阅已过期"; + } + } catch (NumberFormatException e) { + log.warn("无法解析过期时间: {}", expiresDateMs); + } + } + + // 根据产品ID处理不同的订阅类型 + String packageType = null; + String orderName = null; + Long amount = null; // 金额(分) + + if ("com.mingyue.product.month".equals(productId)) { + // 月付 + packageType = "1"; + orderName = "VIP会员包月"; + amount = 1200L; // 12元 = 1200分 + log.info("用户ID:{}, 产品:月付订阅", userId); + } else if ("com.mingyue.product.quarterly".equals(productId)) { + // 季付 + packageType = "2"; + orderName = "VIP会员包季度"; + amount = 3000L; // 30元 = 3000分 + log.info("用户ID:{}, 产品:季付订阅", userId); + } else if ("com.mingyue.product.semiAnnual".equals(productId)) { + // 半年付 + packageType = "3"; + orderName = "VIP会员包半年"; + amount = 6000L; // 60元 = 6000分(假设) + log.info("用户ID:{}, 产品:半年付订阅", userId); + } else if ("com.mingyue.product.annual".equals(productId)) { + // 年付 + packageType = "4"; + orderName = "VIP会员包年"; + amount = 12000L; // 120元 = 12000分(假设) + log.info("用户ID:{}, 产品:年付订阅", userId); + } else { + log.error("用户ID:{}, 未知的产品ID: {}", userId, productId); + return "支付失败,未知的产品ID: " + productId; + } + + // 创建或更新订单 + try { + // 计算服务开始和结束时间 + Date startTime = new Date(); + Date endTime = calculateEndTime(startTime, packageType); + + // 创建订单记录 + OrderInfo orderInfo = new OrderInfo(); + orderInfo.setId(java.util.UUID.randomUUID().toString().replace("-", "")); + orderInfo.setOrderId(System.currentTimeMillis()); + orderInfo.setOrderName(orderName); + orderInfo.setUserId(Long.parseLong(userId)); + orderInfo.setAmount(amount); + orderInfo.setPayType("applePay"); + orderInfo.setPayStatus(2L); // 2-待出货 + orderInfo.setPayTime(DateUtils.getNowDate()); + orderInfo.setStartTime(startTime); + orderInfo.setEndTime(endTime); + orderInfo.setPackageType(packageType); + orderInfo.setTradeNo(transactionId); + orderInfo.setCallbackContent(sb.toString()); // 保存完整的回调内容 + orderInfo.setCallTime(DateUtils.getNowDate()); + orderInfo.setIdDel(0L); + orderInfo.setVersion(1L); + orderInfo.setCreateTime(DateUtils.getNowDate()); + orderInfo.setUpdateTime(DateUtils.getNowDate()); + + // 保存订单 + int result = orderInfoService.insertOrderInfo(orderInfo); + if (result > 0) { + log.info("订单创建成功。订单ID: {}, 交易ID: {}, 用户ID: {}, 产品: {}", + orderInfo.getOrderId(), transactionId, userId, productId); + return "支付成功"; + } else { + log.error("订单创建失败。交易ID: {}, 用户ID: {}", transactionId, userId); + return "支付失败,订单创建失败"; + } + } catch (Exception e) { + log.error("创建订单异常。交易ID: {}, 用户ID: {}, 异常: {}", transactionId, userId, e.getMessage(), e); + return "支付失败,订单创建异常: " + e.getMessage(); + } + } catch (Exception e) { - log.error("向苹果发起验证支付请求是否有效出现异常:{}", e.getMessage()); - return "支付过程中,出现了异常!"; + log.error("向苹果发起验证支付请求是否有效出现异常:{}", e.getMessage(), e); + return "支付过程中,出现了异常: " + e.getMessage(); + } + } + + /** + * 根据套餐类型计算结束时间 + * + * @param startTime 开始时间 + * @param packageType 套餐类型:1-月付, 3-季付, 6-半年付, 12-年付 + * @return 结束时间 + */ + private Date calculateEndTime(Date startTime, String packageType) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(startTime); + + switch (packageType) { + case "1": // 月付 + calendar.add(Calendar.MONTH, 1); + break; + case "3": // 季付 + calendar.add(Calendar.MONTH, 3); + break; + case "6": // 半年付 + calendar.add(Calendar.MONTH, 6); + break; + case "12": // 年付 + calendar.add(Calendar.YEAR, 1); + break; + default: + calendar.add(Calendar.MONTH, 1); // 默认1个月 + break; + } + + return calendar.getTime(); + } + + /** + * 获取苹果状态码对应的错误信息 + * + * @param status 状态码 + * @return 错误信息 + */ + private String getStatusErrorMessage(Integer status) { + if (status == null) { + return "未知错误"; + } + + switch (status) { + case 0: + return "成功"; + case 21000: + return "App Store无法读取你提供的JSON数据"; + case 21002: + return "receipt-data属性中的数据格式错误或丢失"; + case 21003: + return "收据无法验证"; + case 21004: + return "你提供的共享密钥与账户中的共享密钥不匹配"; + case 21005: + return "收据服务器当前不可用"; + case 21006: + return "此收据有效,但订阅已过期"; + case 21007: + return "此收据来自测试环境,但发送到生产环境进行验证"; + case 21008: + return "此收据来自生产环境,但发送到测试环境进行验证"; + case 21010: + return "此收据无法被授权"; + default: + return "未知错误,状态码: " + status; } }