Files
keyboard/keyBoard/Class/Pay/StoreKit2Manager/Converts/SubscriptionConverter.swift
2025-12-16 13:10:50 +08:00

238 lines
8.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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