refactor(service): 重构Apple购买服务并新增一次性购买处理
This commit is contained in:
@@ -43,4 +43,11 @@ public interface ApplePurchaseService {
|
||||
* @param notification 解码后的通知载荷
|
||||
*/
|
||||
void handleConsumptionRequest(ResponseBodyV2DecodedPayload notification);
|
||||
|
||||
/**
|
||||
* 处理一次性购买通知(ONE_TIME_CHARGE)
|
||||
*
|
||||
* @param notification 解码后的通知载荷
|
||||
*/
|
||||
void handleOneTimeChargeNotification(ResponseBodyV2DecodedPayload notification);
|
||||
}
|
||||
|
||||
@@ -38,4 +38,5 @@ public interface UserService extends IService<KeyboardUser> {
|
||||
*/
|
||||
Boolean cancelAccount(long userId);
|
||||
|
||||
Long selectUserByUUid(String uuid);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.yolo.keyborad.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.apple.itunes.storekit.model.JWSRenewalInfoDecodedPayload;
|
||||
import com.apple.itunes.storekit.model.JWSTransactionDecodedPayload;
|
||||
import com.apple.itunes.storekit.model.NotificationTypeV2;
|
||||
@@ -34,6 +35,7 @@ import java.time.format.DateTimeParseException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 苹果购买后置处理:订阅续期 / 内购充值 + 记录落库
|
||||
@@ -152,23 +154,10 @@ public class ApplePurchaseServiceImpl implements ApplePurchaseService {
|
||||
JWSTransactionDecodedPayload transaction =
|
||||
signedDataVerifier.verifyAndDecodeTransaction(signedTransactionInfo);
|
||||
|
||||
String originalTransactionId = transaction.getOriginalTransactionId();
|
||||
String productId = transaction.getProductId();
|
||||
|
||||
// 根据原始交易ID查询用户购买记录
|
||||
List<KeyboardUserPurchaseRecords> records = purchaseRecordsService.lambdaQuery()
|
||||
.eq(KeyboardUserPurchaseRecords::getOriginalTransactionId, originalTransactionId)
|
||||
.orderByDesc(KeyboardUserPurchaseRecords::getId)
|
||||
.last("LIMIT 1")
|
||||
.list();
|
||||
|
||||
if (records == null || records.isEmpty()) {
|
||||
log.warn("No purchase record found for originalTransactionId={}", originalTransactionId);
|
||||
return;
|
||||
}
|
||||
|
||||
KeyboardUserPurchaseRecords existingRecord = records.get(0);
|
||||
Long userId = existingRecord.getUserId().longValue();
|
||||
UUID appAccountToken = transaction.getAppAccountToken();
|
||||
Long userId = userService.selectUserByUUid(appAccountToken.toString());
|
||||
|
||||
// 查询商品信息
|
||||
KeyboardProductItems product = productItemsService.getProductEntityByProductId(productId);
|
||||
@@ -354,6 +343,127 @@ public class ApplePurchaseServiceImpl implements ApplePurchaseService {
|
||||
// 提供用户消费状态、交付状态等信息,帮助 Apple 评估退款请求
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理一次性购买通知(ONE_TIME_CHARGE)
|
||||
* 流程参考 processPurchase:
|
||||
* 1. 验证并解码交易信息
|
||||
* 2. 幂等性检查,避免重复处理
|
||||
* 3. 通过 originalTransactionId 查找用户
|
||||
* 4. 查询商品信息
|
||||
* 5. 保存购买记录
|
||||
* 6. 根据商品类型执行对应逻辑(订阅延期或钱包充值)
|
||||
*
|
||||
* @param notification 解码后的通知载荷
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void handleOneTimeChargeNotification(ResponseBodyV2DecodedPayload notification) {
|
||||
if (notification == null || notification.getData() == null) {
|
||||
log.warn("ONE_TIME_CHARGE notification data is null");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 解码交易信息
|
||||
String signedTransactionInfo = notification.getData().getSignedTransactionInfo();
|
||||
if (signedTransactionInfo == null || signedTransactionInfo.isBlank()) {
|
||||
log.warn("No signed transaction info in ONE_TIME_CHARGE notification");
|
||||
return;
|
||||
}
|
||||
|
||||
JWSTransactionDecodedPayload transaction =
|
||||
signedDataVerifier.verifyAndDecodeTransaction(signedTransactionInfo);
|
||||
|
||||
String transactionId = transaction.getTransactionId();
|
||||
String originalTransactionId = transaction.getOriginalTransactionId();
|
||||
String productId = transaction.getProductId();
|
||||
UUID appAccountToken = transaction.getAppAccountToken();
|
||||
Long userId = userService.selectUserByUUid(appAccountToken.toString());
|
||||
|
||||
log.info("Processing ONE_TIME_CHARGE: transactionId={}, productId={}", transactionId, productId);
|
||||
|
||||
// 2. 幂等性检查:根据交易ID判断是否已处理
|
||||
boolean handled = purchaseRecordsService.lambdaQuery()
|
||||
.eq(KeyboardUserPurchaseRecords::getTransactionId, transactionId)
|
||||
.eq(KeyboardUserPurchaseRecords::getStatus, "PAID")
|
||||
.exists();
|
||||
if (handled) {
|
||||
log.info("ONE_TIME_CHARGE already handled, transactionId={}", transactionId);
|
||||
return;
|
||||
}
|
||||
|
||||
// // 3. 通过 originalTransactionId 查找关联的用户购买记录以获取 userId
|
||||
// List<KeyboardUserPurchaseRecords> records = purchaseRecordsService.lambdaQuery()
|
||||
// .eq(KeyboardUserPurchaseRecords::getOriginalTransactionId, originalTransactionId)
|
||||
// .orderByDesc(KeyboardUserPurchaseRecords::getId)
|
||||
// .last("LIMIT 1")
|
||||
// .list();
|
||||
//
|
||||
// if (records == null || records.isEmpty()) {
|
||||
// log.warn("No purchase record found for ONE_TIME_CHARGE, originalTransactionId={}", originalTransactionId);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Long userId = records.get(0).getUserId().longValue();
|
||||
|
||||
// 4. 查询商品信息
|
||||
KeyboardProductItems product = productItemsService.getProductEntityByProductId(productId);
|
||||
if (product == null) {
|
||||
log.error("Product not found for ONE_TIME_CHARGE, productId={}", productId);
|
||||
return;
|
||||
}
|
||||
// 5. 构建并保存购买记录
|
||||
KeyboardUserPurchaseRecords purchaseRecord = new KeyboardUserPurchaseRecords();
|
||||
purchaseRecord.setUserId(userId.intValue());
|
||||
purchaseRecord.setProductId(productId);
|
||||
purchaseRecord.setPurchaseQuantity(product.getDurationValue());
|
||||
purchaseRecord.setPrice(product.getPrice());
|
||||
purchaseRecord.setCurrency(product.getCurrency());
|
||||
purchaseRecord.setPurchaseType(product.getType());
|
||||
purchaseRecord.setStatus("PAID");
|
||||
purchaseRecord.setPaymentMethod("APPLE");
|
||||
purchaseRecord.setTransactionId(transactionId);
|
||||
purchaseRecord.setOriginalTransactionId(originalTransactionId);
|
||||
purchaseRecord.setProductIds(new String[]{productId});
|
||||
|
||||
if (transaction.getPurchaseDate() != null) {
|
||||
purchaseRecord.setPurchaseTime(Date.from(Instant.ofEpochMilli(transaction.getPurchaseDate())));
|
||||
purchaseRecord.setPurchaseDate(Date.from(Instant.ofEpochMilli(transaction.getPurchaseDate())));
|
||||
} else {
|
||||
purchaseRecord.setPurchaseTime(new Date());
|
||||
}
|
||||
|
||||
if (transaction.getExpiresDate() != null) {
|
||||
purchaseRecord.setExpiresDate(Date.from(Instant.ofEpochMilli(transaction.getExpiresDate())));
|
||||
}
|
||||
|
||||
if (transaction.getEnvironment() != null) {
|
||||
purchaseRecord.setEnvironment(transaction.getEnvironment().name());
|
||||
}
|
||||
|
||||
purchaseRecordsService.save(purchaseRecord);
|
||||
|
||||
// 6. 根据商品类型执行对应的业务逻辑
|
||||
if ("subscription".equalsIgnoreCase(product.getType())) {
|
||||
Instant expiresInstant = transaction.getExpiresDate() != null
|
||||
? Instant.ofEpochMilli(transaction.getExpiresDate()) : null;
|
||||
extendVip(userId, product, expiresInstant);
|
||||
} else if ("in-app-purchase".equalsIgnoreCase(product.getType())) {
|
||||
handleInAppPurchase(userId, product, purchaseRecord.getId());
|
||||
} else {
|
||||
log.warn("未知商品类型, type={}, productId={}", product.getType(), productId);
|
||||
}
|
||||
|
||||
log.info("ONE_TIME_CHARGE processed successfully: userId={}, transactionId={}", userId, transactionId);
|
||||
|
||||
} catch (VerificationException e) {
|
||||
log.error("Failed to verify transaction in ONE_TIME_CHARGE notification", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing ONE_TIME_CHARGE notification", e);
|
||||
throw new BusinessException(ErrorCode.OPERATION_ERROR, "处理一次性购买通知失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理成功的续订
|
||||
* 创建新的购买记录并延长VIP有效期
|
||||
|
||||
@@ -155,6 +155,7 @@ public class AppleReceiptServiceImpl implements AppleReceiptService {
|
||||
|
||||
// 续订偏好变更通知
|
||||
case DID_CHANGE_RENEWAL_PREF:
|
||||
|
||||
case DID_CHANGE_RENEWAL_STATUS:
|
||||
case PRICE_INCREASE:
|
||||
applePurchaseService.handleRenewalPreferenceChange(notification);
|
||||
@@ -165,9 +166,14 @@ public class AppleReceiptServiceImpl implements AppleReceiptService {
|
||||
applePurchaseService.handleConsumptionRequest(notification);
|
||||
break;
|
||||
|
||||
// 一次性购买通知
|
||||
case ONE_TIME_CHARGE:
|
||||
applePurchaseService.handleOneTimeChargeNotification(notification);
|
||||
break;
|
||||
|
||||
// 其他通知类型(记录但不处理)
|
||||
case EXTERNAL_PURCHASE_TOKEN:
|
||||
case ONE_TIME_CHARGE:
|
||||
|
||||
case REVOKE:
|
||||
case TEST:
|
||||
log.info("Received notification type {} - no action required", type);
|
||||
|
||||
@@ -199,6 +199,14 @@ public class UserServiceImpl extends ServiceImpl<KeyboardUserMapper, KeyboardUse
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long selectUserByUUid(String uuid) {
|
||||
KeyboardUser keyboardUserDB = keyboardUserMapper.selectOne(
|
||||
new LambdaQueryWrapper<KeyboardUser>()
|
||||
.eq(KeyboardUser::getUuid, uuid));
|
||||
return keyboardUserDB.getId();
|
||||
}
|
||||
|
||||
private KeyboardUser buildNewUserWithSubjectId(String sub) {
|
||||
KeyboardUser keyboardUser = new KeyboardUser();
|
||||
keyboardUser.setSubjectId(sub);
|
||||
|
||||
Reference in New Issue
Block a user