diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index 1125674632..314967ea80 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -294,6 +294,7 @@ 35D83300262FAD8000E60AC5 /* ETagManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35D832FF262FAD8000E60AC5 /* ETagManagerTests.swift */; }; 35D8330A262FBA9A00E60AC5 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E357D16038F07915D7825D /* MockUserDefaults.swift */; }; 35D83312262FBD4200E60AC5 /* MockETagManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35D83311262FBD4200E60AC5 /* MockETagManager.swift */; }; + 35DE0DB62CEF9E8F00EB83E9 /* SubscriptionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35DE0DB52CEF9E8C00EB83E9 /* SubscriptionInfo.swift */; }; 35E840CC270FB70D00899AE2 /* ManageSubscriptionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E840C5270FB47C00899AE2 /* ManageSubscriptionsHelper.swift */; }; 35E840CE2710E2EB00899AE2 /* MockManageSubscriptionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E840CD2710E2EB00899AE2 /* MockManageSubscriptionsHelper.swift */; }; 35F249CA2C493D970058993A /* LoadPromotionalOfferUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35F249C92C493D970058993A /* LoadPromotionalOfferUseCase.swift */; }; @@ -1536,6 +1537,7 @@ 35D832F3262E606500E60AC5 /* HTTPResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPResponse.swift; sourceTree = ""; }; 35D832FF262FAD8000E60AC5 /* ETagManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ETagManagerTests.swift; sourceTree = ""; }; 35D83311262FBD4200E60AC5 /* MockETagManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockETagManager.swift; sourceTree = ""; }; + 35DE0DB52CEF9E8C00EB83E9 /* SubscriptionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionInfo.swift; sourceTree = ""; }; 35E1CE1F26E022C20008560A /* TrialOrIntroPriceEligibilityCheckerSK1Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrialOrIntroPriceEligibilityCheckerSK1Tests.swift; sourceTree = ""; }; 35E840C5270FB47C00899AE2 /* ManageSubscriptionsHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManageSubscriptionsHelper.swift; sourceTree = ""; }; 35E840CD2710E2EB00899AE2 /* MockManageSubscriptionsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockManageSubscriptionsHelper.swift; sourceTree = ""; }; @@ -4767,6 +4769,7 @@ B3A36AAC26BC76230059EDEA /* Identity */ = { isa = PBXGroup; children = ( + 35DE0DB52CEF9E8C00EB83E9 /* SubscriptionInfo.swift */, A56F9AB026990E9200AFC48F /* CustomerInfo.swift */, 57F3C10429B7B22E0004FD7E /* CustomerInfo+ActiveDates.swift */, 4F15B4A02A6774C9005BEFE8 /* CustomerInfo+NonSubscriptions.swift */, @@ -5733,6 +5736,7 @@ FD43D2FC2C41864000077235 /* TimeInterval+Extensions.swift in Sources */, 4F7DBFBD2A1E986C00A2F511 /* StoreKit2TransactionFetcher.swift in Sources */, 5766AB4728401B8400FA6091 /* PackageType.swift in Sources */, + 35DE0DB62CEF9E8F00EB83E9 /* SubscriptionInfo.swift in Sources */, B3F3E8DA277158FE0047A5B9 /* DNSChecker.swift in Sources */, A525BF4B26C320D100C354C4 /* SubscriberAttributesManager.swift in Sources */, 2D1015DA275959840086173F /* StoreTransaction.swift in Sources */, diff --git a/Sources/Identity/CustomerInfo.swift b/Sources/Identity/CustomerInfo.swift index a7bd7cae06..03a4587190 100644 --- a/Sources/Identity/CustomerInfo.swift +++ b/Sources/Identity/CustomerInfo.swift @@ -12,8 +12,14 @@ // Created by Madeline Beyl on 7/9/21. // +// swiftlint:disable file_length import Foundation +/** + An identifier used to identify a product. + */ +public typealias ProductIdentifier = String + /** A container for the most recent customer info returned from `Purchases`. These objects are non-mutable and do not update automatically. @@ -24,10 +30,12 @@ import Foundation @objc public let entitlements: EntitlementInfos /// All *subscription* product identifiers with expiration dates in the future. - @objc public var activeSubscriptions: Set { self.activeKeys(dates: self.expirationDatesByProductId) } + @objc public var activeSubscriptions: Set { + self.activeKeys(dates: self.expirationDatesByProductId) + } /// All product identifiers purchases by the user regardless of expiration. - @objc public let allPurchasedProductIdentifiers: Set + @objc public let allPurchasedProductIdentifiers: Set /// Returns the latest expiration date of all products, nil if there are none. @objc public var latestExpirationDate: Date? { @@ -88,17 +96,20 @@ import Foundation */ @objc public let originalApplicationVersion: String? + /// Dictionary of all subscription product identifiers and their subscription info + @objc public let subscriptionsByProductIdentifier: [ProductIdentifier: SubscriptionInfo] + /// Get the expiration date for a given product identifier. You should use Entitlements though! /// - Parameter productIdentifier: Product identifier for product /// - Returns: The expiration date for `productIdentifier`, `nil` if product never purchased - @objc public func expirationDate(forProductIdentifier productIdentifier: String) -> Date? { + @objc public func expirationDate(forProductIdentifier productIdentifier: ProductIdentifier) -> Date? { return expirationDatesByProductId[productIdentifier] ?? nil } /// Get the latest purchase or renewal date for a given product identifier. You should use Entitlements though! /// - Parameter productIdentifier: Product identifier for subscription product /// - Returns: The purchase date for `productIdentifier`, `nil` if product never purchased - @objc public func purchaseDate(forProductIdentifier productIdentifier: String) -> Date? { + @objc public func purchaseDate(forProductIdentifier productIdentifier: ProductIdentifier) -> Date? { return purchaseDatesByProductId[productIdentifier] ?? nil } @@ -143,6 +154,8 @@ import Foundation let verificationResult = self.entitlements.verification.debugDescription + let subscriptionsDescription = self.subscriptionsByProductIdentifier.mapValues { $0.description } + return """ <\(String(describing: CustomerInfo.self)): originalApplicationVersion=\(self.originalApplicationVersion ?? ""), @@ -150,6 +163,7 @@ import Foundation activeEntitlements=\(activeEntitlementsDescription), activeSubscriptions=\(activeSubsDescription), nonSubscriptions=\(self.nonSubscriptions), + subscriptions=\(subscriptionsDescription), requestDate=\(String(describing: self.requestDate)), firstSeen=\(String(describing: self.firstSeen)), originalAppUserId=\(self.originalAppUserId), @@ -208,6 +222,26 @@ import Foundation self.purchaseDatesByProductId = Self.extractPurchaseDates(subscriber) self.allPurchasedProductIdentifiers = Set(self.expirationDatesByProductId.keys) .union(self.nonSubscriptions.map { $0.productIdentifier }) + + self.subscriptionsByProductIdentifier = + Dictionary(uniqueKeysWithValues: subscriber.subscriptions.map { (key, subscriptionData) in + (key, SubscriptionInfo( + productIdentifier: key, + purchaseDate: subscriptionData.purchaseDate, + originalPurchaseDate: subscriptionData.originalPurchaseDate, + expiresDate: subscriptionData.expiresDate, + store: subscriptionData.store, + isSandbox: subscriptionData.isSandbox, + unsubscribeDetectedAt: subscriptionData.unsubscribeDetectedAt, + billingIssuesDetectedAt: subscriptionData.billingIssuesDetectedAt, + gracePeriodExpiresDate: subscriptionData.gracePeriodExpiresDate, + ownershipType: subscriptionData.ownershipType, + periodType: subscriptionData.periodType, + refundedAt: subscriptionData.refundedAt, + storeTransactionId: subscriptionData.storeTransactionId, + requestDate: response.requestDate + )) + }) } private let expirationDatesByProductId: [String: Date?] diff --git a/Sources/Identity/SubscriptionInfo.swift b/Sources/Identity/SubscriptionInfo.swift new file mode 100644 index 0000000000..c79e9a7f32 --- /dev/null +++ b/Sources/Identity/SubscriptionInfo.swift @@ -0,0 +1,134 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// SubscriptionInfo.swift +// +// Created by Cesar de la Vega on 21/11/24. + +import Foundation + +/// Subscription purchases of the Customer +@objc(RCSubscriptionInfo) public final class SubscriptionInfo: NSObject { + + /// The product identifier. + @objc public let productIdentifier: ProductIdentifier + + /// Date when the last subscription period started. + @objc public let purchaseDate: Date + + /// Date when this subscription first started. This property does not update with renewals. + /// This property also does not update for product changes within a subscription group or + /// resubscriptions by lapsed subscribers. + @objc public let originalPurchaseDate: Date? + + /// Date when the subscription expires/expired + @objc public let expiresDate: Date? + + /// Store where the subscription was purchased. + @objc public let store: Store + + /// Whether or not the purchase was made in sandbox mode. + @objc public let isSandbox: Bool + + /// Date when RevenueCat detected that auto-renewal was turned off for this subsription. + /// Note the subscription may still be active, check the ``expiresDate`` attribute. + @objc public let unsubscribeDetectedAt: Date? + + /// Date when RevenueCat detected any billing issues with this subscription. + /// If and when the billing issue gets resolved, this field is set to nil. + /// Note the subscription may still be active, check the ``expiresDate`` attribute. + @objc public let billingIssuesDetectedAt: Date? + + /// Date when any grace period for this subscription expires/expired. + /// nil if the customer has never been in a grace period. + @objc public let gracePeriodExpiresDate: Date? + + /// How the Customer received access to this subscription: + /// - ``PurchaseOwnershipType/purchased``: The customer bought the subscription. + /// - ``PurchaseOwnershipType/familyShared``: The Customer has access to the product via their family. + @objc public let ownershipType: PurchaseOwnershipType + + /// Type of the current subscription period: + /// - ``PeriodType/normal``: The product is in a normal period (default) + /// - ``PeriodType/trial``: The product is in a free trial period + /// - ``PeriodType/intro``: The product is in an introductory pricing period + @objc public let periodType: PeriodType + + /// Date when RevenueCat detected a refund of this subscription. + @objc public let refundedAt: Date? + + /// The transaction id in the store of the subscription. + @objc public let storeTransactionId: String? + + /// Whether the subscription is currently active. + @objc public let isActive: Bool + + /// Whether the subscription will renew at the next billing period. + @objc public let willRenew: Bool + + init(productIdentifier: String, + purchaseDate: Date, + originalPurchaseDate: Date?, + expiresDate: Date?, + store: Store, + isSandbox: Bool, + unsubscribeDetectedAt: Date?, + billingIssuesDetectedAt: Date?, + gracePeriodExpiresDate: Date?, + ownershipType: PurchaseOwnershipType, + periodType: PeriodType, + refundedAt: Date?, + storeTransactionId: String?, + requestDate: Date) { + self.productIdentifier = productIdentifier + self.purchaseDate = purchaseDate + self.originalPurchaseDate = originalPurchaseDate + self.expiresDate = expiresDate + self.store = store + self.isSandbox = isSandbox + self.unsubscribeDetectedAt = unsubscribeDetectedAt + self.billingIssuesDetectedAt = billingIssuesDetectedAt + self.gracePeriodExpiresDate = gracePeriodExpiresDate + self.ownershipType = ownershipType + self.periodType = periodType + self.refundedAt = refundedAt + self.storeTransactionId = storeTransactionId + self.isActive = CustomerInfo.isDateActive(expirationDate: expiresDate, for: requestDate) + self.willRenew = EntitlementInfo.willRenewWithExpirationDate(expirationDate: expiresDate, + store: store, + unsubscribeDetectedAt: unsubscribeDetectedAt, + billingIssueDetectedAt: billingIssuesDetectedAt) + + super.init() + } + + public override var description: String { + return """ + SubscriptionInfo { + purchaseDate: \(String(describing: purchaseDate)), + originalPurchaseDate: \(String(describing: originalPurchaseDate)), + expiresDate: \(String(describing: expiresDate)), + store: \(store), + isSandbox: \(isSandbox), + unsubscribeDetectedAt: \(String(describing: unsubscribeDetectedAt)), + billingIssuesDetectedAt: \(String(describing: billingIssuesDetectedAt)), + gracePeriodExpiresDate: \(String(describing: gracePeriodExpiresDate)), + ownershipType: \(ownershipType), + periodType: \(String(describing: periodType)), + refundedAt: \(String(describing: refundedAt)), + storeTransactionId: \(String(describing: storeTransactionId)), + isActive: \(isActive), + willRenew: \(willRenew) + } + """ + } + +} + +extension SubscriptionInfo: Sendable {} diff --git a/Sources/Networking/Responses/CustomerInfoResponse.swift b/Sources/Networking/Responses/CustomerInfoResponse.swift index 3d3513fe4b..230db607bc 100644 --- a/Sources/Networking/Responses/CustomerInfoResponse.swift +++ b/Sources/Networking/Responses/CustomerInfoResponse.swift @@ -49,7 +49,7 @@ extension CustomerInfoResponse { @IgnoreDecodeErrors var periodType: PeriodType - var purchaseDate: Date? + var purchaseDate: Date var originalPurchaseDate: Date? var expiresDate: Date? @IgnoreDecodeErrors @@ -62,12 +62,15 @@ extension CustomerInfoResponse { var ownershipType: PurchaseOwnershipType var productPlanIdentifier: String? var metadata: [String: String]? + var gracePeriodExpiresDate: Date? + var refundedAt: Date? + var storeTransactionId: String? } struct Transaction { - var purchaseDate: Date? + var purchaseDate: Date var originalPurchaseDate: Date? var transactionIdentifier: String? var storeTransactionIdentifier: String? @@ -174,7 +177,7 @@ extension CustomerInfoResponse.Subscriber { extension CustomerInfoResponse.Transaction { init( - purchaseDate: Date?, + purchaseDate: Date, originalPurchaseDate: Date?, transactionIdentifier: String?, storeTransactionIdentifier: String?, @@ -202,14 +205,15 @@ extension CustomerInfoResponse.Subscription { init( periodType: PeriodType = .defaultValue, - purchaseDate: Date? = nil, + purchaseDate: Date, originalPurchaseDate: Date? = nil, expiresDate: Date? = nil, store: Store = .defaultValue, isSandbox: Bool, unsubscribeDetectedAt: Date? = nil, billingIssuesDetectedAt: Date? = nil, - ownershipType: PurchaseOwnershipType = .defaultValue + ownershipType: PurchaseOwnershipType = .defaultValue, + storeTransactionId: String? = nil ) { self.periodType = periodType self.purchaseDate = purchaseDate @@ -220,6 +224,7 @@ extension CustomerInfoResponse.Subscription { self.unsubscribeDetectedAt = unsubscribeDetectedAt self.billingIssuesDetectedAt = billingIssuesDetectedAt self.ownershipType = ownershipType + self.storeTransactionId = storeTransactionId } var asTransaction: CustomerInfoResponse.Transaction { diff --git a/Sources/Purchasing/EntitlementInfo.swift b/Sources/Purchasing/EntitlementInfo.swift index 987584e773..2f24ff4e5b 100644 --- a/Sources/Purchasing/EntitlementInfo.swift +++ b/Sources/Purchasing/EntitlementInfo.swift @@ -299,7 +299,7 @@ public extension EntitlementInfo { // MARK: - Internal -private extension EntitlementInfo { +extension EntitlementInfo { static func willRenewWithExpirationDate(expirationDate: Date?, store: Store, diff --git a/Sources/Purchasing/NonSubscriptionTransaction.swift b/Sources/Purchasing/NonSubscriptionTransaction.swift index a6b66406be..9f66970ec5 100644 --- a/Sources/Purchasing/NonSubscriptionTransaction.swift +++ b/Sources/Purchasing/NonSubscriptionTransaction.swift @@ -34,10 +34,14 @@ public final class NonSubscriptionTransaction: NSObject { /// The unique identifier for the transaction created by the Store. @objc public let storeTransactionIdentifier: String + /** + * The ``Store`` where this transaction was performed. + */ + @objc public let store: Store + init?(with transaction: CustomerInfoResponse.Transaction, productID: String) { guard let transactionIdentifier = transaction.transactionIdentifier, - let storeTransactionIdentifier = transaction.storeTransactionIdentifier, - let purchaseDate = transaction.purchaseDate else { + let storeTransactionIdentifier = transaction.storeTransactionIdentifier else { Logger.error("Couldn't initialize NonSubscriptionTransaction. " + "Reason: missing data: \(transaction).") return nil @@ -45,8 +49,9 @@ public final class NonSubscriptionTransaction: NSObject { self.transactionIdentifier = transactionIdentifier self.storeTransactionIdentifier = storeTransactionIdentifier - self.purchaseDate = purchaseDate + self.purchaseDate = transaction.purchaseDate self.productIdentifier = productID + self.store = transaction.store } public override var description: String { diff --git a/Tests/APITesters/AllAPITests/AllAPITests.xcodeproj/project.pbxproj b/Tests/APITesters/AllAPITests/AllAPITests.xcodeproj/project.pbxproj index 2e51ea5554..ca3babde38 100644 --- a/Tests/APITesters/AllAPITests/AllAPITests.xcodeproj/project.pbxproj +++ b/Tests/APITesters/AllAPITests/AllAPITests.xcodeproj/project.pbxproj @@ -121,6 +121,9 @@ 2D4C62D62C5D41E200A29FD2 /* RevenueCat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 2D4C62D42C5D41E200A29FD2 /* RevenueCat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 2D4C62D92C5D41EC00A29FD2 /* RevenueCat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D4C62D82C5D41EC00A29FD2 /* RevenueCat.framework */; }; 2D4C62DA2C5D41EC00A29FD2 /* RevenueCat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 2D4C62D82C5D41EC00A29FD2 /* RevenueCat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3502630E2CF61E9F00894270 /* SubscriptionInfoAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3502630D2CF61E9A00894270 /* SubscriptionInfoAPI.swift */; }; + 35370AC52CFF8304004F0A64 /* RCSubscriptionInfoAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 35370AC42CFF82F8004F0A64 /* RCSubscriptionInfoAPI.h */; }; + 35370AC82CFF8317004F0A64 /* RCSubscriptionInfoAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = 35370AC72CFF8312004F0A64 /* RCSubscriptionInfoAPI.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -342,6 +345,9 @@ 2D4C62D02C5D41D400A29FD2 /* ReceiptParser.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReceiptParser.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2D4C62D42C5D41E200A29FD2 /* RevenueCat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RevenueCat.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2D4C62D82C5D41EC00A29FD2 /* RevenueCat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RevenueCat.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3502630D2CF61E9A00894270 /* SubscriptionInfoAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionInfoAPI.swift; sourceTree = ""; }; + 35370AC42CFF82F8004F0A64 /* RCSubscriptionInfoAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCSubscriptionInfoAPI.h; sourceTree = ""; }; + 35370AC72CFF8312004F0A64 /* RCSubscriptionInfoAPI.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCSubscriptionInfoAPI.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -456,6 +462,8 @@ 2D4C613E2C5AD30400A29FD2 /* ObjcAPITester */ = { isa = PBXGroup; children = ( + 35370AC72CFF8312004F0A64 /* RCSubscriptionInfoAPI.m */, + 35370AC42CFF82F8004F0A64 /* RCSubscriptionInfoAPI.h */, 2D4C61732C5AD31900A29FD2 /* main.m */, 2D4C61582C5AD31600A29FD2 /* RCAttributionAPI.h */, 2D4C614C2C5AD31500A29FD2 /* RCAttributionAPI.m */, @@ -529,6 +537,7 @@ 2D4C61B42C5AD61800A29FD2 /* SwiftAPITester */ = { isa = PBXGroup; children = ( + 3502630D2CF61E9A00894270 /* SubscriptionInfoAPI.swift */, 2D4C61D32C5AD62900A29FD2 /* AttributionNetworkAPI.swift */, 2D4C61CC2C5AD62900A29FD2 /* AttributionAPI.swift */, 2D4C61D42C5AD62900A29FD2 /* ConfigurationAPI.swift */, @@ -637,6 +646,7 @@ buildActionMask = 2147483647; files = ( 2D4C61402C5AD30400A29FD2 /* ObjcAPITester.h in Headers */, + 35370AC52CFF8304004F0A64 /* RCSubscriptionInfoAPI.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -974,6 +984,7 @@ 2D4C617C2C5AD31A00A29FD2 /* RCStoreKitVersionAPI.m in Sources */, 2D4C61902C5AD31A00A29FD2 /* main.m in Sources */, 2D4C61932C5AD31A00A29FD2 /* RCPurchasesAPI.m in Sources */, + 35370AC82CFF8317004F0A64 /* RCSubscriptionInfoAPI.m in Sources */, 2D4C618C2C5AD31A00A29FD2 /* RCPurchasesDiagnosticsAPI.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -989,6 +1000,7 @@ 2D4C61DA2C5AD62A00A29FD2 /* RefundRequestStatusAPI.swift in Sources */, 2D4C61F52C5AD62A00A29FD2 /* PromotionalOfferAPI.swift in Sources */, 2D4C61E92C5AD62A00A29FD2 /* StoreProductAPI.swift in Sources */, + 3502630E2CF61E9F00894270 /* SubscriptionInfoAPI.swift in Sources */, 2D4C61EA2C5AD62A00A29FD2 /* StorefrontAPI.swift in Sources */, 2D4C61E42C5AD62A00A29FD2 /* main.swift in Sources */, 2D4C61E62C5AD62A00A29FD2 /* StoreProductDiscountAPI.swift in Sources */, diff --git a/Tests/APITesters/AllAPITests/ObjcAPITester/RCSubscriptionInfoAPI.h b/Tests/APITesters/AllAPITests/ObjcAPITester/RCSubscriptionInfoAPI.h new file mode 100644 index 0000000000..a87eafb710 --- /dev/null +++ b/Tests/APITesters/AllAPITests/ObjcAPITester/RCSubscriptionInfoAPI.h @@ -0,0 +1,19 @@ +// +// RCSubscriptionInfoAPI.h +// AllAPITests +// +// Created by Cesar de la Vega on 3/12/24. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RCSubscriptionInfoAPI : NSObject + ++ (void)checkAPI; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/Tests/APITesters/AllAPITests/ObjcAPITester/RCSubscriptionInfoAPI.m b/Tests/APITesters/AllAPITests/ObjcAPITester/RCSubscriptionInfoAPI.m new file mode 100644 index 0000000000..e165b18d11 --- /dev/null +++ b/Tests/APITesters/AllAPITests/ObjcAPITester/RCSubscriptionInfoAPI.m @@ -0,0 +1,34 @@ +// +// RCSubscriptionInfoAPI.m +// AllAPITests +// +// Created by Cesar de la Vega on 3/12/24. +// + +#import "RCSubscriptionInfoAPI.h" + +@import RevenueCat; + +@implementation RCSubscriptionInfoAPI + ++ (void)checkAPI { + RCSubscriptionInfo *subscription; + + NSString *productIdentifier __unused = subscription.productIdentifier; + NSDate *purchaseDate __unused = subscription.purchaseDate; + NSDate *originalPurchaseDate __unused = subscription.originalPurchaseDate; + NSDate *expiresDate __unused = subscription.expiresDate; + RCStore store __unused = subscription.store; + BOOL isSandbox __unused = subscription.isSandbox; + NSDate *unsubscribeDetectedAt __unused = subscription.unsubscribeDetectedAt; + NSDate *billingIssuesDetectedAt __unused = subscription.billingIssuesDetectedAt; + NSDate *gracePeriodExpiresDate __unused = subscription.gracePeriodExpiresDate; + RCPurchaseOwnershipType ownershipType __unused = subscription.ownershipType; + RCPeriodType periodType __unused = subscription.periodType; + NSDate *refundedAt __unused = subscription.refundedAt; + NSString *storeTransactionId __unused = subscription.storeTransactionId; + BOOL isActive __unused = subscription.isActive; + BOOL willRenew __unused = subscription.willRenew; +} + +@end \ No newline at end of file diff --git a/Tests/APITesters/AllAPITests/SwiftAPITester/CustomerInfoAPI.swift b/Tests/APITesters/AllAPITests/SwiftAPITester/CustomerInfoAPI.swift index de2811be07..362f8dd90a 100644 --- a/Tests/APITesters/AllAPITests/SwiftAPITester/CustomerInfoAPI.swift +++ b/Tests/APITesters/AllAPITests/SwiftAPITester/CustomerInfoAPI.swift @@ -17,7 +17,9 @@ import RevenueCat var customerInfo: CustomerInfo! func checkCustomerInfoAPI() { let entitlementInfo: EntitlementInfos = customerInfo.entitlements + let asubsp: Set = customerInfo.activeSubscriptions let asubs: Set = customerInfo.activeSubscriptions + let appisp: Set = customerInfo.allPurchasedProductIdentifiers let appis: Set = customerInfo.allPurchasedProductIdentifiers let led: Date? = customerInfo.latestExpirationDate @@ -40,8 +42,12 @@ func checkCustomerInfoAPI() { let _: String = customerInfo.id + let subs: [String: SubscriptionInfo] = customerInfo.subscriptionsByProductIdentifier + + let subsp: [ProductIdentifier: SubscriptionInfo] = customerInfo.subscriptionsByProductIdentifier + print(customerInfo!, entitlementInfo, asubs, appis, led!, nst, oav!, opd!, rDate!, fSeen, - oaud!, murl!, edfpi!, pdfpi!, exdf!, pdfe!, desc, rawData) + oaud!, murl!, edfpi!, pdfpi!, exdf!, pdfe!, desc, rawData, subs) } func checkCacheFetchPolicyEnum(_ policy: CacheFetchPolicy) { diff --git a/Tests/APITesters/AllAPITests/SwiftAPITester/SubscriptionInfoAPI.swift b/Tests/APITesters/AllAPITests/SwiftAPITester/SubscriptionInfoAPI.swift new file mode 100644 index 0000000000..829d5e62b7 --- /dev/null +++ b/Tests/APITesters/AllAPITests/SwiftAPITester/SubscriptionInfoAPI.swift @@ -0,0 +1,35 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// SubscriptionInfoAPI.swift +// +// Created by Cesar de la Vega on 26/11/24. + +import Foundation +import RevenueCat + +var subscription: SubscriptionInfo! +func checkSubscriptionInfoAPI() { + let pId: String = subscription.productIdentifier + let pIdP: ProductIdentifier = subscription.productIdentifier + let pd: Date = subscription.purchaseDate + let opd: Date? = subscription.originalPurchaseDate + let eDate: Date? = subscription.expiresDate + let store: Store = subscription.store + let iss: Bool = subscription.isSandbox + let uda: Date? = subscription.unsubscribeDetectedAt + let bida: Date? = subscription.billingIssuesDetectedAt + let gped: Date? = subscription.gracePeriodExpiresDate + let oType: PurchaseOwnershipType = subscription.ownershipType + let pType: PeriodType = subscription.periodType + let rAt: Date? = subscription.refundedAt + let txId: String? = subscription.storeTransactionId + let isActive: Bool = subscription.isActive + let willRenew: Bool = subscription.willRenew +} diff --git a/Tests/StoreKitUnitTests/BeginRefundRequestHelperTests.swift b/Tests/StoreKitUnitTests/BeginRefundRequestHelperTests.swift index 8b57577b51..d7d0c2b80a 100644 --- a/Tests/StoreKitUnitTests/BeginRefundRequestHelperTests.swift +++ b/Tests/StoreKitUnitTests/BeginRefundRequestHelperTests.swift @@ -238,7 +238,7 @@ private extension BeginRefundRequestHelperTests { "first_seen": "2019-06-17T16:05:33Z", "non_subscriptions": [:] as [String: Any], "subscriptions": [ - "onemonth_freetrial": [:] as [String: Any] + "onemonth_freetrial": ["purchase_date": "2018-10-26T23:17:53Z"] as [String: Any] ], "entitlements": [ "\(mockEntitlementID)": [ @@ -260,8 +260,8 @@ private extension BeginRefundRequestHelperTests { "first_seen": "2019-06-17T16:05:33Z", "non_subscriptions": [:] as [String: Any], "subscriptions": [ - "onemonth_freetrial": [:] as [String: Any], - "onemonth_freetrial2": [:] as [String: Any] + "onemonth_freetrial": ["purchase_date": "2018-10-26T23:17:53Z"] as [String: Any], + "onemonth_freetrial2": ["purchase_date": "2018-10-26T23:17:53Z"] as [String: Any] ], "entitlements": [ "\(mockEntitlementID)": [ @@ -287,7 +287,9 @@ private extension BeginRefundRequestHelperTests { "original_application_version": "2083", "first_seen": "2019-06-17T16:05:33Z", "non_subscriptions": [:] as [String: Any], - "subscriptions": [:] as [String: Any], + "subscriptions": [ + "onemonth_freetrial": ["purchase_date": "2018-10-26T23:17:53Z"] as [String: Any] + ], "entitlements": [ "\(mockEntitlementID)": [ "expires_date": "2000-08-30T02:40:36Z", @@ -307,7 +309,9 @@ private extension BeginRefundRequestHelperTests { "original_application_version": "2083", "first_seen": "2019-06-17T16:05:33Z", "non_subscriptions": [:] as [String: Any], - "subscriptions": [:] as [String: Any], + "subscriptions": [ + "onemonth_freetrial": ["purchase_date": "2018-10-26T23:17:53Z"] as [String: Any] + ], "entitlements": [ "pro": [ "expires_date": "2100-08-30T02:40:36Z", diff --git a/Tests/UnitTests/Identity/CustomerInfoManagerTests.swift b/Tests/UnitTests/Identity/CustomerInfoManagerTests.swift index 80bd9b21ff..7e668f23a8 100644 --- a/Tests/UnitTests/Identity/CustomerInfoManagerTests.swift +++ b/Tests/UnitTests/Identity/CustomerInfoManagerTests.swift @@ -264,11 +264,81 @@ class CustomerInfoManagerTests: BaseCustomerInfoManagerTests { "original_app_user_id": Self.appUserID, "first_seen": "2019-06-17T16:05:33Z", "subscriptions": [ - "product_a": ["expires_date": "2098-05-27T06:24:50Z", "period_type": "normal"], - "Product_B": ["expires_date": "2098-05-27T06:24:50Z", "period_type": "normal"], - "ProductC": ["expires_date": "2098-05-27T06:24:50Z", "period_type": "normal"], - "Pro": ["expires_date": "2098-05-27T06:24:50Z", "period_type": "normal"], - "ProductD": ["expires_date": "2018-05-27T06:24:50Z", "period_type": "normal"] + "product_a": [ + "purchase_date": "2098-04-27T06:24:50Z", + "expires_date": "2098-05-27T06:24:50Z", + "period_type": "normal", + "billing_issues_detected_at": nil, + "grace_period_expires_date": nil, + "is_sandbox": true, + "original_purchase_date": "2022-04-12T00:03:28Z", + "store": "app_store", + "unsubscribe_detected_at": nil, + "ownership_type": "PURCHASED", + "refunded_at": nil, + "store_transaction_id": "1", + "auto_resume_date": nil + ], + "Product_B": [ + "purchase_date": "2022-04-12T00:03:28Z", + "expires_date": "2098-05-27T06:24:50Z", + "period_type": "normal", + "billing_issues_detected_at": "2098-05-18T06:24:50Z", + "grace_period_expires_date": nil, + "is_sandbox": true, + "original_purchase_date": "2022-04-12T00:03:28Z", + "store": "app_store", + "unsubscribe_detected_at": "2098-05-14T06:24:50Z", + "ownership_type": "PURCHASED", + "refunded_at": "2098-05-16T06:24:50Z", + "store_transaction_id": "1", + "auto_resume_date": nil + ], + "ProductC": [ + "purchase_date": "2098-04-27T06:24:50Z", + "expires_date": "2098-05-27T06:24:50Z", + "period_type": "normal", + "billing_issues_detected_at": nil, + "grace_period_expires_date": nil, + "is_sandbox": true, + "original_purchase_date": "2022-04-12T00:03:28Z", + "store": "app_store", + "unsubscribe_detected_at": nil, + "ownership_type": "PURCHASED", + "refunded_at": nil, + "store_transaction_id": "1", + "auto_resume_date": nil + ], + "Pro": [ + "purchase_date": "2098-04-27T06:24:50Z", + "expires_date": "2098-05-27T06:24:50Z", + "period_type": "normal", + "billing_issues_detected_at": nil, + "grace_period_expires_date": nil, + "is_sandbox": true, + "original_purchase_date": "2022-04-12T00:03:28Z", + "store": "app_store", + "unsubscribe_detected_at": nil, + "ownership_type": "PURCHASED", + "refunded_at": nil, + "store_transaction_id": "1", + "auto_resume_date": nil + ], + "ProductD": [ + "purchase_date": "2018-04-27T06:24:50Z", + "expires_date": "2018-05-27T06:24:50Z", + "period_type": "normal", + "billing_issues_detected_at": nil, + "grace_period_expires_date": nil, + "is_sandbox": true, + "original_purchase_date": "2022-04-12T00:03:28Z", + "store": "app_store", + "unsubscribe_detected_at": nil, + "ownership_type": "PURCHASED", + "refunded_at": nil, + "store_transaction_id": "1", + "auto_resume_date": nil + ] ] as [String: Any], "other_purchases": [:] as [String: Any] ] as [String: Any] @@ -300,7 +370,23 @@ class CustomerInfoManagerTests: BaseCustomerInfoManagerTests { "subscriber": [ "original_app_user_id": Self.appUserID, "first_seen": "2019-06-17T16:05:33Z", - "subscriptions": ["product_a": ["expires_date": "2018-05-27T06:24:50Z", "period_type": "normal"]], + "subscriptions": [ + "product_a": [ + "purchase_date": "2018-04-27T06:24:50Z", + "expires_date": "2018-05-27T06:24:50Z", + "period_type": "normal", + "billing_issues_detected_at": nil, + "grace_period_expires_date": nil, + "is_sandbox": true, + "original_purchase_date": "2022-04-12T00:03:28Z", + "store": "app_store", + "unsubscribe_detected_at": nil, + "ownership_type": "PURCHASED", + "refunded_at": nil, + "store_transaction_id": "1", + "auto_resume_date": nil + ] + ], "other_purchases": [:] as [String: Any] ] as [String: Any] ]) @@ -329,7 +415,23 @@ class CustomerInfoManagerTests: BaseCustomerInfoManagerTests { "subscriber": [ "original_app_user_id": Self.appUserID, "first_seen": "2019-06-17T16:05:33Z", - "subscriptions": ["product_a": ["expires_date": "2018-05-27T06:24:50Z", "period_type": "normal"]], + "subscriptions": [ + "product_a": [ + "purchase_date": "2018-04-27T06:24:50Z", + "expires_date": "2018-05-27T06:24:50Z", + "period_type": "normal", + "billing_issues_detected_at": nil, + "grace_period_expires_date": nil, + "is_sandbox": true, + "original_purchase_date": "2022-04-12T00:03:28Z", + "store": "app_store", + "unsubscribe_detected_at": nil, + "ownership_type": "PURCHASED", + "refunded_at": nil, + "store_transaction_id": "1", + "auto_resume_date": nil + ] + ], "other_purchases": [:] as [String: Any] ] as [String: Any] ] @@ -350,7 +452,23 @@ class CustomerInfoManagerTests: BaseCustomerInfoManagerTests { "subscriber": [ "original_app_user_id": Self.appUserID, "first_seen": "2019-06-17T16:05:33Z", - "subscriptions": ["product_a": ["expires_date": "2018-05-27T06:24:50Z", "period_type": "normal"]], + "subscriptions": [ + "product_a": [ + "purchase_date": "2018-04-27T06:24:50Z", + "expires_date": "2018-05-27T06:24:50Z", + "period_type": "normal", + "billing_issues_detected_at": nil, + "grace_period_expires_date": nil, + "is_sandbox": true, + "original_purchase_date": "2022-04-12T00:03:28Z", + "store": "app_store", + "unsubscribe_detected_at": nil, + "ownership_type": "PURCHASED", + "refunded_at": nil, + "store_transaction_id": "1", + "auto_resume_date": nil + ] + ], "other_purchases": [:] as [String: Any] ] as [String: Any] ] diff --git a/Tests/UnitTests/Networking/Backend/BackendPostReceiptDataTests.swift b/Tests/UnitTests/Networking/Backend/BackendPostReceiptDataTests.swift index 429b4ca680..ddef8149f7 100644 --- a/Tests/UnitTests/Networking/Backend/BackendPostReceiptDataTests.swift +++ b/Tests/UnitTests/Networking/Backend/BackendPostReceiptDataTests.swift @@ -594,8 +594,9 @@ class BackendPostReceiptDataTests: BaseBackendPostReceiptDataTests { func testGetsUpdatedSubscriberInfoAfterPost() { var dateComponent = DateComponents() dateComponent.month = 1 + let today = Date() let futureDateString = ISO8601DateFormatter() - .string(from: Calendar.current.date(byAdding: dateComponent, to: Date())!) + .string(from: Calendar.current.date(byAdding: dateComponent, to: today)!) let getCustomerInfoPath: HTTPRequest.Path = .getCustomerInfo(appUserID: Self.userID) @@ -606,6 +607,7 @@ class BackendPostReceiptDataTests: BaseBackendPostReceiptDataTests { "original_app_user_id": "ORIGINAL", "subscriptions": [ "onemonth_freetrial": [ + "purchase_date": "2024-11-25T00:05:54Z", "expires_date": futureDateString ] ] @@ -619,9 +621,11 @@ class BackendPostReceiptDataTests: BaseBackendPostReceiptDataTests { "original_app_user_id": "UPDATED", "subscriptions": [ "onemonth_freetrial": [ + "purchase_date": "2024-11-25T00:05:54Z", "expires_date": futureDateString ], "twomonth_awesome": [ + "purchase_date": "2024-11-25T00:05:54Z", "expires_date": futureDateString ] ] diff --git a/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift b/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift index be6b606075..9d16422d86 100644 --- a/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift +++ b/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift @@ -148,6 +148,7 @@ extension BaseBackendTests { "original_app_user_id": "", "subscriptions": [ "onemonth_freetrial": [ + "purchase_date": "2017-07-30T02:40:36Z", "expires_date": "2017-08-30T02:40:36Z" ] ] diff --git a/Tests/UnitTests/OfflineEntitlements/CustomerInfoResponseHandlerTests.swift b/Tests/UnitTests/OfflineEntitlements/CustomerInfoResponseHandlerTests.swift index f6f903b858..6948681859 100644 --- a/Tests/UnitTests/OfflineEntitlements/CustomerInfoResponseHandlerTests.swift +++ b/Tests/UnitTests/OfflineEntitlements/CustomerInfoResponseHandlerTests.swift @@ -390,7 +390,7 @@ private extension BaseCustomerInfoResponseHandlerTests { static let purchasedProduct: PurchasedSK2Product = .init( productIdentifier: "product", - subscription: .init(), + subscription: .init(purchaseDate: Date()), entitlement: .init(productIdentifier: "entitlement", rawData: [:]) ) static let mapping: ProductEntitlementMapping = .init(entitlementsByProduct: [ diff --git a/Tests/UnitTests/Purchasing/CustomerInfoTests.swift b/Tests/UnitTests/Purchasing/CustomerInfoTests.swift index 7fb725a951..4a8ffee11a 100644 --- a/Tests/UnitTests/Purchasing/CustomerInfoTests.swift +++ b/Tests/UnitTests/Purchasing/CustomerInfoTests.swift @@ -22,14 +22,6 @@ class EmptyCustomerInfoTests: TestCase { class BasicCustomerInfoTests: TestCase { - private static func date(withDaysAgo days: Int) throws -> Date { - return try XCTUnwrap(Calendar.current.date(byAdding: .day, value: days, to: Date())) - } - - private static let expiredSubscriptionDate = ISO8601DateFormatter.default.string( - // swiftlint:disable:next force_try - from: try! BasicCustomerInfoTests.date(withDaysAgo: -1) - ) static let validSubscriberResponse: [String: Any] = [ "request_date": "2018-10-19T02:40:36Z", "request_date_ms": Int64(1563379533946), @@ -51,6 +43,7 @@ class BasicCustomerInfoTests: TestCase { ] as [String: Any], "subscriptions": [ "onemonth_freetrial": [ + "purchase_date": "2100-07-30T02:40:36Z", "expires_date": "2100-08-30T02:40:36Z", "period_type": "normal", "is_sandbox": false @@ -63,6 +56,7 @@ class BasicCustomerInfoTests: TestCase { "purchase_date": "2018-05-20T06:24:50Z" ], "onemonth": [ + "purchase_date": "2000-07-30T02:40:36Z", "expires_date": BasicCustomerInfoTests.expiredSubscriptionDate, "period_type": "normal", "is_sandbox": false @@ -103,17 +97,29 @@ class BasicCustomerInfoTests: TestCase { ] as [String: Any] ] - static let validTwoProductsJSON = "{" + - "\"request_date\": \"2018-05-20T06:24:50Z\"," + - "\"subscriber\": {" + - "\"first_seen\": \"2018-05-20T06:24:50Z\"," + - "\"original_application_version\": \"1.0\"," + - "\"original_app_user_id\": \"abcd\"," + - "\"other_purchases\": {}," + - "\"subscriptions\":{" + - "\"product_a\": {\"expires_date\": \"2018-05-27T06:24:50Z\",\"period_type\": \"normal\"}," + - "\"product_b\": {\"expires_date\": \"2018-05-27T05:24:50Z\",\"period_type\": \"normal\"}" + - "}}}" + static let validTwoProductsJSON = """ + { + "request_date": "2018-05-20T06:24:50Z", + "subscriber": { + "first_seen": "2018-05-20T06:24:50Z", + "original_application_version": "1.0", + "original_app_user_id": "abcd", + "other_purchases": {}, + "subscriptions": { + "product_a": { + "purchase_date": "2018-04-27T06:24:50Z", + "expires_date": "2018-05-27T06:24:50Z", + "period_type": "normal" + }, + "product_b": { + "purchase_date": "2018-04-27T05:24:50Z", + "expires_date": "2018-05-27T05:24:50Z", + "period_type": "normal" + } + } + } + } + """ private var customerInfo: CustomerInfo! @@ -397,18 +403,22 @@ class BasicCustomerInfoTests: TestCase { ], "subscriptions": [ "onemonth_freetrial": [ + "purchase_date": "2100-07-30T02:40:36Z", "expires_date": "2100-08-30T02:40:36Z", "period_type": "normal" ], "threemonth_freetrial": [ + "purchase_date": "1989-08-30T02:40:36Z", "expires_date": "1990-08-30T02:40:36Z", "period_type": "normal" ], "pro.1": [ + "purchase_date": "2100-07-30T02:40:36Z", "expires_date": "2100-08-30T02:40:36Z", "period_type": "normal" ], "pro.2": [ + "purchase_date": "1990-07-30T02:40:36Z", "expires_date": "1990-08-30T02:40:36Z", "period_type": "normal" ] @@ -468,9 +478,11 @@ class BasicCustomerInfoTests: TestCase { ], "subscriptions": [ "onemonth_freetrial": [ + "purchase_date": "2100-07-30T02:40:36Z", "expires_date": "2100-08-30T02:40:36Z" ], "threemonth_freetrial": [ + "purchase_date": "1990-08-30T02:40:36Z", "expires_date": "1990-08-30T02:40:36Z" ] ], @@ -547,6 +559,7 @@ class BasicCustomerInfoTests: TestCase { "original_app_user_id": "", "subscriptions": [ "pro.1": [ + "purchase_date": "2018-07-30T02:40:36Z", "expires_date": "2018-12-19T02:40:36Z" ]], "other_purchases": [:] as [String: Any], @@ -565,6 +578,7 @@ class BasicCustomerInfoTests: TestCase { "original_app_user_id": "", "subscriptions": [ "pro.1": [ + "purchase_date": "2018-07-30T02:40:36Z", "expires_date": "2018-12-19T02:40:36Z" ] ], @@ -784,13 +798,16 @@ class BasicCustomerInfoTests: TestCase { "non_subscriptions": [:] as [String: Any], "subscriptions": [ "onemonth_freetrial": [ + "purchase_date": "2100-07-30T02:40:36Z", "expires_date": "2100-08-30T02:40:36Z", "period_type": "normal" ], "twomonth_freetrial": [ + "purchase_date": "2100-07-30T02:40:36Z", "period_type": "normal" ], "threemonth_freetrial": [ + "purchase_date": "1990-07-30T02:40:36Z", "expires_date": "1990-08-30T02:40:36Z" ] ], @@ -828,13 +845,16 @@ class BasicCustomerInfoTests: TestCase { "non_subscriptions": [:] as [String: Any], "subscriptions": [ "onemonth_freetrial": [ + "purchase_date": "2100-07-30T02:40:36Z", "expires_date": "2100-08-30T02:40:36Z", "period_type": "normal" ], "twomonth_freetrial": [ + "purchase_date": "2100-07-30T02:40:36Z", "period_type": "normal" ], "threemonth_freetrial": [ + "purchase_date": "1990-07-30T02:40:36Z", "expires_date": "1990-08-30T02:40:36Z" ] ], @@ -915,9 +935,35 @@ class BasicCustomerInfoTests: TestCase { expect(self.customerInfo.copy(with: .verifiedOnDevice).isComputedOffline) == true } - // MARK: - Private +} + +extension CustomerInfo { - private func verifyCopy( + convenience init?(testData: [String: Any]) { + do { + try self.init(data: testData) + } catch { + let errorDescription = (error as? DescribableError)?.description ?? error.localizedDescription + Logger.error("Caught error creating testData, this is probably expected, right? \(errorDescription).") + + return nil + } + } + +} + +private extension BasicCustomerInfoTests { + + static func date(withDaysAgo days: Int) throws -> Date { + return try XCTUnwrap(Calendar.current.date(byAdding: .day, value: days, to: Date())) + } + + static let expiredSubscriptionDate = ISO8601DateFormatter.default.string( + // swiftlint:disable:next force_try + from: try! BasicCustomerInfoTests.date(withDaysAgo: -1) + ) + + func verifyCopy( of customerInfo: CustomerInfo, onlyModifiesEntitlementVerification newVerification: VerificationResult ) { @@ -930,7 +976,7 @@ class BasicCustomerInfoTests: TestCase { expect(copyWithOriginalVerification) == customerInfo } - private func verifyCopy( + func verifyCopy( of customerInfo: CustomerInfo, onlyModifiesRequestDate newRequestDate: Date ) { @@ -946,21 +992,6 @@ class BasicCustomerInfoTests: TestCase { } -extension CustomerInfo { - - convenience init?(testData: [String: Any]) { - do { - try self.init(data: testData) - } catch { - let errorDescription = (error as? DescribableError)?.description ?? error.localizedDescription - Logger.error("Caught error creating testData, this is probably expected, right? \(errorDescription).") - - return nil - } - } - -} - private extension BasicCustomerInfoTests { static let sampleTestDataWithEntitlements: [String: Any] = [ diff --git a/Tests/UnitTests/SubscriberAttributes/BackendSubscriberAttributesTests.swift b/Tests/UnitTests/SubscriberAttributes/BackendSubscriberAttributesTests.swift index 0b57c289a2..9a63e90000 100644 --- a/Tests/UnitTests/SubscriberAttributes/BackendSubscriberAttributesTests.swift +++ b/Tests/UnitTests/SubscriberAttributes/BackendSubscriberAttributesTests.swift @@ -41,6 +41,7 @@ class BackendSubscriberAttributesTests: TestCase { "original_app_user_id": "app_user_id", "subscriptions": [ "onemonth_freetrial": [ + "purchase_date": "2017-07-30T02:40:36Z", "expires_date": "2017-08-30T02:40:36Z" ] ]