diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 16f8ba24..985024db 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,6 +56,23 @@ jobs: - name: Test public API run: ./gradlew :apiTester:assemble + test-unit-tests: + runs-on: macos-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: gradle/wrapper-validation-action@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 + - name: Execute unit tests + run: ./gradlew allTests + build: needs: [ detekt, validate-binary-compatibility, test-public-api ] runs-on: macos-latest diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ac29d83c..038c3ac8 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -56,6 +56,23 @@ jobs: - name: Test public API run: ./gradlew :apiTester:assemble + test-unit-tests: + runs-on: macos-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: gradle/wrapper-validation-action@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 + - name: Execute unit tests + run: ./gradlew allTests + build: needs: [ detekt, validate-binary-compatibility, test-public-api ] runs-on: macos-latest diff --git a/README.md b/README.md index 36fbf195..9f6e44b3 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Please follow the [Quickstart Guide](https://docs.revenuecat.com/docs/) for more - Java 8+ - Kotlin 1.9.0+ - Android 5.0+ (API level 21+) -- iOS 11.0+ +- iOS 13.0+ ## SDK Reference Our full SDK reference [can be found here](https://revenuecat.github.io/purchases-kmp/). diff --git a/apiTester/src/androidMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesAreCompletedByAPI.kt b/apiTester/src/androidMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesAreCompletedByAPI.kt deleted file mode 100644 index 0122e1d6..00000000 --- a/apiTester/src/androidMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesAreCompletedByAPI.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.revenuecat.purchases.kmp.apitester - -import com.revenuecat.purchases.kmp.PurchasesAreCompletedBy - -@Suppress("unused") -private class PurchasesAreCompletedByAPI { - fun check(mode: PurchasesAreCompletedBy) { - when (mode) { - PurchasesAreCompletedBy.REVENUECAT, - PurchasesAreCompletedBy.MY_APP, - -> { - } - }.exhaustive - } -} diff --git a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/InstallmentsInfoAPI.kt b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/InstallmentsInfoAPI.kt new file mode 100644 index 00000000..63c1c4b9 --- /dev/null +++ b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/InstallmentsInfoAPI.kt @@ -0,0 +1,17 @@ +package com.revenuecat.purchases.kmp.apitester + +import com.revenuecat.purchases.kmp.PresentedOfferingContext +import com.revenuecat.purchases.kmp.StoreKitVersion +import com.revenuecat.purchases.kmp.models.InstallmentsInfo +import com.revenuecat.purchases.kmp.models.PricingPhase +import com.revenuecat.purchases.kmp.models.SubscriptionOption +import com.revenuecat.purchases.kmp.models.isBasePlan +import com.revenuecat.purchases.kmp.models.isPrepaid + +@Suppress("unused") +private class InstallmentsInfoAPI { + fun checkInstallmentsInfo(installmentsInfo: InstallmentsInfo) { + val commitmentPaymentsCount: Int = installmentsInfo.commitmentPaymentsCount + val renewalCommitmentPaymentsCount: Int = installmentsInfo.renewalCommitmentPaymentsCount + } +} diff --git a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesAreCompletedByAPI.kt b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesAreCompletedByAPI.kt new file mode 100644 index 00000000..d4ae19f0 --- /dev/null +++ b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesAreCompletedByAPI.kt @@ -0,0 +1,20 @@ +package com.revenuecat.purchases.kmp.apitester + +import com.revenuecat.purchases.kmp.PurchasesAreCompletedBy +import com.revenuecat.purchases.kmp.StoreKitVersion + +@Suppress("unused") +private class PurchasesAreCompletedByAPI { + fun check(mode: PurchasesAreCompletedBy) { + when (mode) { + is PurchasesAreCompletedBy.RevenueCat -> { } + is PurchasesAreCompletedBy.MyApp -> { + val storeKitVersion: StoreKitVersion = mode.storeKitVersion + } + }.exhaustive + } + + fun checkStoreKitValueInPurchasesAreCompletedByMyApp() { + PurchasesAreCompletedBy.MyApp(storeKitVersion = StoreKitVersion.STOREKIT_2) + } +} \ No newline at end of file diff --git a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesCommonAPI.kt b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesCommonAPI.kt index 87792034..1902a396 100644 --- a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesCommonAPI.kt +++ b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesCommonAPI.kt @@ -9,11 +9,12 @@ import com.revenuecat.purchases.kmp.LogLevel import com.revenuecat.purchases.kmp.Offerings import com.revenuecat.purchases.kmp.Package import com.revenuecat.purchases.kmp.Purchases -import com.revenuecat.purchases.kmp.PurchasesAreCompletedBy.MY_APP +import com.revenuecat.purchases.kmp.PurchasesAreCompletedBy import com.revenuecat.purchases.kmp.PurchasesConfiguration import com.revenuecat.purchases.kmp.PurchasesDelegate import com.revenuecat.purchases.kmp.PurchasesError import com.revenuecat.purchases.kmp.Store +import com.revenuecat.purchases.kmp.StoreKitVersion import com.revenuecat.purchases.kmp.configure import com.revenuecat.purchases.kmp.either.FailedPurchase import com.revenuecat.purchases.kmp.either.awaitGetProductsEither @@ -240,7 +241,20 @@ private class PurchasesCommonAPI { val config: PurchasesConfiguration = PurchasesConfiguration(apiKey = "") { appUserId = "" - purchasesAreCompletedBy = MY_APP + purchasesAreCompletedBy = PurchasesAreCompletedBy.RevenueCat + userDefaultsSuiteName = "" + storeKitVersion = StoreKitVersion.DEFAULT + showInAppMessagesAutomatically = true + store = Store.PLAY_STORE + diagnosticsEnabled = true + dangerousSettings = DangerousSettings(autoSyncPurchases = true) + verificationMode = EntitlementVerificationMode.INFORMATIONAL + pendingTransactionsForPrepaidPlansEnabled = true + } + + val config2: PurchasesConfiguration = PurchasesConfiguration(apiKey = "") { + appUserId = "" + purchasesAreCompletedBy = PurchasesAreCompletedBy.MyApp(StoreKitVersion.DEFAULT) userDefaultsSuiteName = "" showInAppMessagesAutomatically = true store = Store.PLAY_STORE @@ -252,7 +266,7 @@ private class PurchasesCommonAPI { val configuredInstance: Purchases = Purchases.configure(config) val otherConfiguredInstance: Purchases = Purchases.configure(apiKey = "") { appUserId = "" - purchasesAreCompletedBy = MY_APP + purchasesAreCompletedBy = PurchasesAreCompletedBy.MyApp(StoreKitVersion.DEFAULT) userDefaultsSuiteName = "" showInAppMessagesAutomatically = true store = Store.PLAY_STORE @@ -262,6 +276,14 @@ private class PurchasesCommonAPI { } val instance: Purchases = Purchases.sharedInstance } + + fun checkRecordPurchase() { + Purchases.sharedInstance.recordPurchase( + productID = "myProductID", + onError = { error -> }, + onSuccess = { storeTransaction -> } + ) + } fun checkLogHandler() { Purchases.logHandler = object : LogHandler { diff --git a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/StoreKitVersionAPI.kt b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/StoreKitVersionAPI.kt new file mode 100644 index 00000000..df9fc978 --- /dev/null +++ b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/StoreKitVersionAPI.kt @@ -0,0 +1,16 @@ +package com.revenuecat.purchases.kmp.apitester + +import com.revenuecat.purchases.kmp.StoreKitVersion + +@Suppress("unused") +private class StoreKitVersionAPI { + fun check(skVersion: StoreKitVersion) { + when (skVersion) { + StoreKitVersion.STOREKIT_1, + StoreKitVersion.STOREKIT_2, + StoreKitVersion.DEFAULT, + -> { + } + }.exhaustive + } +} \ No newline at end of file diff --git a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/SubscriptionOptionAPI.kt b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/SubscriptionOptionAPI.kt index 425d3324..5c0dd1e3 100644 --- a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/SubscriptionOptionAPI.kt +++ b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/SubscriptionOptionAPI.kt @@ -1,6 +1,7 @@ package com.revenuecat.purchases.kmp.apitester import com.revenuecat.purchases.kmp.PresentedOfferingContext +import com.revenuecat.purchases.kmp.models.InstallmentsInfo import com.revenuecat.purchases.kmp.models.PricingPhase import com.revenuecat.purchases.kmp.models.SubscriptionOption import com.revenuecat.purchases.kmp.models.isBasePlan @@ -16,5 +17,6 @@ private class SubscriptionOptionAPI { val presentedOfferingContext: PresentedOfferingContext? = subscriptionOption.presentedOfferingContext val isPrepaid: Boolean = subscriptionOption.isPrepaid + val installmentsInfo: InstallmentsInfo? = subscriptionOption.installmentsInfo } } diff --git a/apiTester/src/iosMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesAreCompletedByAPI.kt b/apiTester/src/iosMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesAreCompletedByAPI.kt deleted file mode 100644 index 0122e1d6..00000000 --- a/apiTester/src/iosMain/kotlin/com/revenuecat/purchases/kmp/apitester/PurchasesAreCompletedByAPI.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.revenuecat.purchases.kmp.apitester - -import com.revenuecat.purchases.kmp.PurchasesAreCompletedBy - -@Suppress("unused") -private class PurchasesAreCompletedByAPI { - fun check(mode: PurchasesAreCompletedBy) { - when (mode) { - PurchasesAreCompletedBy.REVENUECAT, - PurchasesAreCompletedBy.MY_APP, - -> { - } - }.exhaustive - } -} diff --git a/core/api/core.api b/core/api/core.api index 4be36fd9..44c445b6 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -134,7 +134,6 @@ public final class com/revenuecat/purchases/kmp/Purchases { public final fun getProducts (Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V public final fun getPromotionalOffer (Lcom/revenuecat/purchases/kmp/models/StoreProductDiscount;Lcom/revenuecat/purchases/kmp/models/StoreProduct;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V public static final fun getProxyURL ()Ljava/lang/String; - public final fun getPurchasesAreCompletedBy ()Lcom/revenuecat/purchases/PurchasesAreCompletedBy; public static final fun getSharedInstance ()Lcom/revenuecat/purchases/kmp/Purchases; public static final fun getSimulatesAskToBuyInSandbox ()Z public final fun getStore ()Lcom/revenuecat/purchases/Store; @@ -151,6 +150,7 @@ public final class com/revenuecat/purchases/kmp/Purchases { public static synthetic fun purchase$default (Lcom/revenuecat/purchases/kmp/Purchases;Lcom/revenuecat/purchases/Package;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Ljava/lang/Boolean;Ljava/lang/String;Lcom/revenuecat/purchases/models/GoogleReplacementMode;ILjava/lang/Object;)V public static synthetic fun purchase$default (Lcom/revenuecat/purchases/kmp/Purchases;Lcom/revenuecat/purchases/kmp/models/StoreProduct;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Ljava/lang/Boolean;Ljava/lang/String;Lcom/revenuecat/purchases/models/GoogleReplacementMode;ILjava/lang/Object;)V public static synthetic fun purchase$default (Lcom/revenuecat/purchases/kmp/Purchases;Lcom/revenuecat/purchases/models/SubscriptionOption;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Ljava/lang/Boolean;Ljava/lang/String;Lcom/revenuecat/purchases/models/GoogleReplacementMode;ILjava/lang/Object;)V + public final fun recordPurchase (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V public final fun restorePurchases (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V public final fun setAd (Ljava/lang/String;)V public final fun setAdGroup (Ljava/lang/String;)V @@ -177,7 +177,6 @@ public final class com/revenuecat/purchases/kmp/Purchases { public final fun setOnesignalUserID (Ljava/lang/String;)V public final fun setPhoneNumber (Ljava/lang/String;)V public static final fun setProxyURL (Ljava/lang/String;)V - public final fun setPurchasesAreCompletedBy (Lcom/revenuecat/purchases/PurchasesAreCompletedBy;)V public final fun setPushToken (Ljava/lang/String;)V public static final fun setSimulatesAskToBuyInSandbox (Z)V public final fun showInAppMessagesIfNeeded (Ljava/util/List;)V @@ -205,15 +204,38 @@ public final class com/revenuecat/purchases/kmp/Purchases$Companion { public final fun setSimulatesAskToBuyInSandbox (Z)V } +public abstract interface class com/revenuecat/purchases/kmp/PurchasesAreCompletedBy { +} + +public final class com/revenuecat/purchases/kmp/PurchasesAreCompletedBy$MyApp : com/revenuecat/purchases/kmp/PurchasesAreCompletedBy { + public fun (Lcom/revenuecat/purchases/kmp/StoreKitVersion;)V + public final fun component1 ()Lcom/revenuecat/purchases/kmp/StoreKitVersion; + public final fun copy (Lcom/revenuecat/purchases/kmp/StoreKitVersion;)Lcom/revenuecat/purchases/kmp/PurchasesAreCompletedBy$MyApp; + public static synthetic fun copy$default (Lcom/revenuecat/purchases/kmp/PurchasesAreCompletedBy$MyApp;Lcom/revenuecat/purchases/kmp/StoreKitVersion;ILjava/lang/Object;)Lcom/revenuecat/purchases/kmp/PurchasesAreCompletedBy$MyApp; + public fun equals (Ljava/lang/Object;)Z + public final fun getStoreKitVersion ()Lcom/revenuecat/purchases/kmp/StoreKitVersion; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/revenuecat/purchases/kmp/PurchasesAreCompletedBy$RevenueCat : com/revenuecat/purchases/kmp/PurchasesAreCompletedBy { + public static final field INSTANCE Lcom/revenuecat/purchases/kmp/PurchasesAreCompletedBy$RevenueCat; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class com/revenuecat/purchases/kmp/PurchasesConfiguration { - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lcom/revenuecat/purchases/PurchasesAreCompletedBy;Ljava/lang/String;ZLcom/revenuecat/purchases/Store;ZLcom/revenuecat/purchases/kmp/DangerousSettings;Lcom/revenuecat/purchases/kmp/EntitlementVerificationMode;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lcom/revenuecat/purchases/kmp/PurchasesAreCompletedBy;Ljava/lang/String;Lcom/revenuecat/purchases/kmp/StoreKitVersion;ZLcom/revenuecat/purchases/Store;ZLcom/revenuecat/purchases/kmp/DangerousSettings;Lcom/revenuecat/purchases/kmp/EntitlementVerificationMode;Ljava/lang/Boolean;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getApiKey ()Ljava/lang/String; public final fun getAppUserId ()Ljava/lang/String; public final fun getDangerousSettings ()Lcom/revenuecat/purchases/kmp/DangerousSettings; public final fun getDiagnosticsEnabled ()Z - public final fun getPurchasesAreCompletedBy ()Lcom/revenuecat/purchases/PurchasesAreCompletedBy; + public final fun getPendingTransactionsForPrepaidPlansEnabled ()Ljava/lang/Boolean; + public final fun getPurchasesAreCompletedBy ()Lcom/revenuecat/purchases/kmp/PurchasesAreCompletedBy; public final fun getShowInAppMessagesAutomatically ()Z public final fun getStore ()Lcom/revenuecat/purchases/Store; + public final fun getStoreKitVersion ()Lcom/revenuecat/purchases/kmp/StoreKitVersion; public final fun getUserDefaultsSuiteName ()Ljava/lang/String; public final fun getVerificationMode ()Lcom/revenuecat/purchases/kmp/EntitlementVerificationMode; public fun toString ()Ljava/lang/String; @@ -230,23 +252,29 @@ public final class com/revenuecat/purchases/kmp/PurchasesConfiguration$Builder { public final fun getAppUserId ()Ljava/lang/String; public final fun getDangerousSettings ()Lcom/revenuecat/purchases/kmp/DangerousSettings; public final fun getDiagnosticsEnabled ()Z - public final fun getPurchasesAreCompletedBy ()Lcom/revenuecat/purchases/PurchasesAreCompletedBy; + public final fun getPendingTransactionsForPrepaidPlansEnabled ()Ljava/lang/Boolean; + public final fun getPurchasesAreCompletedBy ()Lcom/revenuecat/purchases/kmp/PurchasesAreCompletedBy; public final fun getShowInAppMessagesAutomatically ()Z public final fun getStore ()Lcom/revenuecat/purchases/Store; + public final fun getStoreKitVersion ()Lcom/revenuecat/purchases/kmp/StoreKitVersion; public final fun getUserDefaultsSuiteName ()Ljava/lang/String; public final fun getVerificationMode ()Lcom/revenuecat/purchases/kmp/EntitlementVerificationMode; - public final fun purchasesAreCompletedBy (Lcom/revenuecat/purchases/PurchasesAreCompletedBy;)Lcom/revenuecat/purchases/kmp/PurchasesConfiguration$Builder; + public final fun pendingTransactionsForPrepaidPlansEnabled (Z)Lcom/revenuecat/purchases/kmp/PurchasesConfiguration$Builder; + public final fun purchasesAreCompletedBy (Lcom/revenuecat/purchases/kmp/PurchasesAreCompletedBy;)Lcom/revenuecat/purchases/kmp/PurchasesConfiguration$Builder; public final synthetic fun setApiKey (Ljava/lang/String;)V public final synthetic fun setAppUserId (Ljava/lang/String;)V public final synthetic fun setDangerousSettings (Lcom/revenuecat/purchases/kmp/DangerousSettings;)V public final synthetic fun setDiagnosticsEnabled (Z)V - public final synthetic fun setPurchasesAreCompletedBy (Lcom/revenuecat/purchases/PurchasesAreCompletedBy;)V + public final synthetic fun setPendingTransactionsForPrepaidPlansEnabled (Ljava/lang/Boolean;)V + public final synthetic fun setPurchasesAreCompletedBy (Lcom/revenuecat/purchases/kmp/PurchasesAreCompletedBy;)V public final synthetic fun setShowInAppMessagesAutomatically (Z)V public final synthetic fun setStore (Lcom/revenuecat/purchases/Store;)V + public final synthetic fun setStoreKitVersion (Lcom/revenuecat/purchases/kmp/StoreKitVersion;)V public final synthetic fun setUserDefaultsSuiteName (Ljava/lang/String;)V public final synthetic fun setVerificationMode (Lcom/revenuecat/purchases/kmp/EntitlementVerificationMode;)V public final fun showInAppMessagesAutomatically (Z)Lcom/revenuecat/purchases/kmp/PurchasesConfiguration$Builder; public final fun store (Lcom/revenuecat/purchases/Store;)Lcom/revenuecat/purchases/kmp/PurchasesConfiguration$Builder; + public final fun storeKitVersion (Lcom/revenuecat/purchases/kmp/StoreKitVersion;)Lcom/revenuecat/purchases/kmp/PurchasesConfiguration$Builder; public final fun userDefaultsSuiteName (Ljava/lang/String;)Lcom/revenuecat/purchases/kmp/PurchasesConfiguration$Builder; public final fun verificationMode (Lcom/revenuecat/purchases/kmp/EntitlementVerificationMode;)Lcom/revenuecat/purchases/kmp/PurchasesConfiguration$Builder; } @@ -334,6 +362,15 @@ public final class com/revenuecat/purchases/kmp/PurchasesTransactionException : public final fun getUserCancelled ()Z } +public final class com/revenuecat/purchases/kmp/StoreKitVersion : java/lang/Enum { + public static final field DEFAULT Lcom/revenuecat/purchases/kmp/StoreKitVersion; + public static final field STOREKIT_1 Lcom/revenuecat/purchases/kmp/StoreKitVersion; + public static final field STOREKIT_2 Lcom/revenuecat/purchases/kmp/StoreKitVersion; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/revenuecat/purchases/kmp/StoreKitVersion; + public static fun values ()[Lcom/revenuecat/purchases/kmp/StoreKitVersion; +} + public final class com/revenuecat/purchases/kmp/UpdatedCustomerInfoDelegate : com/revenuecat/purchases/kmp/PurchasesDelegate { public fun (Lkotlin/jvm/functions/Function1;)V public fun onCustomerInfoUpdated (Lcom/revenuecat/purchases/CustomerInfo;)V diff --git a/core/api/core.klib.api b/core/api/core.klib.api index 13fe2947..ecc1fe32 100644 --- a/core/api/core.klib.api +++ b/core/api/core.klib.api @@ -6,6 +6,12 @@ // - Show declarations: true // Library unique name: +abstract interface com.revenuecat.purchases.kmp.models/InstallmentsInfo { // com.revenuecat.purchases.kmp.models/InstallmentsInfo|null[0] + abstract val commitmentPaymentsCount // com.revenuecat.purchases.kmp.models/InstallmentsInfo.commitmentPaymentsCount|{}commitmentPaymentsCount[0] + abstract fun (): kotlin/Int // com.revenuecat.purchases.kmp.models/InstallmentsInfo.commitmentPaymentsCount.|(){}[0] + abstract val renewalCommitmentPaymentsCount // com.revenuecat.purchases.kmp.models/InstallmentsInfo.renewalCommitmentPaymentsCount|{}renewalCommitmentPaymentsCount[0] + abstract fun (): kotlin/Int // com.revenuecat.purchases.kmp.models/InstallmentsInfo.renewalCommitmentPaymentsCount.|(){}[0] +} abstract interface com.revenuecat.purchases.kmp.models/PurchasingData { // com.revenuecat.purchases.kmp.models/PurchasingData|null[0] abstract val productId // com.revenuecat.purchases.kmp.models/PurchasingData.productId|{}productId[0] abstract fun (): kotlin/String // com.revenuecat.purchases.kmp.models/PurchasingData.productId.|(){}[0] @@ -15,6 +21,8 @@ abstract interface com.revenuecat.purchases.kmp.models/PurchasingData { // com.r abstract interface com.revenuecat.purchases.kmp.models/SubscriptionOption { // com.revenuecat.purchases.kmp.models/SubscriptionOption|null[0] abstract val id // com.revenuecat.purchases.kmp.models/SubscriptionOption.id|{}id[0] abstract fun (): kotlin/String // com.revenuecat.purchases.kmp.models/SubscriptionOption.id.|(){}[0] + abstract val installmentsInfo // com.revenuecat.purchases.kmp.models/SubscriptionOption.installmentsInfo|{}installmentsInfo[0] + abstract fun (): com.revenuecat.purchases.kmp.models/InstallmentsInfo? // com.revenuecat.purchases.kmp.models/SubscriptionOption.installmentsInfo.|(){}[0] abstract val presentedOfferingContext // com.revenuecat.purchases.kmp.models/SubscriptionOption.presentedOfferingContext|{}presentedOfferingContext[0] abstract fun (): cocoapods.PurchasesHybridCommon/RCPresentedOfferingContext? // com.revenuecat.purchases.kmp.models/SubscriptionOption.presentedOfferingContext.|(){}[0] abstract val presentedOfferingIdentifier // com.revenuecat.purchases.kmp.models/SubscriptionOption.presentedOfferingIdentifier|{}presentedOfferingIdentifier[0] @@ -35,7 +43,7 @@ abstract interface com.revenuecat.purchases.kmp/LogHandler { // com.revenuecat.p } abstract interface com.revenuecat.purchases.kmp/PurchasesDelegate { // com.revenuecat.purchases.kmp/PurchasesDelegate|null[0] abstract fun onCustomerInfoUpdated(cocoapods.PurchasesHybridCommon/RCCustomerInfo) // com.revenuecat.purchases.kmp/PurchasesDelegate.onCustomerInfoUpdated|onCustomerInfoUpdated(cocoapods.PurchasesHybridCommon.RCCustomerInfo){}[0] - abstract fun onPurchasePromoProduct(cocoapods.PurchasesHybridCommon/RCStoreProduct, kotlin/Function2, kotlin/Function2, kotlin/Unit>) // com.revenuecat.purchases.kmp/PurchasesDelegate.onPurchasePromoProduct|onPurchasePromoProduct(cocoapods.PurchasesHybridCommon.RCStoreProduct;kotlin.Function2,kotlin.Function2,kotlin.Unit>){}[0] + abstract fun onPurchasePromoProduct(cocoapods.PurchasesHybridCommon/RCStoreProduct, kotlin/Function2, kotlin/Function2, kotlin/Unit>) // com.revenuecat.purchases.kmp/PurchasesDelegate.onPurchasePromoProduct|onPurchasePromoProduct(cocoapods.PurchasesHybridCommon.RCStoreProduct;kotlin.Function2,kotlin.Function2,kotlin.Unit>){}[0] } abstract interface com.revenuecat.purchases.kmp/ReplacementMode { // com.revenuecat.purchases.kmp/ReplacementMode|null[0] abstract val name // com.revenuecat.purchases.kmp/ReplacementMode.name|{}name[0] @@ -66,17 +74,17 @@ final class com.revenuecat.purchases.kmp.ktx/SuccessfulLogin { // com.revenuecat final fun (): cocoapods.PurchasesHybridCommon/RCCustomerInfo // com.revenuecat.purchases.kmp.ktx/SuccessfulLogin.customerInfo.|(){}[0] } final class com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase { // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase|null[0] - constructor (cocoapods.PurchasesHybridCommon/RCStoreTransaction, cocoapods.PurchasesHybridCommon/RCCustomerInfo) // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.|(cocoapods.PurchasesHybridCommon.RCStoreTransaction;cocoapods.PurchasesHybridCommon.RCCustomerInfo){}[0] - final fun component1(): cocoapods.PurchasesHybridCommon/RCStoreTransaction // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.component1|component1(){}[0] + constructor (com.revenuecat.purchases.kmp.models/StoreTransaction, cocoapods.PurchasesHybridCommon/RCCustomerInfo) // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.|(com.revenuecat.purchases.kmp.models.StoreTransaction;cocoapods.PurchasesHybridCommon.RCCustomerInfo){}[0] + final fun component1(): com.revenuecat.purchases.kmp.models/StoreTransaction // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.component1|component1(){}[0] final fun component2(): cocoapods.PurchasesHybridCommon/RCCustomerInfo // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.component2|component2(){}[0] - final fun copy(cocoapods.PurchasesHybridCommon/RCStoreTransaction =..., cocoapods.PurchasesHybridCommon/RCCustomerInfo =...): com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.copy|copy(cocoapods.PurchasesHybridCommon.RCStoreTransaction;cocoapods.PurchasesHybridCommon.RCCustomerInfo){}[0] + final fun copy(com.revenuecat.purchases.kmp.models/StoreTransaction =..., cocoapods.PurchasesHybridCommon/RCCustomerInfo =...): com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.copy|copy(com.revenuecat.purchases.kmp.models.StoreTransaction;cocoapods.PurchasesHybridCommon.RCCustomerInfo){}[0] final fun equals(kotlin/Any?): kotlin/Boolean // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.equals|equals(kotlin.Any?){}[0] final fun hashCode(): kotlin/Int // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.hashCode|hashCode(){}[0] final fun toString(): kotlin/String // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.toString|toString(){}[0] final val customerInfo // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.customerInfo|{}customerInfo[0] final fun (): cocoapods.PurchasesHybridCommon/RCCustomerInfo // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.customerInfo.|(){}[0] final val storeTransaction // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.storeTransaction|{}storeTransaction[0] - final fun (): cocoapods.PurchasesHybridCommon/RCStoreTransaction // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.storeTransaction.|(){}[0] + final fun (): com.revenuecat.purchases.kmp.models/StoreTransaction // com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase.storeTransaction.|(){}[0] } final class com.revenuecat.purchases.kmp.models/Price { // com.revenuecat.purchases.kmp.models/Price|null[0] constructor (kotlin/String, kotlin/Long, kotlin/String) // com.revenuecat.purchases.kmp.models/Price.|(kotlin.String;kotlin.Long;kotlin.String){}[0] @@ -116,6 +124,7 @@ final class com.revenuecat.purchases.kmp.models/PricingPhase { // com.revenuecat final val recurrenceMode // com.revenuecat.purchases.kmp.models/PricingPhase.recurrenceMode|{}recurrenceMode[0] final fun (): com.revenuecat.purchases.kmp.models/RecurrenceMode // com.revenuecat.purchases.kmp.models/PricingPhase.recurrenceMode.|(){}[0] } +final class com.revenuecat.purchases.kmp.models/StoreTransaction // com.revenuecat.purchases.kmp.models/StoreTransaction|null[0] final class com.revenuecat.purchases.kmp.models/SubscriptionOptions { // com.revenuecat.purchases.kmp.models/SubscriptionOptions|null[0] final fun withTag(kotlin/String): kotlin.collections/List // com.revenuecat.purchases.kmp.models/SubscriptionOptions.withTag|withTag(kotlin.String){}[0] final val basePlan // com.revenuecat.purchases.kmp.models/SubscriptionOptions.basePlan|{}basePlan[0] @@ -148,11 +157,12 @@ final class com.revenuecat.purchases.kmp/Purchases { // com.revenuecat.purchases final fun invalidateCustomerInfoCache() // com.revenuecat.purchases.kmp/Purchases.invalidateCustomerInfoCache|invalidateCustomerInfoCache(){}[0] final fun logIn(kotlin/String, kotlin/Function1, kotlin/Function2) // com.revenuecat.purchases.kmp/Purchases.logIn|logIn(kotlin.String;kotlin.Function1;kotlin.Function2){}[0] final fun logOut(kotlin/Function1, kotlin/Function1) // com.revenuecat.purchases.kmp/Purchases.logOut|logOut(kotlin.Function1;kotlin.Function1){}[0] - final fun purchase(cocoapods.PurchasesHybridCommon/RCPackage, cocoapods.PurchasesHybridCommon/RCPromotionalOffer, kotlin/Function2, kotlin/Function2) // com.revenuecat.purchases.kmp/Purchases.purchase|purchase(cocoapods.PurchasesHybridCommon.RCPackage;cocoapods.PurchasesHybridCommon.RCPromotionalOffer;kotlin.Function2;kotlin.Function2){}[0] - final fun purchase(cocoapods.PurchasesHybridCommon/RCPackage, kotlin/Function2, kotlin/Function2, kotlin/Boolean? =..., kotlin/String? =..., com.revenuecat.purchases.kmp.models/GoogleReplacementMode =...) // com.revenuecat.purchases.kmp/Purchases.purchase|purchase(cocoapods.PurchasesHybridCommon.RCPackage;kotlin.Function2;kotlin.Function2;kotlin.Boolean?;kotlin.String?;com.revenuecat.purchases.kmp.models.GoogleReplacementMode){}[0] - final fun purchase(cocoapods.PurchasesHybridCommon/RCStoreProduct, cocoapods.PurchasesHybridCommon/RCPromotionalOffer, kotlin/Function2, kotlin/Function2) // com.revenuecat.purchases.kmp/Purchases.purchase|purchase(cocoapods.PurchasesHybridCommon.RCStoreProduct;cocoapods.PurchasesHybridCommon.RCPromotionalOffer;kotlin.Function2;kotlin.Function2){}[0] - final fun purchase(cocoapods.PurchasesHybridCommon/RCStoreProduct, kotlin/Function2, kotlin/Function2, kotlin/Boolean? =..., kotlin/String? =..., com.revenuecat.purchases.kmp.models/GoogleReplacementMode =...) // com.revenuecat.purchases.kmp/Purchases.purchase|purchase(cocoapods.PurchasesHybridCommon.RCStoreProduct;kotlin.Function2;kotlin.Function2;kotlin.Boolean?;kotlin.String?;com.revenuecat.purchases.kmp.models.GoogleReplacementMode){}[0] - final fun purchase(com.revenuecat.purchases.kmp.models/SubscriptionOption, kotlin/Function2, kotlin/Function2, kotlin/Boolean? =..., kotlin/String? =..., com.revenuecat.purchases.kmp.models/GoogleReplacementMode =...) // com.revenuecat.purchases.kmp/Purchases.purchase|purchase(com.revenuecat.purchases.kmp.models.SubscriptionOption;kotlin.Function2;kotlin.Function2;kotlin.Boolean?;kotlin.String?;com.revenuecat.purchases.kmp.models.GoogleReplacementMode){}[0] + final fun purchase(cocoapods.PurchasesHybridCommon/RCPackage, cocoapods.PurchasesHybridCommon/RCPromotionalOffer, kotlin/Function2, kotlin/Function2) // com.revenuecat.purchases.kmp/Purchases.purchase|purchase(cocoapods.PurchasesHybridCommon.RCPackage;cocoapods.PurchasesHybridCommon.RCPromotionalOffer;kotlin.Function2;kotlin.Function2){}[0] + final fun purchase(cocoapods.PurchasesHybridCommon/RCPackage, kotlin/Function2, kotlin/Function2, kotlin/Boolean? =..., kotlin/String? =..., com.revenuecat.purchases.kmp.models/GoogleReplacementMode =...) // com.revenuecat.purchases.kmp/Purchases.purchase|purchase(cocoapods.PurchasesHybridCommon.RCPackage;kotlin.Function2;kotlin.Function2;kotlin.Boolean?;kotlin.String?;com.revenuecat.purchases.kmp.models.GoogleReplacementMode){}[0] + final fun purchase(cocoapods.PurchasesHybridCommon/RCStoreProduct, cocoapods.PurchasesHybridCommon/RCPromotionalOffer, kotlin/Function2, kotlin/Function2) // com.revenuecat.purchases.kmp/Purchases.purchase|purchase(cocoapods.PurchasesHybridCommon.RCStoreProduct;cocoapods.PurchasesHybridCommon.RCPromotionalOffer;kotlin.Function2;kotlin.Function2){}[0] + final fun purchase(cocoapods.PurchasesHybridCommon/RCStoreProduct, kotlin/Function2, kotlin/Function2, kotlin/Boolean? =..., kotlin/String? =..., com.revenuecat.purchases.kmp.models/GoogleReplacementMode =...) // com.revenuecat.purchases.kmp/Purchases.purchase|purchase(cocoapods.PurchasesHybridCommon.RCStoreProduct;kotlin.Function2;kotlin.Function2;kotlin.Boolean?;kotlin.String?;com.revenuecat.purchases.kmp.models.GoogleReplacementMode){}[0] + final fun purchase(com.revenuecat.purchases.kmp.models/SubscriptionOption, kotlin/Function2, kotlin/Function2, kotlin/Boolean? =..., kotlin/String? =..., com.revenuecat.purchases.kmp.models/GoogleReplacementMode =...) // com.revenuecat.purchases.kmp/Purchases.purchase|purchase(com.revenuecat.purchases.kmp.models.SubscriptionOption;kotlin.Function2;kotlin.Function2;kotlin.Boolean?;kotlin.String?;com.revenuecat.purchases.kmp.models.GoogleReplacementMode){}[0] + final fun recordPurchase(kotlin/String, kotlin/Function1, kotlin/Function1) // com.revenuecat.purchases.kmp/Purchases.recordPurchase|recordPurchase(kotlin.String;kotlin.Function1;kotlin.Function1){}[0] final fun restorePurchases(kotlin/Function1, kotlin/Function1) // com.revenuecat.purchases.kmp/Purchases.restorePurchases|restorePurchases(kotlin.Function1;kotlin.Function1){}[0] final fun setAd(kotlin/String?) // com.revenuecat.purchases.kmp/Purchases.setAd|setAd(kotlin.String?){}[0] final fun setAdGroup(kotlin/String?) // com.revenuecat.purchases.kmp/Purchases.setAdGroup|setAdGroup(kotlin.String?){}[0] @@ -211,9 +221,6 @@ final class com.revenuecat.purchases.kmp/Purchases { // com.revenuecat.purchases final var delegate // com.revenuecat.purchases.kmp/Purchases.delegate|{}delegate[0] final fun (): com.revenuecat.purchases.kmp/PurchasesDelegate? // com.revenuecat.purchases.kmp/Purchases.delegate.|(){}[0] final fun (com.revenuecat.purchases.kmp/PurchasesDelegate?) // com.revenuecat.purchases.kmp/Purchases.delegate.|(com.revenuecat.purchases.kmp.PurchasesDelegate?){}[0] - final var purchasesAreCompletedBy // com.revenuecat.purchases.kmp/Purchases.purchasesAreCompletedBy|{}purchasesAreCompletedBy[0] - final fun (): com.revenuecat.purchases.kmp/PurchasesAreCompletedBy // com.revenuecat.purchases.kmp/Purchases.purchasesAreCompletedBy.|(){}[0] - final fun (com.revenuecat.purchases.kmp/PurchasesAreCompletedBy) // com.revenuecat.purchases.kmp/Purchases.purchasesAreCompletedBy.|(com.revenuecat.purchases.kmp.PurchasesAreCompletedBy){}[0] } final class com.revenuecat.purchases.kmp/PurchasesConfiguration { // com.revenuecat.purchases.kmp/PurchasesConfiguration|null[0] final class Builder { // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder|null[0] @@ -223,9 +230,11 @@ final class com.revenuecat.purchases.kmp/PurchasesConfiguration { // com.revenue final fun build(): com.revenuecat.purchases.kmp/PurchasesConfiguration // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.build|build(){}[0] final fun dangerousSettings(com.revenuecat.purchases.kmp/DangerousSettings): com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.dangerousSettings|dangerousSettings(com.revenuecat.purchases.kmp.DangerousSettings){}[0] final fun diagnosticsEnabled(kotlin/Boolean): com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.diagnosticsEnabled|diagnosticsEnabled(kotlin.Boolean){}[0] + final fun pendingTransactionsForPrepaidPlansEnabled(kotlin/Boolean): com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.pendingTransactionsForPrepaidPlansEnabled|pendingTransactionsForPrepaidPlansEnabled(kotlin.Boolean){}[0] final fun purchasesAreCompletedBy(com.revenuecat.purchases.kmp/PurchasesAreCompletedBy): com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.purchasesAreCompletedBy|purchasesAreCompletedBy(com.revenuecat.purchases.kmp.PurchasesAreCompletedBy){}[0] final fun showInAppMessagesAutomatically(kotlin/Boolean): com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.showInAppMessagesAutomatically|showInAppMessagesAutomatically(kotlin.Boolean){}[0] final fun store(com.revenuecat.purchases.kmp/Store?): com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.store|store(com.revenuecat.purchases.kmp.Store?){}[0] + final fun storeKitVersion(com.revenuecat.purchases.kmp/StoreKitVersion): com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.storeKitVersion|storeKitVersion(com.revenuecat.purchases.kmp.StoreKitVersion){}[0] final fun userDefaultsSuiteName(kotlin/String?): com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.userDefaultsSuiteName|userDefaultsSuiteName(kotlin.String?){}[0] final fun verificationMode(com.revenuecat.purchases.kmp/EntitlementVerificationMode): com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.verificationMode|verificationMode(com.revenuecat.purchases.kmp.EntitlementVerificationMode){}[0] final var apiKey // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.apiKey|{}apiKey[0] @@ -240,6 +249,9 @@ final class com.revenuecat.purchases.kmp/PurchasesConfiguration { // com.revenue final var diagnosticsEnabled // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.diagnosticsEnabled|{}diagnosticsEnabled[0] final fun (): kotlin/Boolean // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.diagnosticsEnabled.|(){}[0] final fun (kotlin/Boolean) // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.diagnosticsEnabled.|(kotlin.Boolean){}[0] + final var pendingTransactionsForPrepaidPlansEnabled // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.pendingTransactionsForPrepaidPlansEnabled|{}pendingTransactionsForPrepaidPlansEnabled[0] + final fun (): kotlin/Boolean? // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.pendingTransactionsForPrepaidPlansEnabled.|(){}[0] + final fun (kotlin/Boolean?) // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.pendingTransactionsForPrepaidPlansEnabled.|(kotlin.Boolean?){}[0] final var purchasesAreCompletedBy // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.purchasesAreCompletedBy|{}purchasesAreCompletedBy[0] final fun (): com.revenuecat.purchases.kmp/PurchasesAreCompletedBy // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.purchasesAreCompletedBy.|(){}[0] final fun (com.revenuecat.purchases.kmp/PurchasesAreCompletedBy) // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.purchasesAreCompletedBy.|(com.revenuecat.purchases.kmp.PurchasesAreCompletedBy){}[0] @@ -249,6 +261,9 @@ final class com.revenuecat.purchases.kmp/PurchasesConfiguration { // com.revenue final var store // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.store|{}store[0] final fun (): com.revenuecat.purchases.kmp/Store? // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.store.|(){}[0] final fun (com.revenuecat.purchases.kmp/Store?) // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.store.|(com.revenuecat.purchases.kmp.Store?){}[0] + final var storeKitVersion // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.storeKitVersion|{}storeKitVersion[0] + final fun (): com.revenuecat.purchases.kmp/StoreKitVersion // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.storeKitVersion.|(){}[0] + final fun (com.revenuecat.purchases.kmp/StoreKitVersion) // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.storeKitVersion.|(com.revenuecat.purchases.kmp.StoreKitVersion){}[0] final var userDefaultsSuiteName // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.userDefaultsSuiteName|{}userDefaultsSuiteName[0] final fun (): kotlin/String? // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.userDefaultsSuiteName.|(){}[0] final fun (kotlin/String?) // com.revenuecat.purchases.kmp/PurchasesConfiguration.Builder.userDefaultsSuiteName.|(kotlin.String?){}[0] @@ -265,12 +280,16 @@ final class com.revenuecat.purchases.kmp/PurchasesConfiguration { // com.revenue final fun (): com.revenuecat.purchases.kmp/DangerousSettings // com.revenuecat.purchases.kmp/PurchasesConfiguration.dangerousSettings.|(){}[0] final val diagnosticsEnabled // com.revenuecat.purchases.kmp/PurchasesConfiguration.diagnosticsEnabled|{}diagnosticsEnabled[0] final fun (): kotlin/Boolean // com.revenuecat.purchases.kmp/PurchasesConfiguration.diagnosticsEnabled.|(){}[0] + final val pendingTransactionsForPrepaidPlansEnabled // com.revenuecat.purchases.kmp/PurchasesConfiguration.pendingTransactionsForPrepaidPlansEnabled|{}pendingTransactionsForPrepaidPlansEnabled[0] + final fun (): kotlin/Boolean? // com.revenuecat.purchases.kmp/PurchasesConfiguration.pendingTransactionsForPrepaidPlansEnabled.|(){}[0] final val purchasesAreCompletedBy // com.revenuecat.purchases.kmp/PurchasesConfiguration.purchasesAreCompletedBy|{}purchasesAreCompletedBy[0] final fun (): com.revenuecat.purchases.kmp/PurchasesAreCompletedBy // com.revenuecat.purchases.kmp/PurchasesConfiguration.purchasesAreCompletedBy.|(){}[0] final val showInAppMessagesAutomatically // com.revenuecat.purchases.kmp/PurchasesConfiguration.showInAppMessagesAutomatically|{}showInAppMessagesAutomatically[0] final fun (): kotlin/Boolean // com.revenuecat.purchases.kmp/PurchasesConfiguration.showInAppMessagesAutomatically.|(){}[0] final val store // com.revenuecat.purchases.kmp/PurchasesConfiguration.store|{}store[0] final fun (): com.revenuecat.purchases.kmp/Store? // com.revenuecat.purchases.kmp/PurchasesConfiguration.store.|(){}[0] + final val storeKitVersion // com.revenuecat.purchases.kmp/PurchasesConfiguration.storeKitVersion|{}storeKitVersion[0] + final fun (): com.revenuecat.purchases.kmp/StoreKitVersion // com.revenuecat.purchases.kmp/PurchasesConfiguration.storeKitVersion.|(){}[0] final val userDefaultsSuiteName // com.revenuecat.purchases.kmp/PurchasesConfiguration.userDefaultsSuiteName|{}userDefaultsSuiteName[0] final fun (): kotlin/String? // com.revenuecat.purchases.kmp/PurchasesConfiguration.userDefaultsSuiteName.|(){}[0] final val verificationMode // com.revenuecat.purchases.kmp/PurchasesConfiguration.verificationMode|{}verificationMode[0] @@ -449,14 +468,6 @@ final enum class com.revenuecat.purchases.kmp/ProductType : kotlin/Enum(): kotlin.enums/EnumEntries // com.revenuecat.purchases.kmp/ProductType.entries.|#static(){}[0] } -final enum class com.revenuecat.purchases.kmp/PurchasesAreCompletedBy : kotlin/Enum { // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy|null[0] - enum entry MY_APP // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.MY_APP|null[0] - enum entry REVENUECAT // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.REVENUECAT|null[0] - final fun valueOf(kotlin/String): com.revenuecat.purchases.kmp/PurchasesAreCompletedBy // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.valueOf|valueOf#static(kotlin.String){}[0] - final fun values(): kotlin/Array // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.values|values#static(){}[0] - final val entries // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.entries|#static{}entries[0] - final fun (): kotlin.enums/EnumEntries // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.entries.|#static(){}[0] -} final enum class com.revenuecat.purchases.kmp/PurchasesErrorCode : kotlin/Enum { // com.revenuecat.purchases.kmp/PurchasesErrorCode|null[0] enum entry ApiEndpointBlocked // com.revenuecat.purchases.kmp/PurchasesErrorCode.ApiEndpointBlocked|null[0] enum entry BeginRefundRequestError // com.revenuecat.purchases.kmp/PurchasesErrorCode.BeginRefundRequestError|null[0] @@ -519,6 +530,15 @@ final enum class com.revenuecat.purchases.kmp/Store : kotlin/Enum(): kotlin.enums/EnumEntries // com.revenuecat.purchases.kmp/Store.entries.|#static(){}[0] } +final enum class com.revenuecat.purchases.kmp/StoreKitVersion : kotlin/Enum { // com.revenuecat.purchases.kmp/StoreKitVersion|null[0] + enum entry DEFAULT // com.revenuecat.purchases.kmp/StoreKitVersion.DEFAULT|null[0] + enum entry STOREKIT_1 // com.revenuecat.purchases.kmp/StoreKitVersion.STOREKIT_1|null[0] + enum entry STOREKIT_2 // com.revenuecat.purchases.kmp/StoreKitVersion.STOREKIT_2|null[0] + final fun valueOf(kotlin/String): com.revenuecat.purchases.kmp/StoreKitVersion // com.revenuecat.purchases.kmp/StoreKitVersion.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // com.revenuecat.purchases.kmp/StoreKitVersion.values|values#static(){}[0] + final val entries // com.revenuecat.purchases.kmp/StoreKitVersion.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // com.revenuecat.purchases.kmp/StoreKitVersion.entries.|#static(){}[0] +} final enum class com.revenuecat.purchases.kmp/VerificationResult : kotlin/Enum { // com.revenuecat.purchases.kmp/VerificationResult|null[0] enum entry FAILED // com.revenuecat.purchases.kmp/VerificationResult.FAILED|null[0] enum entry NOT_REQUESTED // com.revenuecat.purchases.kmp/VerificationResult.NOT_REQUESTED|null[0] @@ -601,12 +621,12 @@ final val com.revenuecat.purchases.kmp.models/price // com.revenuecat.purchases. final fun (cocoapods.PurchasesHybridCommon/RCStoreProduct).(): com.revenuecat.purchases.kmp.models/Price // com.revenuecat.purchases.kmp.models/price.|@cocoapods.PurchasesHybridCommon.RCStoreProduct(){}[0] final val com.revenuecat.purchases.kmp.models/productIdentifier // com.revenuecat.purchases.kmp.models/productIdentifier|@com.revenuecat.purchases.kmp.models.Transaction{}productIdentifier[0] final fun (com.revenuecat.purchases.kmp.models/Transaction).(): kotlin/String // com.revenuecat.purchases.kmp.models/productIdentifier.|@com.revenuecat.purchases.kmp.models.Transaction(){}[0] -final val com.revenuecat.purchases.kmp.models/productIds // com.revenuecat.purchases.kmp.models/productIds|@cocoapods.PurchasesHybridCommon.RCStoreTransaction{}productIds[0] - final fun (cocoapods.PurchasesHybridCommon/RCStoreTransaction).(): kotlin.collections/List // com.revenuecat.purchases.kmp.models/productIds.|@cocoapods.PurchasesHybridCommon.RCStoreTransaction(){}[0] +final val com.revenuecat.purchases.kmp.models/productIds // com.revenuecat.purchases.kmp.models/productIds|@com.revenuecat.purchases.kmp.models.StoreTransaction{}productIds[0] + final fun (com.revenuecat.purchases.kmp.models/StoreTransaction).(): kotlin.collections/List // com.revenuecat.purchases.kmp.models/productIds.|@com.revenuecat.purchases.kmp.models.StoreTransaction(){}[0] final val com.revenuecat.purchases.kmp.models/purchaseDateMillis // com.revenuecat.purchases.kmp.models/purchaseDateMillis|@com.revenuecat.purchases.kmp.models.Transaction{}purchaseDateMillis[0] final fun (com.revenuecat.purchases.kmp.models/Transaction).(): kotlin/Long // com.revenuecat.purchases.kmp.models/purchaseDateMillis.|@com.revenuecat.purchases.kmp.models.Transaction(){}[0] -final val com.revenuecat.purchases.kmp.models/purchaseTime // com.revenuecat.purchases.kmp.models/purchaseTime|@cocoapods.PurchasesHybridCommon.RCStoreTransaction{}purchaseTime[0] - final fun (cocoapods.PurchasesHybridCommon/RCStoreTransaction).(): kotlin/Long // com.revenuecat.purchases.kmp.models/purchaseTime.|@cocoapods.PurchasesHybridCommon.RCStoreTransaction(){}[0] +final val com.revenuecat.purchases.kmp.models/purchaseTime // com.revenuecat.purchases.kmp.models/purchaseTime|@com.revenuecat.purchases.kmp.models.StoreTransaction{}purchaseTime[0] + final fun (com.revenuecat.purchases.kmp.models/StoreTransaction).(): kotlin/Long // com.revenuecat.purchases.kmp.models/purchaseTime.|@com.revenuecat.purchases.kmp.models.StoreTransaction(){}[0] final val com.revenuecat.purchases.kmp.models/purchasingData // com.revenuecat.purchases.kmp.models/purchasingData|@cocoapods.PurchasesHybridCommon.RCStoreProduct{}purchasingData[0] final fun (cocoapods.PurchasesHybridCommon/RCStoreProduct).(): com.revenuecat.purchases.kmp.models/PurchasingData // com.revenuecat.purchases.kmp.models/purchasingData.|@cocoapods.PurchasesHybridCommon.RCStoreProduct(){}[0] final val com.revenuecat.purchases.kmp.models/subscriptionOptions // com.revenuecat.purchases.kmp.models/subscriptionOptions|@cocoapods.PurchasesHybridCommon.RCStoreProduct{}subscriptionOptions[0] @@ -615,8 +635,8 @@ final val com.revenuecat.purchases.kmp.models/subscriptionPeriod // com.revenuec final fun (cocoapods.PurchasesHybridCommon/RCStoreProductDiscount).(): cocoapods.PurchasesHybridCommon/RCSubscriptionPeriod // com.revenuecat.purchases.kmp.models/subscriptionPeriod.|@cocoapods.PurchasesHybridCommon.RCStoreProductDiscount(){}[0] final val com.revenuecat.purchases.kmp.models/title // com.revenuecat.purchases.kmp.models/title|@cocoapods.PurchasesHybridCommon.RCStoreProduct{}title[0] final fun (cocoapods.PurchasesHybridCommon/RCStoreProduct).(): kotlin/String // com.revenuecat.purchases.kmp.models/title.|@cocoapods.PurchasesHybridCommon.RCStoreProduct(){}[0] -final val com.revenuecat.purchases.kmp.models/transactionId // com.revenuecat.purchases.kmp.models/transactionId|@cocoapods.PurchasesHybridCommon.RCStoreTransaction{}transactionId[0] - final fun (cocoapods.PurchasesHybridCommon/RCStoreTransaction).(): kotlin/String? // com.revenuecat.purchases.kmp.models/transactionId.|@cocoapods.PurchasesHybridCommon.RCStoreTransaction(){}[0] +final val com.revenuecat.purchases.kmp.models/transactionId // com.revenuecat.purchases.kmp.models/transactionId|@com.revenuecat.purchases.kmp.models.StoreTransaction{}transactionId[0] + final fun (com.revenuecat.purchases.kmp.models/StoreTransaction).(): kotlin/String? // com.revenuecat.purchases.kmp.models/transactionId.|@com.revenuecat.purchases.kmp.models.StoreTransaction(){}[0] final val com.revenuecat.purchases.kmp.models/transactionIdentifier // com.revenuecat.purchases.kmp.models/transactionIdentifier|@com.revenuecat.purchases.kmp.models.Transaction{}transactionIdentifier[0] final fun (com.revenuecat.purchases.kmp.models/Transaction).(): kotlin/String // com.revenuecat.purchases.kmp.models/transactionIdentifier.|@com.revenuecat.purchases.kmp.models.Transaction(){}[0] final val com.revenuecat.purchases.kmp.models/type // com.revenuecat.purchases.kmp.models/type|@cocoapods.PurchasesHybridCommon.RCStoreProductDiscount{}type[0] @@ -752,3 +772,20 @@ open class com.revenuecat.purchases.kmp/PurchasesException : kotlin/Exception { open val message // com.revenuecat.purchases.kmp/PurchasesException.message|{}message[0] open fun (): kotlin/String // com.revenuecat.purchases.kmp/PurchasesException.message.|(){}[0] } +sealed interface com.revenuecat.purchases.kmp/PurchasesAreCompletedBy { // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy|null[0] + final class MyApp : com.revenuecat.purchases.kmp/PurchasesAreCompletedBy { // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.MyApp|null[0] + constructor (com.revenuecat.purchases.kmp/StoreKitVersion) // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.MyApp.|(com.revenuecat.purchases.kmp.StoreKitVersion){}[0] + final fun component1(): com.revenuecat.purchases.kmp/StoreKitVersion // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.MyApp.component1|component1(){}[0] + final fun copy(com.revenuecat.purchases.kmp/StoreKitVersion =...): com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.MyApp // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.MyApp.copy|copy(com.revenuecat.purchases.kmp.StoreKitVersion){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.MyApp.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.MyApp.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.MyApp.toString|toString(){}[0] + final val storeKitVersion // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.MyApp.storeKitVersion|{}storeKitVersion[0] + final fun (): com.revenuecat.purchases.kmp/StoreKitVersion // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.MyApp.storeKitVersion.|(){}[0] + } + final object RevenueCat : com.revenuecat.purchases.kmp/PurchasesAreCompletedBy { // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.RevenueCat|null[0] + final fun equals(kotlin/Any?): kotlin/Boolean // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.RevenueCat.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.RevenueCat.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // com.revenuecat.purchases.kmp/PurchasesAreCompletedBy.RevenueCat.toString|toString(){}[0] + } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 92127469..42b0f8de 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -24,7 +24,7 @@ kotlin { cocoapods { version = "1.0" - ios.deploymentTarget = "11.0" + ios.deploymentTarget = "13.0" framework { baseName = "Purchases" diff --git a/core/core.podspec b/core/core.podspec index b8677091..0a5080d3 100644 --- a/core/core.podspec +++ b/core/core.podspec @@ -8,8 +8,8 @@ Pod::Spec.new do |spec| spec.summary = '' spec.vendored_frameworks = 'build/cocoapods/framework/Purchases.framework' spec.libraries = 'c++' - spec.ios.deployment_target = '11.0' - spec.dependency 'PurchasesHybridCommon', '11.1.1' + spec.ios.deployment_target = '13.0' + spec.dependency 'PurchasesHybridCommon', '13.0.1' if !Dir.exist?('build/cocoapods/framework/Purchases.framework') || Dir.empty?('build/cocoapods/framework/Purchases.framework') raise " diff --git a/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/Purchases.android.kt b/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/Purchases.android.kt index 2eb09c77..ed1c7c80 100644 --- a/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/Purchases.android.kt +++ b/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/Purchases.android.kt @@ -70,7 +70,7 @@ public actual class Purchases private constructor(private val androidPurchases: context = AndroidProvider.requireApplication(), apiKey = apiKey, appUserID = appUserId, - purchasesAreCompletedBy = purchasesAreCompletedBy, + purchasesAreCompletedBy = purchasesAreCompletedBy.toHybridString(), platformInfo = PlatformInfo( flavor = BuildKonfig.platformFlavor, version = frameworkVersion, @@ -79,6 +79,7 @@ public actual class Purchases private constructor(private val androidPurchases: dangerousSettings = dangerousSettings.toAndroidDangerousSettings(), shouldShowInAppMessagesAutomatically = showInAppMessagesAutomatically, verificationMode = verificationMode.name, + pendingTransactionsForPrepaidPlansEnabled = pendingTransactionsForPrepaidPlansEnabled ) } @@ -98,8 +99,6 @@ public actual class Purchases private constructor(private val androidPurchases: AndroidDangerousSettings(autoSyncPurchases) } - public actual var purchasesAreCompletedBy: PurchasesAreCompletedBy by androidPurchases::purchasesAreCompletedBy - public actual val appUserID: String by androidPurchases::appUserID public actual var delegate: PurchasesDelegate? @@ -258,6 +257,19 @@ public actual class Purchases private constructor(private val androidPurchases: onSuccess = { onSuccess(it) }, ) + public actual fun recordPurchase( + productID: String, + onError: (error: PurchasesError) -> Unit, + onSuccess: (storeTransaction: StoreTransaction) -> Unit, + ) { + onError( + PurchasesError( + PurchasesErrorCode.UnsupportedError, + underlyingErrorMessage = "recordPurchase() is not supported on Android." + ) + ) + } + public actual fun logIn( newAppUserID: String, onError: (error: PurchasesError) -> Unit, diff --git a/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedBy.android.kt b/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedBy.android.kt deleted file mode 100644 index 3f800e20..00000000 --- a/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedBy.android.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.revenuecat.purchases.kmp - -import com.revenuecat.purchases.PurchasesAreCompletedBy as AndroidPurchasesAreCompletedBy - -public actual typealias PurchasesAreCompletedBy = AndroidPurchasesAreCompletedBy diff --git a/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/models/InstallmentsInfo.android.kt b/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/models/InstallmentsInfo.android.kt new file mode 100644 index 00000000..168abe72 --- /dev/null +++ b/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/models/InstallmentsInfo.android.kt @@ -0,0 +1,5 @@ +package com.revenuecat.purchases.kmp.models + +import com.revenuecat.purchases.models.InstallmentsInfo as RcInstallmentsInfo + +public actual typealias InstallmentsInfo = RcInstallmentsInfo \ No newline at end of file diff --git a/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/Purchases.kt b/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/Purchases.kt index 6c6b36cb..c27f960f 100644 --- a/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/Purchases.kt +++ b/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/Purchases.kt @@ -1,7 +1,5 @@ package com.revenuecat.purchases.kmp -import com.revenuecat.purchases.kmp.PurchasesAreCompletedBy.MY_APP -import com.revenuecat.purchases.kmp.PurchasesAreCompletedBy.REVENUECAT import com.revenuecat.purchases.kmp.models.BillingFeature import com.revenuecat.purchases.kmp.models.GoogleReplacementMode import com.revenuecat.purchases.kmp.models.PromotionalOffer @@ -93,12 +91,6 @@ public expect class Purchases { ) } - /** - * Default to [REVENUECAT], set this to [MY_APP] if you are consuming and acknowledging - * transactions outside of the Purchases SDK. - */ - public var purchasesAreCompletedBy: PurchasesAreCompletedBy - /** * The passed in or generated app user ID. */ @@ -141,7 +133,7 @@ public expect class Purchases { /** * This method will send an Amazon purchase to the RevenueCat backend. This function should * only be called if you have set [purchasesAreCompletedBy] to - * [MY_APP][PurchasesAreCompletedBy.MY_APP] or when performing a client side migration of your + * [MyApp][PurchasesAreCompletedBy.MyApp] or when performing a client side migration of your * current users to RevenueCat. * * The receipt IDs are cached if successfully posted so they are not posted more than once. @@ -380,6 +372,33 @@ public expect class Purchases { onSuccess: (customerInfo: CustomerInfo) -> Unit, ) + /** + * iOS only. Always returns an error on iOS < 15. + * + * Use this method only if you already have your own IAP implementation using StoreKit 2 and + * want to use RevenueCat's backend. If you are using StoreKit 1 for your implementation, you + * do not need this method. + * + * You only need to use this method with *new* purchases. + * Subscription updates are observed automatically. + * + * Important: This should only be used if you have set [purchasesAreCompletedBy] to + * [PurchasesAreCompletedBy.MyApp] during SDK configuration. + * + * **Warning** You need to finish the transaction yourself after calling this method. + * + * @param productID: The Product ID that was just purchased + * @param onError Will be called if an error occurs, providing a [PurchasesError] describing + * what went wrong. + * @param onSuccess Will be called if the function completes successfully, including details + * on the [StoreTransaction] that was recorded. + */ + public fun recordPurchase( + productID: String, + onError: (error: PurchasesError) -> Unit, + onSuccess: (storeTransaction: StoreTransaction) -> Unit, + ) + /** * This function will change the current [appUserID]. Typically this would be used after a log * out to identify a new user without calling `configure()`. diff --git a/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedBy.kt b/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedBy.kt index 80c82a87..ee6458b8 100644 --- a/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedBy.kt +++ b/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedBy.kt @@ -3,21 +3,41 @@ package com.revenuecat.purchases.kmp /** * Modes for completing the purchase process. */ -public expect enum class PurchasesAreCompletedBy { +public sealed interface PurchasesAreCompletedBy { + /** - * RevenueCat will automatically acknowledge verified purchases. No action is required by you. + * RevenueCat will automatically acknowledge verified purchases. + * No action is required by you. */ - REVENUECAT, + public data object RevenueCat : PurchasesAreCompletedBy /** - * RevenueCat will **not** automatically acknowledge any purchases. You will have to do so manually. + * RevenueCat will **not** automatically acknowledge any purchases. + * You will have to do so manually. + * Note that failing to acknowledge a purchase within 3 days will lead + * to Google Play automatically issuing a refund to the user. * - * **Note:** failing to acknowledge a purchase within 3 days will lead to Google Play automatically issuing a - * refund to the user. + * For more info, see [revenuecat.com](https://www.revenuecat.com/docs/migrating-to-revenuecat/sdk-or-not/finishing-transactions) + * and [developer.android.com](https://developer.android.com/google/play/billing/integrate#process). * - * For more info, see - * [revenuecat.com](https://www.revenuecat.com/docs/migrating-to-revenuecat/sdk-or-not/finishing-transactions) and - * [developer.android.com](https://developer.android.com/google/play/billing/integrate#process). + * @property storeKitVersion The version of StoreKit to use for purchases on Apple devices. + * If your app is Android-only, you may provide any value since it is ignored in the native + * Android SDK. */ - MY_APP, + public data class MyApp(val storeKitVersion: StoreKitVersion) : PurchasesAreCompletedBy } + +/** + * Converts an instance of `PurchasesAreCompletedBy` to its corresponding string representation + * suitable for usage with the PurchasesHybridCommon library. + * + * @return A `String` that represents the type of `PurchasesAreCompletedBy`: + * - Returns `"REVENUECAT"` if the instance is of type `PurchasesAreCompletedBy.RevenueCat`. + * - Returns `"MY_APP"` if the instance is of type `PurchasesAreCompletedBy.MyApp`. + * + */ +internal fun PurchasesAreCompletedBy.toHybridString(): String = + when(this) { + is PurchasesAreCompletedBy.RevenueCat -> "REVENUECAT" + is PurchasesAreCompletedBy.MyApp -> "MY_APP" + } diff --git a/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/PurchasesConfiguration.kt b/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/PurchasesConfiguration.kt index ded23fff..1a799010 100644 --- a/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/PurchasesConfiguration.kt +++ b/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/PurchasesConfiguration.kt @@ -1,6 +1,5 @@ package com.revenuecat.purchases.kmp -import com.revenuecat.purchases.kmp.PurchasesAreCompletedBy.REVENUECAT import com.revenuecat.purchases.kmp.PurchasesConfiguration.Builder import kotlin.jvm.JvmSynthetic @@ -13,11 +12,13 @@ public class PurchasesConfiguration private constructor( public val appUserId: String?, public val purchasesAreCompletedBy: PurchasesAreCompletedBy, public val userDefaultsSuiteName: String?, + public val storeKitVersion: StoreKitVersion, public val showInAppMessagesAutomatically: Boolean, public val store: Store?, public val diagnosticsEnabled: Boolean, public val dangerousSettings: DangerousSettings, public val verificationMode: EntitlementVerificationMode, + public val pendingTransactionsForPrepaidPlansEnabled: Boolean? ) { override fun toString(): String = "PurchasesConfiguration(" + @@ -25,13 +26,40 @@ public class PurchasesConfiguration private constructor( "appUserId=$appUserId, " + "purchasesAreCompletedBy=$purchasesAreCompletedBy, " + "userDefaultsSuiteName=$userDefaultsSuiteName, " + + "storeKitVersion=$storeKitVersion, " + "showInAppMessagesAutomatically=$showInAppMessagesAutomatically, " + "store=$store, " + "diagnosticsEnabled=$diagnosticsEnabled, " + "dangerousSettings=$dangerousSettings, " + - "verificationMode=$verificationMode" + + "verificationMode=$verificationMode," + + "pendingTransactionsForPrepaidPlansEnabled=$pendingTransactionsForPrepaidPlansEnabled" + ")" + internal fun storeKitVersionToUse(): StoreKitVersion { + var storeKitVersionToUse = this.storeKitVersion + + if (this.purchasesAreCompletedBy is PurchasesAreCompletedBy.MyApp) { + storeKitVersionToUse = this.purchasesAreCompletedBy.storeKitVersion + + if (this.storeKitVersion != StoreKitVersion.DEFAULT && + storeKitVersionToUse != this.storeKitVersion) { + Purchases.logHandler.w("[Purchases]", "The storeKitVersion in purchasesAreCompletedBy " + + "does not match the provided storeKitVersion parameter. We will use the " + + "value found in purchasesAreCompletedBy.") + } + + if(this.purchasesAreCompletedBy.storeKitVersion == StoreKitVersion.DEFAULT) { + Purchases.logHandler.w("[Purchases]", + "Warning: You should provide the specific StoreKit version you're using in " + + "your implementation when configuring PurchasesAreCompletedBy.MyApp, " + + "and not rely on the DEFAULT." + ) + } + } + + return storeKitVersionToUse + } + /** * Use this builder to create an instance of [PurchasesConfiguration]. */ @@ -43,11 +71,14 @@ public class PurchasesConfiguration private constructor( public var appUserId: String? = null @set:JvmSynthetic - public var purchasesAreCompletedBy: PurchasesAreCompletedBy = REVENUECAT + public var purchasesAreCompletedBy: PurchasesAreCompletedBy = PurchasesAreCompletedBy.RevenueCat @set:JvmSynthetic public var userDefaultsSuiteName: String? = null + @set:JvmSynthetic + public var storeKitVersion: StoreKitVersion = StoreKitVersion.DEFAULT + @set:JvmSynthetic public var showInAppMessagesAutomatically: Boolean = true @@ -64,6 +95,9 @@ public class PurchasesConfiguration private constructor( public var verificationMode: EntitlementVerificationMode = EntitlementVerificationMode.DISABLED + @set:JvmSynthetic + public var pendingTransactionsForPrepaidPlansEnabled: Boolean? = null + /** * Your RevenueCat API Key. */ @@ -103,6 +137,21 @@ public class PurchasesConfiguration private constructor( public fun userDefaultsSuiteName(userDefaultsSuiteName: String?): Builder = apply { this.userDefaultsSuiteName = userDefaultsSuiteName } + /** + * iOS-only, will be ignored for Android. By providing [StoreKitVersion.DEFAULT], + * RevenueCat will automatically select the most appropriate StoreKit version + * for the app's runtime environment. + * + * **Warning:** Make sure you have an In-App Purchase Key configured in your app. + * Please see []revenuecat.com](https://rev.cat/in-app-purchase-key-configuration) + * for more info. + * + * - Note: StoreKit 2 is only available on iOS 16+. StoreKit 1 will be used for + * previous iOS versions regardless of this setting. + */ + public fun storeKitVersion(storeKitVersion: StoreKitVersion): Builder = + apply { this.storeKitVersion = storeKitVersion } + /** * Enable this setting to show in-app messages from Google Play automatically. Default is * enabled. @@ -153,6 +202,17 @@ public class PurchasesConfiguration private constructor( public fun verificationMode(verificationMode: EntitlementVerificationMode): Builder = apply { this.verificationMode = verificationMode } + /** + * Enable this setting if you want to allow pending purchases for prepaid subscriptions (only supported + * in Google Play). Note that entitlements are not granted until payment is done. + * Default is disabled. + */ + public fun pendingTransactionsForPrepaidPlansEnabled( + pendingTransactionsForPrepaidPlansEnabled: Boolean + ): Builder = apply { + this.pendingTransactionsForPrepaidPlansEnabled = pendingTransactionsForPrepaidPlansEnabled + } + /** * Creates a [PurchasesConfiguration] instance with the specified properties. */ @@ -161,11 +221,13 @@ public class PurchasesConfiguration private constructor( appUserId = appUserId, purchasesAreCompletedBy = purchasesAreCompletedBy, userDefaultsSuiteName = userDefaultsSuiteName, + storeKitVersion = storeKitVersion, showInAppMessagesAutomatically = showInAppMessagesAutomatically, store = store, diagnosticsEnabled = diagnosticsEnabled, dangerousSettings = dangerousSettings, - verificationMode = verificationMode + verificationMode = verificationMode, + pendingTransactionsForPrepaidPlansEnabled = pendingTransactionsForPrepaidPlansEnabled ) } } diff --git a/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/StoreKitVersion.kt b/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/StoreKitVersion.kt new file mode 100644 index 00000000..308c29dd --- /dev/null +++ b/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/StoreKitVersion.kt @@ -0,0 +1,32 @@ +package com.revenuecat.purchases.kmp + +/** + * Defines which version of StoreKit may be used. + */ +public enum class StoreKitVersion { + /** + * Always use StoreKit 1. + */ + STOREKIT_1, + + /** + * Always use StoreKit 2 (StoreKit 1 will be used if StoreKit 2 + * is not available in the current device.) + * + * - Warning: Make sure you have an In-App Purchase Key configured in your app. + * Please see https://rev.cat/in-app-purchase-key-configuration for more info. + */ + STOREKIT_2, + + /** + * Let RevenueCat use the most appropriate version of StoreKit + */ + DEFAULT, +} + +internal fun StoreKitVersion.toHybridString(): String = + when(this) { + StoreKitVersion.STOREKIT_1 -> "STOREKIT_1" + StoreKitVersion.STOREKIT_2 -> "STOREKIT_2" + StoreKitVersion.DEFAULT -> "DEFAULT" + } \ No newline at end of file diff --git a/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/models/InstallmentsInfo.kt b/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/models/InstallmentsInfo.kt new file mode 100644 index 00000000..eaec9038 --- /dev/null +++ b/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/models/InstallmentsInfo.kt @@ -0,0 +1,16 @@ +package com.revenuecat.purchases.kmp.models + +/** + * Type containing information of installment subscriptions. Currently only supported in Google Play. + */ +public expect interface InstallmentsInfo { + /** + * Number of payments the customer commits to in order to purchase the subscription. + */ + public val commitmentPaymentsCount: Int + + /** + * After the commitment payments are complete, the number of payments the user commits to upon a renewal. + */ + public val renewalCommitmentPaymentsCount: Int +} \ No newline at end of file diff --git a/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/models/SubscriptionOption.kt b/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/models/SubscriptionOption.kt index 2b158a44..da006a5e 100644 --- a/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/models/SubscriptionOption.kt +++ b/core/src/commonMain/kotlin/com/revenuecat/purchases/kmp/models/SubscriptionOption.kt @@ -46,6 +46,8 @@ public expect interface SubscriptionOption { public val presentedOfferingContext: PresentedOfferingContext? public val purchasingData: PurchasingData + + public val installmentsInfo: InstallmentsInfo? } /** diff --git a/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/PrintLnLogHandler.kt b/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/PrintLnLogHandler.kt new file mode 100644 index 00000000..c97469e7 --- /dev/null +++ b/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/PrintLnLogHandler.kt @@ -0,0 +1,28 @@ +package com.revenuecat.purchases.kmp + +/** + * A LogHandler which simply prints to the console. Useful as a fake in tests. + */ +object PrintLnLogHandler : LogHandler { + + override fun v(tag: String, msg: String) { + println("V: $tag $msg") + } + + override fun d(tag: String, msg: String) { + println("D: $tag $msg") + } + + override fun i(tag: String, msg: String) { + println("I: $tag $msg") + } + + override fun w(tag: String, msg: String) { + println("W: $tag $msg") + } + + override fun e(tag: String, msg: String, throwable: Throwable?) { + println("E: $tag $msg") + throwable?.printStackTrace() + } +} diff --git a/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedByTests.kt b/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedByTests.kt new file mode 100644 index 00000000..c6dc8812 --- /dev/null +++ b/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedByTests.kt @@ -0,0 +1,19 @@ +package com.revenuecat.purchases.kmp + +import kotlin.test.Test +import kotlin.test.assertEquals + +class PurchasesAreCompletedByTests { + + @Test + fun `toHybridString returns the correct values`() { + val purchasesAreCompletedByMyApp = PurchasesAreCompletedBy.MyApp(StoreKitVersion.DEFAULT) + val myAppHybridString = purchasesAreCompletedByMyApp.toHybridString() + + val purchasesAreCompletedByRevenueCat = PurchasesAreCompletedBy.RevenueCat + val revenuecatHybridString = purchasesAreCompletedByRevenueCat.toHybridString() + + assertEquals("MY_APP", myAppHybridString) + assertEquals("REVENUECAT", revenuecatHybridString) + } +} \ No newline at end of file diff --git a/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/PurchasesConfigurationTests.kt b/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/PurchasesConfigurationTests.kt new file mode 100644 index 00000000..5748f059 --- /dev/null +++ b/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/PurchasesConfigurationTests.kt @@ -0,0 +1,54 @@ +package com.revenuecat.purchases.kmp + +import kotlin.test.Test +import kotlin.test.assertEquals + + +class PurchasesConfigurationTests { + + @Test + fun `storeKitVersionToUse provides the correct result if the provided values are matching`() { + val config = PurchasesConfiguration(apiKey = "abc123") { + purchasesAreCompletedBy = PurchasesAreCompletedBy.RevenueCat + storeKitVersion = StoreKitVersion.DEFAULT + } + + assertEquals(StoreKitVersion.DEFAULT, config.storeKitVersionToUse()) + } + + @Test + fun `storeKitVersionToUse provides the correct result if storeKitVersion is missing`() { + val config = PurchasesConfiguration(apiKey = "abc123") { + purchasesAreCompletedBy = PurchasesAreCompletedBy.RevenueCat + } + + assertEquals(StoreKitVersion.DEFAULT, config.storeKitVersionToUse()) + } + + @Test + fun `storeKitVersionToUse provides the correct result if PurchasesAreCompletedBy is missing`() { + val config = PurchasesConfiguration(apiKey = "abc123") { + storeKitVersion = StoreKitVersion.STOREKIT_1 + } + + assertEquals(StoreKitVersion.STOREKIT_1, config.storeKitVersionToUse()) + } + + @Test + fun `storeKitVersionToUse provides the DEFAULT value if StoreKitVersion is not provided`() { + val config = PurchasesConfiguration(apiKey = "abc123") + + assertEquals(StoreKitVersion.DEFAULT, config.storeKitVersionToUse()) + } + + @Test + fun `storeKitVersionToUse provides the value from PurchasesAreCompletedBy if conflicting values are provided`() { + Purchases.logHandler = PrintLnLogHandler + val config = PurchasesConfiguration(apiKey = "abc123") { + purchasesAreCompletedBy = PurchasesAreCompletedBy.MyApp(StoreKitVersion.STOREKIT_2) + storeKitVersion = StoreKitVersion.STOREKIT_1 + } + + assertEquals(StoreKitVersion.STOREKIT_2, config.storeKitVersionToUse()) + } +} diff --git a/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/StoreKitVersionTests.kt b/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/StoreKitVersionTests.kt new file mode 100644 index 00000000..15cc11ed --- /dev/null +++ b/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/StoreKitVersionTests.kt @@ -0,0 +1,23 @@ +package com.revenuecat.purchases.kmp + +import kotlin.test.Test +import kotlin.test.assertEquals + +class StoreKitVersionTests { + + @Test + fun `toHybridString returns the correct values`() { + val storeKit1 = StoreKitVersion.STOREKIT_1 + val storeKit1String = storeKit1.toHybridString() + + val storeKit2 = StoreKitVersion.STOREKIT_2 + val storeKit2String = storeKit2.toHybridString() + + val storeKitDefault = StoreKitVersion.DEFAULT + val storeKitDefaultString = storeKitDefault.toHybridString() + + assertEquals("STOREKIT_1", storeKit1String) + assertEquals("STOREKIT_2", storeKit2String) + assertEquals("DEFAULT", storeKitDefaultString) + } +} \ No newline at end of file diff --git a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/Purchases.ios.kt b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/Purchases.ios.kt index c024ce16..a6b0fcd1 100644 --- a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/Purchases.ios.kt +++ b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/Purchases.ios.kt @@ -3,6 +3,7 @@ package com.revenuecat.purchases.kmp import cocoapods.PurchasesHybridCommon.RCCommonFunctionality import cocoapods.PurchasesHybridCommon.RCStoreProduct import cocoapods.PurchasesHybridCommon.configureWithAPIKey +import cocoapods.PurchasesHybridCommon.recordPurchaseForProductID import cocoapods.PurchasesHybridCommon.setAirshipChannelID import cocoapods.PurchasesHybridCommon.setOnesignalUserID import cocoapods.PurchasesHybridCommon.showStoreMessagesForTypes @@ -24,7 +25,6 @@ public actual class Purchases private constructor(private val iosPurchases: IosP private var _sharedInstance: Purchases? = null public actual val sharedInstance: Purchases get() = _sharedInstance ?: error(ConfigureStrings.NO_SINGLETON_INSTANCE) - public actual var logLevel: LogLevel get() = IosPurchases.logLevel().toLogLevel() set(value) = IosPurchases.setLogLevel(value.toRcLogLevel()) @@ -62,12 +62,11 @@ public actual class Purchases private constructor(private val iosPurchases: IosP IosPurchases.configureWithAPIKey( apiKey = apiKey, appUserID = appUserId, - purchasesAreCompletedBy = purchasesAreCompletedBy.toRCPurchasesAreCompletedBy(), + purchasesAreCompletedBy = purchasesAreCompletedBy.toHybridString(), userDefaultsSuiteName = userDefaultsSuiteName, platformFlavor = BuildKonfig.platformFlavor, platformFlavorVersion = frameworkVersion, - // In Flutter it's deprecated & defaults to false. - usesStoreKit2IfAvailable = false, + storeKitVersion = storeKitVersionToUse().toHybridString(), dangerousSettings = dangerousSettings.toIosDangerousSettings(), shouldShowInAppMessagesAutomatically = showInAppMessagesAutomatically, verificationMode = verificationMode.name, @@ -96,12 +95,6 @@ public actual class Purchases private constructor(private val iosPurchases: IosP IosDangerousSettings(autoSyncPurchases) } - public actual var purchasesAreCompletedBy: PurchasesAreCompletedBy - get() = iosPurchases.purchasesAreCompletedBy().toPurchasesAreCompletedBy() - set(value) { - iosPurchases.setPurchasesAreCompletedBy(value.toRCPurchasesAreCompletedBy()) - } - public actual val appUserID: String get() = iosPurchases.appUserID() @@ -185,7 +178,8 @@ public actual class Purchases private constructor(private val iosPurchases: IosP ) { transaction, customerInfo, error, userCancelled -> if (error != null) onError(error.toPurchasesErrorOrThrow(), userCancelled) else onSuccess( - transaction ?: error("Expected a non-null RCStoreTransaction"), + transaction?.let { StoreTransaction(it) } + ?: error("Expected a non-null RCStoreTransaction"), customerInfo ?: error("Expected a non-null RCCustomerInfo") ) } @@ -202,7 +196,8 @@ public actual class Purchases private constructor(private val iosPurchases: IosP ) { transaction, customerInfo, error, userCancelled -> if (error != null) onError(error.toPurchasesErrorOrThrow(), userCancelled) else onSuccess( - transaction ?: error("Expected a non-null RCStoreTransaction"), + transaction?.let { StoreTransaction(it) } + ?: error("Expected a non-null RCStoreTransaction"), customerInfo ?: error("Expected a non-null RCCustomerInfo") ) } @@ -231,7 +226,8 @@ public actual class Purchases private constructor(private val iosPurchases: IosP ) { transaction, customerInfo, error, userCancelled -> if (error != null) onError(error.toPurchasesErrorOrThrow(), userCancelled) else onSuccess( - transaction ?: error("Expected a non-null RCStoreTransaction"), + transaction?.let { StoreTransaction(it) } + ?: error("Expected a non-null RCStoreTransaction"), customerInfo ?: error("Expected a non-null RCCustomerInfo") ) } @@ -247,7 +243,8 @@ public actual class Purchases private constructor(private val iosPurchases: IosP ) { transaction, customerInfo, error, userCancelled -> if (error != null) onError(error.toPurchasesErrorOrThrow(), userCancelled) else onSuccess( - transaction ?: error("Expected a non-null RCStoreTransaction"), + transaction?.let { StoreTransaction(it) } + ?: error("Expected a non-null RCStoreTransaction"), customerInfo ?: error("Expected a non-null RCCustomerInfo") ) } @@ -260,6 +257,47 @@ public actual class Purchases private constructor(private val iosPurchases: IosP else onSuccess(customerInfo ?: error("Expected a non-null RCCustomerInfo")) } + public actual fun recordPurchase( + productID: String, + onError: (error: PurchasesError) -> Unit, + onSuccess: (storeTransaction: StoreTransaction) -> Unit, + ) { + RCCommonFunctionality.recordPurchaseForProductID( + productID, + completion = { storeTransactionMap, error -> + if (error != null) { + onError(error.error().toPurchasesErrorOrThrow()) + return@recordPurchaseForProductID + } + + if (storeTransactionMap == null) { + onError( + PurchasesError( + code = PurchasesErrorCode.UnknownError, + underlyingErrorMessage = + "Expected storeTransactionMap to be non-null when error is non-null." + ) + ) + } else { + val storeTransactionMappingResult = StoreTransaction.fromMap( + storeTransactionMap = storeTransactionMap + ) + storeTransactionMappingResult.onSuccess { + onSuccess(it) + } + storeTransactionMappingResult.onFailure { + onError( + PurchasesError( + code = PurchasesErrorCode.UnknownError, + underlyingErrorMessage = it.message + ) + ) + } + } + } + ) + } + public actual fun logIn( newAppUserID: String, onError: (error: PurchasesError) -> Unit, diff --git a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedBy.ios.kt b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedBy.ios.kt deleted file mode 100644 index e5f7f755..00000000 --- a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/PurchasesAreCompletedBy.ios.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.revenuecat.purchases.kmp - -import cocoapods.PurchasesHybridCommon.RCPurchasesAreCompletedBy -import cocoapods.PurchasesHybridCommon.RCPurchasesAreCompletedByMyApp -import cocoapods.PurchasesHybridCommon.RCPurchasesAreCompletedByRevenueCat - -public actual enum class PurchasesAreCompletedBy { - REVENUECAT, - MY_APP, -} - -internal fun RCPurchasesAreCompletedBy.toPurchasesAreCompletedBy(): PurchasesAreCompletedBy = - when (this) { - RCPurchasesAreCompletedByRevenueCat -> PurchasesAreCompletedBy.REVENUECAT - RCPurchasesAreCompletedByMyApp -> PurchasesAreCompletedBy.MY_APP - else -> error("Unexpected RCPurchasesAreCompletedBy: $this") - } - -internal fun PurchasesAreCompletedBy.toRCPurchasesAreCompletedBy(): RCPurchasesAreCompletedBy = - when (this) { - PurchasesAreCompletedBy.REVENUECAT -> RCPurchasesAreCompletedByRevenueCat - PurchasesAreCompletedBy.MY_APP -> RCPurchasesAreCompletedByMyApp - } diff --git a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/PurchasesDelegate.ios.kt b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/PurchasesDelegate.ios.kt index c7fd3042..f243cdde 100644 --- a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/PurchasesDelegate.ios.kt +++ b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/PurchasesDelegate.ios.kt @@ -7,6 +7,7 @@ import cocoapods.PurchasesHybridCommon.RCPurchases import cocoapods.PurchasesHybridCommon.RCPurchasesDelegateProtocol import cocoapods.PurchasesHybridCommon.RCStoreProduct import cocoapods.PurchasesHybridCommon.RCStoreTransaction +import com.revenuecat.purchases.kmp.models.StoreTransaction import platform.Foundation.NSError import platform.darwin.NSObject @@ -30,7 +31,8 @@ private class PurchasesDelegateWrapper(val wrapped: PurchasesDelegate) : purchase { transaction, customerInfo, error, userCancelled -> if (error != null) onError(error.toPurchasesErrorOrThrow(), userCancelled) else onSuccess( - transaction ?: error("Expected a non-null RCStoreTransaction"), + transaction?.let { StoreTransaction(it) } + ?: error("Expected a non-null RCStoreTransaction"), customerInfo ?: error("Expected a non-null RCCustomerInfo") ) } diff --git a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/models/InstallmentsInfo.ios.kt b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/models/InstallmentsInfo.ios.kt new file mode 100644 index 00000000..145f356d --- /dev/null +++ b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/models/InstallmentsInfo.ios.kt @@ -0,0 +1,24 @@ +package com.revenuecat.purchases.kmp.models + +import com.revenuecat.purchases.kmp.ProductType + +/** + * Type containing information of installment subscriptions. Currently only supported in Google Play. + */ +public actual interface InstallmentsInfo { + /** + * Number of payments the customer commits to in order to purchase the subscription. + */ + public actual val commitmentPaymentsCount: Int + + /** + * After the commitment payments are complete, the number of payments the user commits to upon a renewal. + */ + public actual val renewalCommitmentPaymentsCount: Int + +} + +private data class IosInstallmentsInfo( + override val commitmentPaymentsCount: Int, + override val renewalCommitmentPaymentsCount: Int +): InstallmentsInfo \ No newline at end of file diff --git a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/models/StoreTransaction.ios.kt b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/models/StoreTransaction.ios.kt index 148787c6..b86df2ba 100644 --- a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/models/StoreTransaction.ios.kt +++ b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/models/StoreTransaction.ios.kt @@ -2,14 +2,67 @@ package com.revenuecat.purchases.kmp.models import cocoapods.PurchasesHybridCommon.RCStoreTransaction import com.revenuecat.purchases.kmp.ktx.toEpochMilliseconds +import platform.Foundation.NSDate -public actual typealias StoreTransaction = RCStoreTransaction +public actual class StoreTransaction private constructor( + internal val transactionId: String?, + internal val productIds: List, + internal val purchaseTime: Long +) { + + internal constructor( + rcTransaction: RCStoreTransaction + ) : this( + transactionId = rcTransaction.transactionIdentifier(), + productIds = listOf(rcTransaction.productIdentifier()), + purchaseTime = rcTransaction.purchaseDate().toEpochMilliseconds() + ) + + internal constructor( + transactionId: String, + productId: String, + purchaseTime: Long, + ) : this( + transactionId = transactionId, + productIds = listOf(productId), + purchaseTime = purchaseTime + ) + + internal companion object { + fun fromMap(storeTransactionMap: Map): Result { + val transactionId = storeTransactionMap["transactionIdentifier"] as? String + val productId = storeTransactionMap["productIdentifier"] as? String + val purchaseTime = (storeTransactionMap["purchaseDateMillis"] as? Number)?.toLong() + + if(transactionId == null) { + return Result.failure(IllegalArgumentException("Expected a non-null transactionIdentifier")) + } + + if(productId == null) { + return Result.failure(IllegalArgumentException("Expected a non-null productIdentifier")) + } + + if(purchaseTime == null) { + return Result.failure(IllegalArgumentException("Expected a non-null purchaseDateMillis")) + } + + return Result.success( + StoreTransaction( + transactionId = transactionId, + productId = productId, + purchaseTime = purchaseTime + ) + ) + } + } + +} public actual val StoreTransaction.transactionId: String? - get() = transactionIdentifier() + get() = transactionId public actual val StoreTransaction.productIds: List - get() = listOf(productIdentifier()) + get() = productIds public actual val StoreTransaction.purchaseTime: Long - get() = purchaseDate().toEpochMilliseconds() + get() = purchaseTime diff --git a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/models/SubscriptionOption.ios.kt b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/models/SubscriptionOption.ios.kt index 4cd3d94d..c2d2f6a2 100644 --- a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/models/SubscriptionOption.ios.kt +++ b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/models/SubscriptionOption.ios.kt @@ -9,4 +9,5 @@ public actual interface SubscriptionOption { public actual val presentedOfferingIdentifier: String? public actual val presentedOfferingContext: PresentedOfferingContext? public actual val purchasingData: PurchasingData + public actual val installmentsInfo: InstallmentsInfo? } diff --git a/core/src/iosTest/kotlin/com/revenuecat/purchases/models/StoreTransactionTests.ios.kt b/core/src/iosTest/kotlin/com/revenuecat/purchases/models/StoreTransactionTests.ios.kt new file mode 100644 index 00000000..82e697e7 --- /dev/null +++ b/core/src/iosTest/kotlin/com/revenuecat/purchases/models/StoreTransactionTests.ios.kt @@ -0,0 +1,125 @@ +package com.revenuecat.purchases.models + + +import com.revenuecat.purchases.kmp.models.StoreTransaction +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class StoreTransactionTests { + + @Test + fun `fromMap correctly parses map with all fields`() { + val transactionId = "someTransactionIdentifier" + val productId = "someProductIdentifier" + val purchaseDateMillis = 1627689600000.0 + val purchaseDate = "2021-07-31T00:00:00Z" + + val hybridMap: Map = mapOf( + "transactionIdentifier" to transactionId, + "revenueCatId" to transactionId, // Deprecated + "productIdentifier" to productId, + "productId" to productId, // Deprecated + "purchaseDateMillis" to purchaseDateMillis, + "purchaseDate" to purchaseDate + ) + + val result = StoreTransaction.fromMap(storeTransactionMap = hybridMap) + + assertTrue(result.isSuccess) + val storeTransaction = result.getOrThrow() + assertEquals(transactionId, storeTransaction.transactionId) + assertEquals(listOf(productId), storeTransaction.productIds) + assertEquals(purchaseDateMillis.toLong(), storeTransaction.purchaseTime) + } + + @Test + fun `fromMap correctly parses map with all fields if purchaseDateMillis is an int`() { + val transactionId = "someTransactionIdentifier" + val productId = "someProductIdentifier" + val purchaseDateMillis = 1627689600000 + val purchaseDate = "2021-07-31T00:00:00Z" + + val hybridMap: Map = mapOf( + "transactionIdentifier" to transactionId, + "revenueCatId" to transactionId, // Deprecated + "productIdentifier" to productId, + "productId" to productId, // Deprecated + "purchaseDateMillis" to purchaseDateMillis, + "purchaseDate" to purchaseDate + ) + + val result = StoreTransaction.fromMap(storeTransactionMap = hybridMap) + + assertTrue(result.isSuccess) + val storeTransaction = result.getOrThrow() + assertEquals(transactionId, storeTransaction.transactionId) + assertEquals(listOf(productId), storeTransaction.productIds) + assertEquals(purchaseDateMillis.toLong(), storeTransaction.purchaseTime) + } + + @Test + fun `missing transactionIdentifier returns failure`() { + val productId = "someProductIdentifier" + val purchaseDateMillis = 1627689600000.0 + val purchaseDate = "2021-07-31T00:00:00Z" + + val hybridMap: Map = mapOf( + "productIdentifier" to productId, + "purchaseDateMillis" to purchaseDateMillis, + "purchaseDate" to purchaseDate + ) + + val result = StoreTransaction.fromMap(storeTransactionMap = hybridMap) + + assertTrue(result.isFailure) + result.exceptionOrNull()?.let { + assertTrue(it is IllegalArgumentException) + assertEquals("Expected a non-null transactionIdentifier", it.message) + } + } + + @Test + fun `missing productIdentifier returns failure`() { + val transactionId = "someTransactionIdentifier" + val purchaseDateMillis = 1627689600000.0 + val purchaseDate = "2021-07-31T00:00:00Z" + + val hybridMap: Map = mapOf( + "transactionIdentifier" to transactionId, + "purchaseDateMillis" to purchaseDateMillis, + "purchaseDate" to purchaseDate + ) + + val result = StoreTransaction.fromMap(storeTransactionMap = hybridMap) + + assertTrue(result.isFailure) + result.exceptionOrNull()?.let { + assertTrue(it is IllegalArgumentException) + assertEquals("Expected a non-null productIdentifier", it.message) + } + } + + @Test + fun `missing purchaseDateMillis returns failure`() { + val transactionId = "someTransactionIdentifier" + val productId = "someProductIdentifier" + val purchaseDate = "2021-07-31T00:00:00Z" + + val hybridMap: Map = mapOf( + "transactionIdentifier" to transactionId, + "productIdentifier" to productId, + "purchaseDateMillis" to "InvalidMillis", // This should be a Double + "purchaseDate" to purchaseDate + ) + + val result = StoreTransaction.fromMap(storeTransactionMap = hybridMap) + + assertTrue(result.isFailure) + result.exceptionOrNull()?.let { + assertTrue(it is IllegalArgumentException) + assertEquals("Expected a non-null purchaseDateMillis", it.message) + } + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 34907c44..ca58913a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ android-minSdk = "21" android-targetSdk = "34" java = "1.8" kotlin = "1.9.23" -revenuecat-common = "11.1.1" +revenuecat-common = "13.0.1" revenuecat-kmp = "1.0.0-SNAPSHOT" [libraries] diff --git a/iosApp/Podfile b/iosApp/Podfile index 1a034d45..b0b21437 100644 --- a/iosApp/Podfile +++ b/iosApp/Podfile @@ -6,6 +6,6 @@ target 'iosApp' do # use_frameworks! # Pods for iosApp - pod 'PurchasesHybridCommon', '11.1.1' - pod 'PurchasesHybridCommonUI', '11.1.1' + pod 'PurchasesHybridCommon', '13.0.1' + pod 'PurchasesHybridCommonUI', '13.0.1' end diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock index 4dc93fd2..5b50357d 100644 --- a/iosApp/Podfile.lock +++ b/iosApp/Podfile.lock @@ -1,16 +1,16 @@ PODS: - - PurchasesHybridCommon (11.1.1): - - RevenueCat (= 4.43.2) - - PurchasesHybridCommonUI (11.1.1): - - PurchasesHybridCommon (= 11.1.1) - - RevenueCatUI (= 4.43.2) - - RevenueCat (4.43.2) - - RevenueCatUI (4.43.2): - - RevenueCat (= 4.43.2) + - PurchasesHybridCommon (13.0.1): + - RevenueCat (= 5.2.3) + - PurchasesHybridCommonUI (13.0.1): + - PurchasesHybridCommon (= 13.0.1) + - RevenueCatUI (= 5.2.3) + - RevenueCat (5.2.3) + - RevenueCatUI (5.2.3): + - RevenueCat (= 5.2.3) DEPENDENCIES: - - PurchasesHybridCommon (= 11.1.1) - - PurchasesHybridCommonUI (= 11.1.1) + - PurchasesHybridCommon (= 13.0.1) + - PurchasesHybridCommonUI (= 13.0.1) SPEC REPOS: trunk: @@ -20,11 +20,11 @@ SPEC REPOS: - RevenueCatUI SPEC CHECKSUMS: - PurchasesHybridCommon: 73f4f2c6e4ec3487b4219bf60e1661100fb85da3 - PurchasesHybridCommonUI: 4c8b38f7cdb12d4d29813d9e2f90ca519ed04bec - RevenueCat: 3d934653b7e8b09af88fd47e9e84cfaf5d0a89ba - RevenueCatUI: 1417f375baf005eaf3afa8bc99724f567abfd6da + PurchasesHybridCommon: 6e5772e574ce345922c42f31403cf72a0ba762bf + PurchasesHybridCommonUI: 015291e795d694c83ab1fd35caf3716c7c1f7cdf + RevenueCat: 86e4486345a6f7740fecb1eeff91eeff8708eee1 + RevenueCatUI: c19388c5f35ef5beea1b9b7a2593479f12303dc6 -PODFILE CHECKSUM: 0037313ddb3029b5a9639ad45f5ec01bb5e70c99 +PODFILE CHECKSUM: 6d9a95ffe52c861fbf2a9732430df4145d15f979 COCOAPODS: 1.15.2 diff --git a/migrations/1.0.0-beta.3-MIGRATION.md b/migrations/1.0.0-beta.3-MIGRATION.md new file mode 100644 index 00000000..32503bd7 --- /dev/null +++ b/migrations/1.0.0-beta.3-MIGRATION.md @@ -0,0 +1,80 @@ +## 1.0.0-beta.3 API Changes + +This latest release updates the Android SDK dependency from v7 to [v8](https://github.com/RevenueCat/purchases-android/releases/tag/6.0.0) to use BillingClient 7 and updates the iOS SDK dependency from v4 to v5 to use StoreKit 2 by default in the SDK. + +### Migration Guides + +- See [Android Native - V8 API Migration Guide](https://github.com/RevenueCat/purchases-android/blob/main/migrations/v8-MIGRATION.md) for a more thorough explanation of the Android changes. +- See [iOS Native - V5 Migration Guide](https://github.com/RevenueCat/purchases-ios/blob/main/Sources/DocCDocumentation/DocCDocumentation.docc/V5_API_Migration_guide.md) for a more thorough explanation of the iOS changes. Notably, this version uses StoreKit 2 to process purchases by default. + +### New Minimum OS Versions + +This release raises the minimum required OS versions to the following: + +- iOS 13.0 +- tvOS 13.0 +- watchOS 6.2 +- macOS 10.15 +- Android: SDK 21 (Android 5.0) + +### In-App Purchase Key Required for StoreKit 2 + +In order to use StoreKit 2, you must configure your In-App Purchase Key in the RevenueCat dashboard. You can find instructions describing how to do this [here](https://www.revenuecat.com/docs/in-app-purchase-key-configuration). + +### Specifying StoreKit version with `storeKitVersion` (iOS Only) + +When configuring the SDK, you may specify which StoreKit version `storeKitVersion: StoreKitVersion` value. It defaults to letting the iOS SDK determine the most appropriate version of StoreKit at runtime. If you'd like to use a specific version of StoreKit, you may provide a value for `storeKitVersion` like so: + +```kotlin +val config: PurchasesConfiguration = PurchasesConfiguration(apiKey = {YOUR_API_KEY}) { + storeKitVersion = StoreKitVersion.STOREKIT_2 +} + +Purchases.configure(config) +``` + +### PurchasesAreCompletedBy Is Now a Sealed Interface +`PurchasesAreCompletedBy` is now a sealed interface instead of an enum, with two types: `RevenueCat` and `MyApp`. When specifying MyApp, you must now provide the StoreKit version you are using to process purchases on iOS. If your app is Android-only, you may provide any value since it is ignored on Android. + +You can set `purchasesAreCompletedBy` to `MyApp` when configuring the SDK like so: + +```kotlin +val configuration = PurchasesConfiguration(apiKey = {YOUR_API_KEY}) { + purchasesAreCompletedBy = PurchasesAreCompletedBy.MyApp(StoreKitVersion.STOREKIT_2) +} +Purchases.configure(configuration) +``` + +#### ⚠️ Observing Purchases Completed by Your App on macOS + +By default, when purchases are completed by your app using StoreKit 2 on macOS, the SDK does not detect a user's purchase until after the user foregrounds the app after the purchase has been made. If you'd like RevenueCat to immediately detect the user's purchase, call `Purchases.recordPurchase(productID)` for any new purchases, like so: + +```kotlin +Purchases.sharedInstance.recordPurchase( + productID = {PRODUCT_ID_OF_RECENTLY_PURCHASED_PRODUCT}, + onError = { error -> }, + onSuccess = { storeTransaction -> } +) +``` + +#### Observing Purchases Completed by Your App with StoreKit 1 + +If purchases are completed by your app using StoreKit 1, you will need to explicitly configure the SDK to use StoreKit 1: + +```kotlin +val configuration = PurchasesConfiguration(apiKey = {YOUR_API_KEY}) { + purchasesAreCompletedBy = PurchasesAreCompletedBy.MyApp(StoreKitVersion.STOREKIT_1) +} +Purchases.configure(configuration) +``` + +#### Removal of Purchases.purchasesAreCompletedBy +The getter and setter on `Purchases.sharedInstance.purchasesAreCompletedBy` has been removed. Please reach out to us if this is something you've been using and need to continue using. + +#### Other BC7 Additions (Android Only) +- `PurchasesConfiguration.pendingTransactionsForPrepaidPlansEnabled`: Enable this setting if you want to allow pending purchases for prepaid subscriptions (only supported in Google Play). Note that entitlements are not granted until payment is done. Default is disabled. +- `SubscriptionOption.InstallmentsInfo`: Type containing information of installment subscriptions. Currently only supported in Google Play. + +### Reporting undocumented issues: + +Feel free to file an issue! [New RevenueCat Issue](https://github.com/RevenueCat/purchases-flutter/issues/new/). \ No newline at end of file diff --git a/revenuecatui/api/revenuecatui.klib.api b/revenuecatui/api/revenuecatui.klib.api index 54f87a96..41fc8906 100644 --- a/revenuecatui/api/revenuecatui.klib.api +++ b/revenuecatui/api/revenuecatui.klib.api @@ -8,7 +8,7 @@ // Library unique name: abstract interface com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallListener { // com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallListener|null[0] open fun onPurchaseCancelled() // com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallListener.onPurchaseCancelled|onPurchaseCancelled(){}[0] - open fun onPurchaseCompleted(cocoapods.PurchasesHybridCommon/RCCustomerInfo, cocoapods.PurchasesHybridCommon/RCStoreTransaction) // com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallListener.onPurchaseCompleted|onPurchaseCompleted(cocoapods.PurchasesHybridCommon.RCCustomerInfo;cocoapods.PurchasesHybridCommon.RCStoreTransaction){}[0] + open fun onPurchaseCompleted(cocoapods.PurchasesHybridCommon/RCCustomerInfo, com.revenuecat.purchases.kmp.models/StoreTransaction) // com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallListener.onPurchaseCompleted|onPurchaseCompleted(cocoapods.PurchasesHybridCommon.RCCustomerInfo;com.revenuecat.purchases.kmp.models.StoreTransaction){}[0] open fun onPurchaseError(com.revenuecat.purchases.kmp/PurchasesError) // com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallListener.onPurchaseError|onPurchaseError(com.revenuecat.purchases.kmp.PurchasesError){}[0] open fun onPurchaseStarted(cocoapods.PurchasesHybridCommon/RCPackage) // com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallListener.onPurchaseStarted|onPurchaseStarted(cocoapods.PurchasesHybridCommon.RCPackage){}[0] open fun onRestoreCompleted(cocoapods.PurchasesHybridCommon/RCCustomerInfo) // com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallListener.onRestoreCompleted|onRestoreCompleted(cocoapods.PurchasesHybridCommon.RCCustomerInfo){}[0] diff --git a/revenuecatui/build.gradle.kts b/revenuecatui/build.gradle.kts index 17003991..d45bad6d 100644 --- a/revenuecatui/build.gradle.kts +++ b/revenuecatui/build.gradle.kts @@ -20,7 +20,7 @@ kotlin { cocoapods { version = "1.0" - ios.deploymentTarget = "11.0" + ios.deploymentTarget = "13.0" pod("PurchasesHybridCommonUI") { version = libs.versions.revenuecat.common.get() diff --git a/revenuecatui/revenuecatui.podspec b/revenuecatui/revenuecatui.podspec index eb819052..273eac18 100644 --- a/revenuecatui/revenuecatui.podspec +++ b/revenuecatui/revenuecatui.podspec @@ -8,8 +8,8 @@ Pod::Spec.new do |spec| spec.summary = '' spec.vendored_frameworks = 'build/cocoapods/framework/revenuecatui.framework' spec.libraries = 'c++' - spec.ios.deployment_target = '11.0' - spec.dependency 'PurchasesHybridCommonUI', '11.1.1' + spec.ios.deployment_target = '13.0' + spec.dependency 'PurchasesHybridCommonUI', '13.0.1' if !Dir.exist?('build/cocoapods/framework/revenuecatui.framework') || Dir.empty?('build/cocoapods/framework/revenuecatui.framework') raise "