refactor(service): 改用JWS验签,移除旧收据解析
废弃ReceiptUtility与AppStoreServerAPIClient,直接以SignedDataVerifier校验客户端传来的signedPayload(JWS),简化流程并减少一次网络IO。
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 客户端
|
||||
* <p>
|
||||
* 用于调用 Apple App Store Server API 获取交易信息
|
||||
* </p>
|
||||
*/
|
||||
private final AppStoreServerAPIClient client;
|
||||
|
||||
/**
|
||||
* 签名数据验证器
|
||||
* <p>
|
||||
* 用于验证和解码 Apple 返回的 JWS 签名数据
|
||||
* 用于验证和解码客户端传来的 JWS 签名交易数据
|
||||
* </p>
|
||||
*/
|
||||
private final SignedDataVerifier signedDataVerifier;
|
||||
|
||||
/**
|
||||
* 收据工具类
|
||||
* <p>
|
||||
* 用于解析应用收据内容,提取交易 ID 等信息
|
||||
* </p>
|
||||
*/
|
||||
private final ReceiptUtility receiptUtility;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* <p>
|
||||
* 通过构造函数注入所需的依赖组件
|
||||
* 通过构造函数注入签名数据验证器
|
||||
* </p>
|
||||
* @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 应用内购买交易
|
||||
* <p>
|
||||
* 执行完整的收据验证流程,包括解析收据、获取交易信息、验证签名和业务逻辑校验
|
||||
* 直接验证从客户端传来的 JWS (JSON Web Signature) 交易数据
|
||||
* 使用 SignedDataVerifier 验证签名并解码交易信息,然后执行业务逻辑校验
|
||||
* </p>
|
||||
* @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");
|
||||
}
|
||||
|
||||
// 2. 调用 App Store Server API 获取单笔交易信息
|
||||
TransactionInfoResponse infoResponse = client.getTransactionInfo(transactionId);
|
||||
|
||||
String signedTransactionInfo = infoResponse.getSignedTransactionInfo();
|
||||
if (signedTransactionInfo == null) {
|
||||
return invalid("no_signed_transaction_info");
|
||||
}
|
||||
|
||||
// 3. 使用 SignedDataVerifier 验证 JWS 并解码 payload
|
||||
// 1. 使用 SignedDataVerifier 直接验证 JWS 并解码交易 payload
|
||||
// 这会验证签名、证书链、bundle ID、环境等
|
||||
JWSTransactionDecodedPayload payload =
|
||||
signedDataVerifier.verifyAndDecodeTransaction(signedTransactionInfo);
|
||||
signedDataVerifier.verifyAndDecodeTransaction(signedTransaction);
|
||||
|
||||
// 4. 执行业务校验:检查交易是否仍然有效
|
||||
// 2. 记录交易信息用于调试
|
||||
log.info("Verified transaction: transactionId={}, productId={}, purchaseDate={}",
|
||||
payload.getTransactionId(),
|
||||
payload.getProductId(),
|
||||
payload.getPurchaseDate());
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user