This commit is contained in:
2025-12-16 13:10:50 +08:00
parent 444877fb73
commit fd0ddfd45a
17 changed files with 4751 additions and 3 deletions

View File

@@ -0,0 +1,48 @@
//
// StoreKitConfig.swift
// StoreKit2Manager
//
// Created by xiaopin on 2025/12/6.
//
import Foundation
/// StoreKit
public struct StoreKitConfig {
/// ID
public let productIds: [String]
/// ID
public let lifetimeIds: [String]
///
/// nil
public let nonRenewableExpirationDays: Int?
///
public let autoSortProducts: Bool
///
public let showLog: Bool
///
/// - Parameters:
/// - productIds: ID
/// - lifetimeIds: ID
/// - nonRenewableExpirationDays: 365
/// - autoSortProducts: true
/// - showLog: false
public init(
productIds: [String],
lifetimeIds: [String],
nonRenewableExpirationDays: Int? = 365,
autoSortProducts: Bool = true,
showLog: Bool = false
) {
self.productIds = productIds
self.lifetimeIds = lifetimeIds
self.nonRenewableExpirationDays = nonRenewableExpirationDays
self.autoSortProducts = autoSortProducts
self.showLog = showLog
}
}

View File

@@ -0,0 +1,85 @@
//
// StoreKitError.swift
// StoreKit2Manager
//
// Created by xiaopin on 2025/12/6.
//
import Foundation
/// StoreKit
public enum StoreKitError: Error, LocalizedError {
///
case productNotFound(String)
///
case purchaseFailed(Error)
///
case verificationFailed
///
case configurationMissing
///
case serviceNotStarted
///
case purchaseInProgress
///
case cancelSubscriptionFailed(Error)
///
case restorePurchasesFailed(Error)
///
case unknownError
public var errorDescription: String? {
switch self {
case .productNotFound(let id):
return "产品未找到: \(id)"
case .purchaseFailed(let error):
return "购买失败: \(error.localizedDescription)"
case .verificationFailed:
return "交易验证失败,可能是设备已越狱或交易数据被篡改"
case .configurationMissing:
return "配置缺失,请先调用 configure 方法进行配置"
case .serviceNotStarted:
return "服务未启动,请先调用 configure 方法"
case .purchaseInProgress:
return "购买正在进行中,请等待当前购买完成"
case .cancelSubscriptionFailed(let error):
return "取消订阅失败: \(error.localizedDescription)"
case .restorePurchasesFailed(let error):
return "恢复购买失败: \(error.localizedDescription)"
case .unknownError:
return "未知错误"
}
}
public var failureReason: String? {
switch self {
case .productNotFound(let id):
return "请检查产品ID是否正确并确保在 App Store Connect 中已配置该产品"
case .purchaseFailed(let error):
return error.localizedDescription
case .verificationFailed:
return "交易数据无法通过 Apple 的验证,这可能是由于设备已越狱或交易数据被篡改"
case .configurationMissing:
return "在调用其他方法之前,必须先调用 configure(with:delegate:) 或 configure(with:onStateChanged:) 方法"
case .serviceNotStarted:
return "StoreKit2Manager 尚未初始化,请先调用 configure 方法"
case .purchaseInProgress:
return "当前有购买正在进行,请等待完成后再试"
case .cancelSubscriptionFailed(let error):
return (error as? LocalizedError)?.failureReason ?? error.localizedDescription
case .restorePurchasesFailed(let error):
return (error as? LocalizedError)?.failureReason ?? error.localizedDescription
case .unknownError:
return "发生了未预期的错误"
}
}
}

View File

