diff --git a/Sources/Identity/CustomerInfo.swift b/Sources/Identity/CustomerInfo.swift index ef84c9b6e3..5a614a945f 100644 --- a/Sources/Identity/CustomerInfo.swift +++ b/Sources/Identity/CustomerInfo.swift @@ -42,6 +42,11 @@ import Foundation /** * Returns all the non-subscription purchases a user has made. * The purchases are ordered by purchase date in ascending order. + * + * This includes: + * - Consumables + * - Non-consumables + * - Non-renewing subscriptions */ @objc public let nonSubscriptions: [NonSubscriptionTransaction] diff --git a/Sources/Purchasing/NonSubscriptionTransaction.swift b/Sources/Purchasing/NonSubscriptionTransaction.swift index 803bcb137d..b204f4a887 100644 --- a/Sources/Purchasing/NonSubscriptionTransaction.swift +++ b/Sources/Purchasing/NonSubscriptionTransaction.swift @@ -14,6 +14,11 @@ import Foundation /// Information that represents a non-subscription purchase made by a user. +/// +/// This can be one of these types of product: +/// - Consumables +/// - Non-consumables +/// - Non-renewing subscriptions @objc(RCNonSubscriptionTransaction) public final class NonSubscriptionTransaction: NSObject { diff --git a/Tests/BackendIntegrationTests/BaseStoreKitIntegrationTests.swift b/Tests/BackendIntegrationTests/BaseStoreKitIntegrationTests.swift index 971179e8e9..5abb684a25 100644 --- a/Tests/BackendIntegrationTests/BaseStoreKitIntegrationTests.swift +++ b/Tests/BackendIntegrationTests/BaseStoreKitIntegrationTests.swift @@ -84,6 +84,8 @@ extension BaseStoreKitIntegrationTests { static let entitlementIdentifier = "premium" static let consumable10Coins = "consumable.10_coins" + static let nonConsumableLifetime = "lifetime" + static let nonRenewingPackage = "non_renewing" static let monthlyNoIntroProductID = "com.revenuecat.monthly_4.99.no_intro" static let group3MonthlyTrialProductID = "com.revenuecat.monthly.1.99.1_free_week" static let group3MonthlyNoTrialProductID = "com.revenuecat.monthly.1.99.no_intro" @@ -163,6 +165,24 @@ extension BaseStoreKitIntegrationTests { return try await self.purchases.purchase(package: package) } + @discardableResult + func purchaseNonConsumablePackage( + file: FileString = #file, + line: UInt = #line + ) async throws -> PurchaseResultData { + let package = try await XCTAsyncUnwrap(try await self.currentOffering.lifetime) + return try await self.purchases.purchase(package: package) + } + + @discardableResult + func purchaseNonRenewingSubscriptionPackage( + file: FileString = #file, + line: UInt = #line + ) async throws -> PurchaseResultData { + let package = try await XCTAsyncUnwrap(try await self.currentOffering[Self.nonRenewingPackage]) + return try await self.purchases.purchase(package: package) + } + @discardableResult func verifyEntitlementWentThrough( _ customerInfo: CustomerInfo, diff --git a/Tests/BackendIntegrationTests/OtherIntegrationTests.swift b/Tests/BackendIntegrationTests/OtherIntegrationTests.swift index 72ed37ba73..7bb8e6a80a 100644 --- a/Tests/BackendIntegrationTests/OtherIntegrationTests.swift +++ b/Tests/BackendIntegrationTests/OtherIntegrationTests.swift @@ -93,10 +93,12 @@ class OtherIntegrationTests: BaseBackendIntegrationTests { func testProductEntitlementMapping() async throws { try AvailabilityChecks.iOS15APIAvailableOrSkipTest() - let result = try await self.purchases.productEntitlementMapping() - expect(result.entitlementsByProduct).to(haveCount(15)) - expect(result.entitlementsByProduct["com.revenuecat.monthly_4.99.1_week_intro"]) == ["premium"] - expect(result.entitlementsByProduct["com.revenuecat.intro_test.monthly.1_week_intro"]).to(beEmpty()) + let result = try await self.purchases.productEntitlementMapping().entitlementsByProduct + expect(result).to(haveCount(17)) + expect(result["com.revenuecat.monthly_4.99.1_week_intro"]) == ["premium"] + expect(result["lifetime"]) == ["premium"] + expect(result["com.revenuecat.intro_test.monthly.1_week_intro"]).to(beEmpty()) + expect(result["consumable.10_coins"]).to(beEmpty()) } @available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) diff --git a/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift b/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift index 4b18b19fa8..0f1c506093 100644 --- a/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift +++ b/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift @@ -134,6 +134,30 @@ class StoreKit1IntegrationTests: BaseStoreKitIntegrationTests { verifyPurchase(info2) } + func testCanPurchaseNonConsumable() async throws { + let result = try await self.purchaseNonConsumablePackage() + let transaction = try XCTUnwrap(result.transaction) + let info = result.customerInfo + let nonSubscription = try XCTUnwrap(info.nonSubscriptions.onlyElement) + + expect(info.allPurchasedProductIdentifiers).to(contain(Self.nonConsumableLifetime)) + expect(nonSubscription.productIdentifier) == transaction.productIdentifier + + try await self.verifyEntitlementWentThrough(info) + } + + func testCanPurchaseNonRenewingSubscription() async throws { + let result = try await self.purchaseNonRenewingSubscriptionPackage() + let transaction = try XCTUnwrap(result.transaction) + let info = result.customerInfo + let nonSubscription = try XCTUnwrap(info.nonSubscriptions.onlyElement) + + expect(info.allPurchasedProductIdentifiers).to(contain(transaction.productIdentifier)) + expect(nonSubscription.productIdentifier) == transaction.productIdentifier + + try await self.verifyEntitlementWentThrough(info) + } + func testCanPurchaseMultipleSubscriptions() async throws { let product1 = try await self.monthlyPackage.storeProduct let product2 = try await self.annualPackage.storeProduct diff --git a/Tests/BackendIntegrationTests/__Snapshots__/StoreKitIntegrationTests/testCanGetOfferings.1.json b/Tests/BackendIntegrationTests/__Snapshots__/StoreKitIntegrationTests/testCanGetOfferings.1.json index 89e9ac31b7..2ab1b2f771 100644 --- a/Tests/BackendIntegrationTests/__Snapshots__/StoreKitIntegrationTests/testCanGetOfferings.1.json +++ b/Tests/BackendIntegrationTests/__Snapshots__/StoreKitIntegrationTests/testCanGetOfferings.1.json @@ -19,6 +19,14 @@ { "identifier" : "$rc_weekly", "platform_product_identifier" : "com.revenuecat.weekly_1.99.3_day_intro" + }, + { + "identifier" : "$rc_lifetime", + "platform_product_identifier" : "lifetime" + }, + { + "identifier" : "non_renewing", + "platform_product_identifier" : "non_renewing_subscription" } ] }, diff --git a/Tests/TestingApps/PurchaseTester/RevenueCat_IntegrationPurchaseTesterConfiguration.storekit b/Tests/TestingApps/PurchaseTester/RevenueCat_IntegrationPurchaseTesterConfiguration.storekit index 88f674af43..b2bd94c5cd 100644 --- a/Tests/TestingApps/PurchaseTester/RevenueCat_IntegrationPurchaseTesterConfiguration.storekit +++ b/Tests/TestingApps/PurchaseTester/RevenueCat_IntegrationPurchaseTesterConfiguration.storekit @@ -1,7 +1,21 @@ { "identifier" : "B495CDA2", "nonRenewingSubscriptions" : [ - + { + "displayPrice" : "0.99", + "familyShareable" : false, + "internalID" : "A13FD1E6", + "localizations" : [ + { + "description" : "", + "displayName" : "", + "locale" : "en_US" + } + ], + "productID" : "non_renewing_subscription", + "referenceName" : "Non-renewing subscription", + "type" : "NonRenewingSubscription" + } ], "products" : [ {