1
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
//
|
||||
// SubscriptionConverter.swift
|
||||
// StoreKit2Manager
|
||||
//
|
||||
// Created by xiaopin on 2025/12/6.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import StoreKit
|
||||
|
||||
/// 订阅相关转换器
|
||||
/// 将订阅相关的对象转换为可序列化的基础数据类型(Dictionary/JSON)
|
||||
public struct SubscriptionConverter {
|
||||
|
||||
// MARK: - SubscriptionInfo
|
||||
|
||||
/// 将 SubscriptionInfo 转换为 Dictionary(同步版本,不包含异步属性)
|
||||
/// - Parameters:
|
||||
/// - subscription: SubscriptionInfo 对象
|
||||
/// - product: 关联的 Product 对象(可选)
|
||||
/// - Returns: Dictionary 对象
|
||||
public static func subscriptionInfoToDictionary(_ subscription: Product.SubscriptionInfo, product: Product? = nil) -> [String: Any] {
|
||||
var dict: [String: Any] = [:]
|
||||
|
||||
// 订阅组ID
|
||||
dict["subscriptionGroupID"] = subscription.subscriptionGroupID
|
||||
|
||||
// 订阅周期
|
||||
dict["subscriptionPeriodCount"] = subscription.subscriptionPeriod.value
|
||||
dict["subscriptionPeriodUnit"] = subscriptionPeriodUnitToString(subscription.subscriptionPeriod.unit)
|
||||
|
||||
// 介绍性优惠(如果有)
|
||||
if let introOffer = subscription.introductoryOffer {
|
||||
dict["introductoryOffer"] = subscriptionOfferToDictionary(introOffer)
|
||||
} else {
|
||||
dict["introductoryOffer"] = NSNull()
|
||||
}
|
||||
|
||||
// 促销优惠列表
|
||||
dict["promotionalOffers"] = subscription.promotionalOffers.map { subscriptionOfferToDictionary($0) }
|
||||
|
||||
// 赢回优惠列表(iOS 18.0+)
|
||||
if #available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *) {
|
||||
dict["winBackOffers"] = subscription.winBackOffers.map { subscriptionOfferToDictionary($0) }
|
||||
} else {
|
||||
dict["winBackOffers"] = []
|
||||
}
|
||||
|
||||
return dict
|
||||
}
|
||||
|
||||
// MARK: - RenewalInfo
|
||||
|
||||
/// 将 RenewalInfo 转换为 Dictionary
|
||||
/// - Parameter renewalInfo: RenewalInfo 对象
|
||||
/// - Returns: Dictionary 对象
|
||||
public static func renewalInfoToDictionary(_ renewalInfo: Product.SubscriptionInfo.RenewalInfo) -> [String: Any] {
|
||||
var dict: [String: Any] = [:]
|
||||
|
||||
// 是否自动续订
|
||||
dict["willAutoRenew"] = renewalInfo.willAutoRenew
|
||||
|
||||
// 续订日期(如果有)
|
||||
if let renewalDate = renewalInfo.renewalDate {
|
||||
dict["renewalDate"] = dateToTimestamp(renewalDate)
|
||||
} else {
|
||||
dict["renewalDate"] = NSNull()
|
||||
}
|
||||
|
||||
// 过期原因(如果有)
|
||||
if let expirationReason = renewalInfo.expirationReason {
|
||||
dict["expirationReason"] = expirationReasonToString(expirationReason)
|
||||
} else {
|
||||
dict["expirationReason"] = NSNull()
|
||||
}
|
||||
|
||||
// 注意:过期日期(expirationDate)不在 RenewalInfo 中,需要从 Transaction 中获取
|
||||
|
||||
return dict
|
||||
}
|
||||
|
||||
/// 将 RenewalInfo 转换为 JSON 字符串
|
||||
/// - Parameter renewalInfo: RenewalInfo 对象
|
||||
/// - Returns: JSON 字符串
|
||||
public static func renewalInfoToJSONString(_ renewalInfo: Product.SubscriptionInfo.RenewalInfo) -> String? {
|
||||
let dict = renewalInfoToDictionary(renewalInfo)
|
||||
return dictionaryToJSONString(dict)
|
||||
}
|
||||
|
||||
// MARK: - RenewalState
|
||||
|
||||
/// 将 RenewalState 转换为字符串
|
||||
/// - Parameter state: RenewalState 对象
|
||||
/// - Returns: 字符串
|
||||
public static func renewalStateToString(_ state: Product.SubscriptionInfo.RenewalState) -> String {
|
||||
switch state {
|
||||
case .subscribed:
|
||||
return "subscribed"
|
||||
case .expired:
|
||||
return "expired"
|
||||
case .inBillingRetryPeriod:
|
||||
return "inBillingRetryPeriod"
|
||||
case .inGracePeriod:
|
||||
return "inGracePeriod"
|
||||
case .revoked:
|
||||
return "revoked"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SubscriptionPeriod
|
||||
|
||||
/// 将 SubscriptionPeriod 转换为 Dictionary
|
||||
/// - Parameter period: SubscriptionPeriod 对象
|
||||
/// - Returns: Dictionary 对象
|
||||
public static func subscriptionPeriodToDictionary(_ period: Product.SubscriptionPeriod) -> [String: Any] {
|
||||
var dict: [String: Any] = [:]
|
||||
|
||||
dict["value"] = period.value
|
||||
dict["unit"] = subscriptionPeriodUnitToString(period.unit)
|
||||
|
||||
return dict
|
||||
}
|
||||
|
||||
// MARK: - SubscriptionOffer
|
||||
|
||||
/// 将 SubscriptionOffer 转换为 Dictionary
|
||||
/// - Parameter offer: SubscriptionOffer 对象
|
||||
/// - Returns: Dictionary 对象
|
||||
private static func subscriptionOfferToDictionary(_ offer: Product.SubscriptionOffer) -> [String: Any] {
|
||||
var dict: [String: Any] = [:]
|
||||
|
||||
// 优惠ID(介绍性优惠为 nil,确保是字符串类型)
|
||||
if let offerID = offer.id {
|
||||
dict["id"] = offerID
|
||||
} else {
|
||||
dict["id"] = NSNull()
|
||||
}
|
||||
|
||||
// 优惠类型
|
||||
dict["type"] = subscriptionOfferTypeToString(offer.type)
|
||||
|
||||
// 价格信息(确保是字符串类型)
|
||||
dict["displayPrice"] = String(describing: offer.displayPrice)
|
||||
dict["price"] = Double(String(format: "%.2f", NSDecimalNumber(decimal: offer.price).doubleValue)) ?? NSDecimalNumber(decimal: offer.price).doubleValue
|
||||
|
||||
// 支付模式
|
||||
dict["paymentMode"] = paymentModeToString(offer.paymentMode)
|
||||
|
||||
// 优惠周期
|
||||
dict["periodCount"] = offer.period.value
|
||||
dict["periodUnit"] = subscriptionPeriodUnitToString(offer.period.unit)
|
||||
|
||||
// 周期数量
|
||||
dict["offerPeriodCount"] = offer.periodCount
|
||||
|
||||
return dict
|
||||
}
|
||||
|
||||
// MARK: - 私有方法
|
||||
|
||||
/// 日期转时间戳(毫秒)
|
||||
private static func dateToTimestamp(_ date: Date) -> Int64 {
|
||||
return Int64(date.timeIntervalSince1970 * 1000)
|
||||
}
|
||||
|
||||
/// 订阅周期单位转字符串
|
||||
private static func subscriptionPeriodUnitToString(_ unit: Product.SubscriptionPeriod.Unit) -> String {
|
||||
switch unit {
|
||||
case .day:
|
||||
return "day"
|
||||
case .week:
|
||||
return "week"
|
||||
case .month:
|
||||
return "month"
|
||||
case .year:
|
||||
return "year"
|
||||
@unknown default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
/// 优惠类型转字符串
|
||||
private static func subscriptionOfferTypeToString(_ type: Product.SubscriptionOffer.OfferType) -> String {
|
||||
switch type {
|
||||
case .introductory:
|
||||
return "introductory"
|
||||
case .promotional:
|
||||
return "promotional"
|
||||
default:
|
||||
if #available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *) {
|
||||
if type == .winBack {
|
||||
return "winBack"
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
/// 支付模式转字符串
|
||||
private static func paymentModeToString(_ mode: Product.SubscriptionOffer.PaymentMode) -> String {
|
||||
switch mode {
|
||||
case .freeTrial:
|
||||
return "freeTrial"
|
||||
case .payAsYouGo:
|
||||
return "payAsYouGo"
|
||||
case .payUpFront:
|
||||
return "payUpFront"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
/// 过期原因转字符串
|
||||
private static func expirationReasonToString(_ reason: Product.SubscriptionInfo.RenewalInfo.ExpirationReason) -> String {
|
||||
// ExpirationReason 的具体枚举值可能因 iOS 版本而异
|
||||
// 使用 String(describing:) 作为后备方案
|
||||
let reasonString = String(describing: reason)
|
||||
// 移除命名空间前缀,只保留枚举值名称
|
||||
if let lastDot = reasonString.lastIndex(of: ".") {
|
||||
let value = String(reasonString[reasonString.index(after: lastDot)...])
|
||||
return value
|
||||
}
|
||||
return reasonString
|
||||
}
|
||||
|
||||
/// Dictionary 转 JSON 字符串
|
||||
private static func dictionaryToJSONString(_ dict: [String: Any]) -> String? {
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted),
|
||||
let jsonString = String(data: jsonData, encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
return jsonString
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user