@@ -0,0 +1,95 @@
//
// StoreKitState.swift
// StoreKit2Manager
//
// Created by xiaopin on 2025/12/6.
//
import Foundation
import StoreKit
/// StoreKit
public enum StoreKitState: Equatable {
///
case idle
///
case loadingProducts
///
case loadingPurchases
///
case purchasesLoaded
///
case purchasing(String) // ID
///
case purchaseSuccess(String) // ID
///
case purchasePending(String) // ID
///
case purchaseCancelled(String) // ID
///
case purchaseFailed(String, Error) // ID,
///
case restoringPurchases
///
case restorePurchasesSuccess
///
case restorePurchasesFailed(Error)
/// 退
case purchaseRefunded(String) // ID
///
case purchaseRevoked(String) // ID
///
/// - Parameters:
/// - productId: ID
/// - isFreeTrialCancelled: true false
case subscriptionCancelled(String, isFreeTrialCancelled: Bool)
///
case error(Error)
public static func == (lhs: StoreKitState, rhs: StoreKitState) -> Bool {
switch (lhs, rhs) {
case (.idle, .idle),
(.loadingProducts, .loadingProducts),
(.loadingPurchases, .loadingPurchases),
(.purchasesLoaded, .purchasesLoaded):
return true
case (.purchasing(let lhsId), .purchasing(let rhsId)),
(.purchaseSuccess(let lhsId), .purchaseSuccess(let rhsId)),
(.purchasePending(let lhsId), .purchasePending(let rhsId)),
(.purchaseCancelled(let lhsId), .purchaseCancelled(let rhsId)):
return lhsId == rhsId
case (.purchaseFailed(let lhsId, _), .purchaseFailed(let rhsId, _)):
return lhsId == rhsId
case (.restoringPurchases, .restoringPurchases),
(.restorePurchasesSuccess, .restorePurchasesSuccess):
return true
case (.restorePurchasesFailed(let lhsError), .restorePurchasesFailed(let rhsError)):
return lhsError.localizedDescription == rhsError.localizedDescription
case (.purchaseRefunded(let lhsId), .purchaseRefunded(let rhsId)),
(.purchaseRevoked(let lhsId), .purchaseRevoked(let rhsId)):
return lhsId == rhsId
case (.subscriptionCancelled(let lhsId, let lhsIsFreeTrial), .subscriptionCancelled(let rhsId, let rhsIsFreeTrial)):
return lhsId == rhsId && lhsIsFreeTrial == rhsIsFreeTrial
case (.error(let lhsError), .error(let rhsError)):
return lhsError.localizedDescription == rhsError.localizedDescription
default:
return false
}
}
}

View File

@@ -0,0 +1,80 @@
//
// TransactionHistory.swift
// StoreKit2Manager
//
// Created by xiaopin on 2025/12/6.
//
import Foundation
import StoreKit
///
public struct TransactionHistory {
/// ID
public let productId: String
///
public let product: Product?
///
public let transaction: StoreKit.Transaction
///
public let purchaseDate: Date
///
public let expirationDate: Date?
/// 退
public let isRefunded: Bool
///
public let isRevoked: Bool
///
public let ownershipType: StoreKit.Transaction.OwnershipType
/// ID
public let transactionId: UInt64
public init(
productId: String,
product: Product?,
transaction: StoreKit.Transaction,
purchaseDate: Date,
expirationDate: Date? = nil,
isRefunded: Bool = false,
isRevoked: Bool = false,
ownershipType: StoreKit.Transaction.OwnershipType,
transactionId: UInt64
) {
self.productId = productId
self.product = product
self.transaction = transaction
self.purchaseDate = purchaseDate
self.expirationDate = expirationDate
self.isRefunded = isRefunded
self.isRevoked = isRevoked
self.ownershipType = ownershipType
self.transactionId = transactionId
}
}
// MARK: - Transaction
extension TransactionHistory {
/// Transaction
public static func from(_ transaction: StoreKit.Transaction, product: Product? = nil) -> TransactionHistory {
return TransactionHistory(
productId: transaction.productID,
product: product,
transaction: transaction,
purchaseDate: transaction.purchaseDate,
expirationDate: transaction.expirationDate,
isRefunded: transaction.revocationDate != nil,
isRevoked: transaction.revocationDate != nil,
ownershipType: transaction.ownershipType,
transactionId: transaction.id
)
}
}