From c54c14de585cfdb320424ed77416b669a5ffb064 Mon Sep 17 00:00:00 2001 From: ziin Date: Tue, 16 Dec 2025 15:06:41 +0800 Subject: [PATCH] =?UTF-8?q?refactor(service):=20=E6=94=B9=E7=94=A8JWS?= =?UTF-8?q?=E9=AA=8C=E7=AD=BE=EF=BC=8C=E7=A7=BB=E9=99=A4=E6=97=A7=E6=94=B6?= =?UTF-8?q?=E6=8D=AE=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 废弃ReceiptUtility与AppStoreServerAPIClient,直接以SignedDataVerifier校验客户端传来的signedPayload(JWS),简化流程并减少一次网络IO。 --- .../controller/AppleReceiptController.java | 8 +- .../keyborad/service/AppleReceiptService.java | 4 +- .../service/impl/AppleReceiptServiceImpl.java | 93 ++++++------------- 3 files changed, 33 insertions(+), 72 deletions(-) diff --git a/src/main/java/com/yolo/keyborad/controller/AppleReceiptController.java b/src/main/java/com/yolo/keyborad/controller/AppleReceiptController.java index 21160cc..a52175e 100644 --- a/src/main/java/com/yolo/keyborad/controller/AppleReceiptController.java +++ b/src/main/java/com/yolo/keyborad/controller/AppleReceiptController.java @@ -62,12 +62,12 @@ public class AppleReceiptController { if (body == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "body 不能为空"); } - String receipt = body.get("receipt"); - if (receipt == null || receipt.isBlank()) { - throw new BusinessException(ErrorCode.PARAMS_ERROR, "receipt 不能为空"); + String signedPayload = body.get("signedPayload"); + if (signedPayload == null || signedPayload.isBlank()) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "signedPayload 不能为空"); } Long userId = StpUtil.getLoginIdAsLong(); - AppleReceiptValidationResult validationResult = appleReceiptService.validateReceipt(receipt); + AppleReceiptValidationResult validationResult = appleReceiptService.validateReceipt(signedPayload); applePurchaseService.processPurchase(userId, validationResult); return ResultUtils.success(Boolean.TRUE); } diff --git a/src/main/java/com/yolo/keyborad/service/AppleReceiptService.java b/src/main/java/com/yolo/keyborad/service/AppleReceiptService.java index 24c30c5..4b9db24 100644 --- a/src/main/java/com/yolo/keyborad/service/AppleReceiptService.java +++ b/src/main/java/com/yolo/keyborad/service/AppleReceiptService.java @@ -7,7 +7,7 @@ public interface AppleReceiptService { /** * 验证 base64 app receipt 是否有效,并返回解析结果。 * - * @param appReceipt Base64 的 app receipt(以 MI... 开头那串) + * @param signedPayload Base64 的 app receipt(以 MI... 开头那串) */ - AppleReceiptValidationResult validateReceipt(String appReceipt); + AppleReceiptValidationResult validateReceipt(String signedPayload); } diff --git a/src/main/java/com/yolo/keyborad/service/impl/AppleReceiptServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/AppleReceiptServiceImpl.java index 0001e2d..4ebceee 100644 --- a/src/main/java/com/yolo/keyborad/service/impl/AppleReceiptServiceImpl.java +++ b/src/main/java/com/yolo/keyborad/service/impl/AppleReceiptServiceImpl.java @@ -1,11 +1,7 @@ package com.yolo.keyborad.service.impl; -import com.apple.itunes.storekit.client.APIException; -import com.apple.itunes.storekit.client.AppStoreServerAPIClient; -import com.apple.itunes.storekit.migration.ReceiptUtility; import com.apple.itunes.storekit.model.Environment; import com.apple.itunes.storekit.model.JWSTransactionDecodedPayload; -import com.apple.itunes.storekit.model.TransactionInfoResponse; import com.apple.itunes.storekit.verification.SignedDataVerifier; import com.apple.itunes.storekit.verification.VerificationException; import com.yolo.keyborad.model.dto.AppleReceiptValidationResult; @@ -27,102 +23,67 @@ import java.util.List; @Service public class AppleReceiptServiceImpl implements AppleReceiptService { - /** - * App Store 服务器 API 客户端 - *

- * 用于调用 Apple App Store Server API 获取交易信息 - *

- */ - private final AppStoreServerAPIClient client; - /** * 签名数据验证器 *

- * 用于验证和解码 Apple 返回的 JWS 签名数据 + * 用于验证和解码客户端传来的 JWS 签名交易数据 *

*/ private final SignedDataVerifier signedDataVerifier; - - /** - * 收据工具类 - *

- * 用于解析应用收据内容,提取交易 ID 等信息 - *

- */ - private final ReceiptUtility receiptUtility; /** * 构造函数 *

- * 通过构造函数注入所需的依赖组件 + * 通过构造函数注入签名数据验证器 *

- * @param client App Store 服务器 API 客户端 * @param signedDataVerifier 签名数据验证器 - * @param receiptUtility 收据工具类 */ - public AppleReceiptServiceImpl(AppStoreServerAPIClient client, - SignedDataVerifier signedDataVerifier, - ReceiptUtility receiptUtility) { - this.client = client; + public AppleReceiptServiceImpl(SignedDataVerifier signedDataVerifier) { this.signedDataVerifier = signedDataVerifier; - this.receiptUtility = receiptUtility; } /** - * 验证 Apple 应用内购买收据 + * 验证 Apple 应用内购买交易 *

- * 执行完整的收据验证流程,包括解析收据、获取交易信息、验证签名和业务逻辑校验 + * 直接验证从客户端传来的 JWS (JSON Web Signature) 交易数据 + * 使用 SignedDataVerifier 验证签名并解码交易信息,然后执行业务逻辑校验 *

- * @param appReceipt Base64 编码的应用内购买收据 + * @param signedTransaction 客户端传来的 JWS 格式的签名交易数据 * @return 验证结果对象,包含验证状态和交易信息 */ @Override - public AppleReceiptValidationResult validateReceipt(String appReceipt) { - // 检查收据是否为空 - if (appReceipt == null || appReceipt.isBlank()) { - return invalid("empty_receipt"); + public AppleReceiptValidationResult validateReceipt(String signedTransaction) { + // 检查 JWS 是否为空 + if (signedTransaction == null || signedTransaction.isBlank()) { + return invalid("empty_transaction"); } try { - // 1. 从收据里解析出 transactionId(不做验证,只是解析 ASN.1) - String transactionId = receiptUtility.extractTransactionIdFromAppReceipt(appReceipt); -// todo 验证服务器传输的transactionId -// String transactionId = receiptUtility.extractTransactionIdFromTransactionReceipt(appReceipt); - if (transactionId == null) { - return invalid("no_transaction_id_in_receipt"); - } + // 1. 使用 SignedDataVerifier 直接验证 JWS 并解码交易 payload + // 这会验证签名、证书链、bundle ID、环境等 + JWSTransactionDecodedPayload payload = + signedDataVerifier.verifyAndDecodeTransaction(signedTransaction); - // 2. 调用 App Store Server API 获取单笔交易信息 - TransactionInfoResponse infoResponse = client.getTransactionInfo(transactionId); + // 2. 记录交易信息用于调试 + log.info("Verified transaction: transactionId={}, productId={}, purchaseDate={}", + payload.getTransactionId(), + payload.getProductId(), + payload.getPurchaseDate()); - String signedTransactionInfo = infoResponse.getSignedTransactionInfo(); - if (signedTransactionInfo == null) { - return invalid("no_signed_transaction_info"); - } - - // 3. 使用 SignedDataVerifier 验证 JWS 并解码 payload - JWSTransactionDecodedPayload payload = - signedDataVerifier.verifyAndDecodeTransaction(signedTransactionInfo); - - // 4. 执行业务校验:检查交易是否仍然有效 + // 3. 执行业务校验:检查交易是否仍然有效 boolean stillActive = isTransactionActive(payload); - // 构建并返回验证结果 + // 4. 构建并返回验证结果 return getAppleReceiptValidationResult(stillActive, payload); + } catch (VerificationException e) { - // 验证异常处理 - log.warn("Apple receipt verification failed", e); + // 验证异常处理:签名无效、证书链问题、bundle ID 不匹配等 + log.warn("Apple transaction verification failed: status={}", e.getStatus(), e); return invalid("verification_exception:" + e.getStatus()); - } catch (APIException e) { - // API 异常处理,可以根据错误码进一步处理(如环境切换) - // 4040010 表示订单不存在,可能是环境不匹配导致 - long code = e.getApiError().errorCode(); - log.warn("App Store API error, code={}", code, e); - return invalid("api_exception:" + code); } catch (Exception e) { // 其他未预期异常处理 - log.error("Unexpected error when validating Apple receipt", e); - return invalid("unexpected_error"); + log.error("Unexpected error when validating Apple transaction", e); + return invalid("unexpected_error:" + e.getMessage()); } }