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,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
}
}