From db174510857c7e79667ed9759681673f7bd17cc3 Mon Sep 17 00:00:00 2001 From: Surik Date: Wed, 14 Aug 2024 14:38:05 +0400 Subject: [PATCH 01/11] Added context keys to purchase --- .../com/qonversion/android/sdk/Qonversion.kt | 41 +++++++++++- .../android/sdk/dto/QPurchaseOptions.kt | 46 ++++++++++++++ .../sdk/internal/QProductCenterManager.kt | 62 ++++++++++++++++++- .../sdk/internal/QonversionInternal.kt | 21 +++++++ .../billing/QonversionBillingService.kt | 8 +-- .../converter/GooglePurchaseConverter.kt | 9 ++- .../internal/converter/PurchaseConverter.kt | 5 +- .../sdk/internal/di/module/AppModule.kt | 2 +- .../internal/dto/QonversionMappingAdapters.kt | 16 +++++ .../sdk/internal/dto/purchase/Inapp.kt | 3 +- .../dto/purchase/PurchaseModelInternal.kt | 19 +++--- .../purchase/PurchaseModelInternalEnriched.kt | 18 +++--- .../android/sdk/internal/purchase/Purchase.kt | 1 + .../internal/repository/DefaultRepository.kt | 3 +- .../sdk/internal/storage/PurchasesCache.kt | 34 ++++++++-- 15 files changed, 248 insertions(+), 40 deletions(-) create mode 100644 sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt diff --git a/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt b/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt index 1d7ca44b..649c2872 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt @@ -4,7 +4,9 @@ import android.app.Activity import android.util.Log import com.qonversion.android.sdk.dto.QAttributionProvider import com.qonversion.android.sdk.dto.QPurchaseModel +import com.qonversion.android.sdk.dto.QPurchaseOptions import com.qonversion.android.sdk.dto.QPurchaseUpdateModel +import com.qonversion.android.sdk.dto.products.QProduct import com.qonversion.android.sdk.dto.properties.QUserPropertyKey import com.qonversion.android.sdk.internal.InternalConfig import com.qonversion.android.sdk.internal.QonversionInternal @@ -82,7 +84,43 @@ interface Qonversion { * @param callback - callback that will be called when response is received * @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases) */ - fun purchase(context: Activity, purchaseModel: QPurchaseModel, callback: QonversionEntitlementsCallback) + @Deprecated("Use new purchase() method", replaceWith = ReplaceWith("purchase(context: Activity, product: QProduct, options: QPurchaseOptions, callback: QonversionEntitlementsCallback)")) + fun purchase( + context: Activity, + purchaseModel: QPurchaseModel, + callback: QonversionEntitlementsCallback + ) + + /** + * Make a purchase and validate it through server-to-server using Qonversion's Backend + * @param context current activity context + * @param product product for purchase + * @param options necessary information for purchase + * @param callback - callback that will be called when response is received + * @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases) + */ + fun purchase( + context: Activity, + product: QProduct, + options: QPurchaseOptions, + callback: QonversionEntitlementsCallback + ) + + /** + * Update (upgrade/downgrade) subscription and validate it through server-to-server using Qonversion's Backend + * @param context current activity context + * @param product product for purchase + * @param options necessary information for purchase + * @param callback - callback that will be called when response is received + * @see [Update policy](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) + * @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases) + */ + fun updatePurchase( + context: Activity, + product: QProduct, + options: QPurchaseOptions, + callback: QonversionEntitlementsCallback + ) /** * Update (upgrade/downgrade) subscription and validate it through server-to-server using Qonversion's Backend @@ -92,6 +130,7 @@ interface Qonversion { * @see [Update policy](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) * @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases) */ + @Deprecated("Use new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context: Activity, product: QProduct, options: QPurchaseOptions, callback: QonversionEntitlementsCallback)")) fun updatePurchase( context: Activity, purchaseUpdateModel: QPurchaseUpdateModel, diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt new file mode 100644 index 00000000..0a16498d --- /dev/null +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt @@ -0,0 +1,46 @@ +package com.qonversion.android.sdk.dto + +import com.qonversion.android.sdk.dto.products.QProduct +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +class QPurchaseOptions internal constructor ( + internal val contextKeys: List? = null, + internal val offerId: String? = null, + internal val applyOffer: Boolean = true, + internal val oldProduct: QProduct? = null, + internal val updatePolicy: QPurchaseUpdatePolicy? = null +) { + class Builder { + private var contextKeys: List? = null + private var offerId: String? = null + private var applyOffer: Boolean = true + private var oldProduct: QProduct? = null + private var updatePolicy: QPurchaseUpdatePolicy? = null + + + fun setContextKeys(contextKeys: List): QPurchaseOptions.Builder = apply { + this.contextKeys = contextKeys + } + + fun setOfferId(offerId: String): QPurchaseOptions.Builder = apply { + this.offerId = offerId + } + + fun setOldProduct(oldProduct: QProduct): QPurchaseOptions.Builder = apply { + this.oldProduct = oldProduct + } + + fun setUpdatePolicy(updatePolicy: QPurchaseUpdatePolicy): QPurchaseOptions.Builder = apply { + this.updatePolicy = updatePolicy + } + + fun removeOffer() = apply { + this.applyOffer = false + } + + fun build(): QPurchaseOptions { + return QPurchaseOptions(contextKeys, offerId, applyOffer, oldProduct, updatePolicy) + } + } +} diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt index 4bce9ad3..22beebf6 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt @@ -5,6 +5,7 @@ import android.app.Application import android.content.pm.PackageManager import android.os.Build import com.android.billingclient.api.Purchase +import com.qonversion.android.sdk.dto.QPurchaseOptions import com.qonversion.android.sdk.dto.entitlements.QEntitlement import com.qonversion.android.sdk.listeners.QonversionEligibilityCallback import com.qonversion.android.sdk.dto.QonversionError @@ -94,6 +95,8 @@ internal class QProductCenterManager internal constructor( private var converter: PurchaseConverter = GooglePurchaseConverter() + private var processingPurchaseOptions: Map? = null + @Volatile lateinit var billingService: BillingService @Synchronized set @@ -284,6 +287,15 @@ internal class QProductCenterManager internal constructor( return launchResultCache.getActualOfferings() } + fun purchase(context: Activity, + product: QProduct, + options: QPurchaseOptions, + callback: QonversionEntitlementsCallback) { + val purchaseModel = PurchaseModelInternal(product, options) + + purchaseProduct(context, purchaseModel, callback) + } + fun purchaseProduct( context: Activity, purchaseModel: PurchaseModelInternal, @@ -324,7 +336,7 @@ internal class QProductCenterManager internal constructor( callback.onError(QonversionError(QonversionErrorCode.ProductNotFound)) return } - val oldProduct: QProduct? = getProductForPurchase(purchaseModel.oldProductId, products) + val oldProduct: QProduct? = purchaseModel.options.oldProduct ?: getProductForPurchase(purchaseModel.oldProductId, products) val purchaseModelEnriched = purchaseModel.enrich(product, oldProduct) processPurchase(context, purchaseModelEnriched, callback) } @@ -349,9 +361,41 @@ internal class QProductCenterManager internal constructor( } purchasingCallbacks[purchaseModel.product.storeID] = callback + + updatePurchaseOptions(purchaseModel.options, purchaseModel.product.storeID) + billingService.purchase(context, purchaseModel) } + private fun actualPurchaseOptions(): Map { + processingPurchaseOptions?.let { + return it + } + + val cachedPurchaseOptions = purchasesCache.loadProcessingPurchasesOptions() + processingPurchaseOptions = cachedPurchaseOptions + + return cachedPurchaseOptions + } + + private fun updatePurchaseOptions(options: QPurchaseOptions?, storeProductId: String?) { + storeProductId?.let { productId -> + val actualOptions = actualPurchaseOptions().toMutableMap() + options?.let { + actualOptions[productId] = it + } ?: run { + actualOptions.remove(productId) + } + + processingPurchaseOptions = actualOptions.toMap() + purchasesCache.saveProcessingPurchasesOptions(processingPurchaseOptions) + } + } + + private fun removePurchaseOptions(productId: String?) { + updatePurchaseOptions(null, productId) + } + private fun getProductForPurchase( productId: String?, products: Map @@ -651,7 +695,9 @@ internal class QProductCenterManager internal constructor( processingPurchases = completedPurchases - val purchasesInfo = converter.convertPurchases(completedPurchases) + val processingPurchaseOptions = actualPurchaseOptions() + + val purchasesInfo = converter.convertPurchases(completedPurchases, processingPurchaseOptions) val handledPurchasesCallback = getWrappedPurchasesCallback(completedPurchases, callback) @@ -673,6 +719,9 @@ internal class QProductCenterManager internal constructor( return object : QonversionLaunchCallback { override fun onSuccess(launchResult: QLaunchResult) { handledPurchasesCache.saveHandledPurchases(trackingPurchases) + trackingPurchases.forEach { + removePurchaseOptions(it.productId) + } outerCallback?.onSuccess(launchResult) } @@ -959,7 +1008,9 @@ internal class QProductCenterManager internal constructor( val product: QProduct? = launchResultCache.getActualProducts()?.values?.find { it.storeID == purchase.productId } - val purchaseInfo = converter.convertPurchase(purchase) + val processingPurchaseOptions = actualPurchaseOptions() + val currentPurchaseOptions = processingPurchaseOptions[purchase.productId] + val purchaseInfo = converter.convertPurchase(purchase, currentPurchaseOptions) repository.purchase( installDate, purchaseInfo, @@ -970,6 +1021,7 @@ internal class QProductCenterManager internal constructor( val entitlements = launchResult.permissions.toEntitlementsMap() + removePurchaseOptions(product?.storeID) purchaseCallback?.onSuccess(entitlements) ?: run { internalConfig.entitlementsUpdateListener?.onEntitlementsUpdated( entitlements @@ -981,6 +1033,10 @@ internal class QProductCenterManager internal constructor( override fun onError(error: QonversionError) { storeFailedPurchaseIfNecessary(purchase, purchaseInfo, product) + product?.storeID?.let { + removePurchaseOptions(it) + } + if (shouldCalculatePermissionsLocally(error)) { calculatePurchasePermissionsLocally( purchase, diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt index 15be6732..b0451f1a 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt @@ -9,6 +9,7 @@ import com.qonversion.android.sdk.Qonversion import com.qonversion.android.sdk.automations.internal.QAutomationsManager import com.qonversion.android.sdk.dto.QAttributionProvider import com.qonversion.android.sdk.dto.QPurchaseModel +import com.qonversion.android.sdk.dto.QPurchaseOptions import com.qonversion.android.sdk.dto.QPurchaseUpdateModel import com.qonversion.android.sdk.dto.entitlements.QEntitlement import com.qonversion.android.sdk.dto.QRemoteConfig @@ -170,6 +171,26 @@ internal class QonversionInternal( ) } + override fun purchase(context: Activity, + product: QProduct, + options: QPurchaseOptions, + callback: QonversionEntitlementsCallback) { + productCenterManager.purchase(context, product, options, callback) + } + + override fun updatePurchase( + context: Activity, + product: QProduct, + options: QPurchaseOptions, + callback: QonversionEntitlementsCallback + ) { + productCenterManager.purchaseProduct( + context, + PurchaseModelInternal(product, options), + mainEntitlementsCallback(callback) + ) + } + override fun updatePurchase( context: Activity, purchaseUpdateModel: QPurchaseUpdateModel, diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/QonversionBillingService.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/QonversionBillingService.kt index 5d7c5a61..99c25756 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/QonversionBillingService.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/QonversionBillingService.kt @@ -103,16 +103,16 @@ internal class QonversionBillingService internal constructor( updatePurchase( activity, purchaseModel.product, - purchaseModel.offerId, - purchaseModel.applyOffer, + purchaseModel.options.offerId, + purchaseModel.options.applyOffer, purchaseModel.oldProduct, purchaseModel.updatePolicy) } else { makePurchase( activity, purchaseModel.product, - purchaseModel.offerId, - purchaseModel.applyOffer + purchaseModel.options.offerId, + purchaseModel.options.applyOffer ) } } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/GooglePurchaseConverter.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/GooglePurchaseConverter.kt index fb48c63a..00454798 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/GooglePurchaseConverter.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/GooglePurchaseConverter.kt @@ -1,5 +1,6 @@ package com.qonversion.android.sdk.internal.converter +import com.qonversion.android.sdk.dto.QPurchaseOptions import com.qonversion.android.sdk.internal.billing.productId import com.qonversion.android.sdk.internal.milliSecondsToSeconds import com.qonversion.android.sdk.internal.purchase.Purchase @@ -7,18 +8,20 @@ import com.qonversion.android.sdk.internal.purchase.Purchase internal class GooglePurchaseConverter : PurchaseConverter { override fun convertPurchases( - purchases: List + purchases: List, + options: Map? ): List { - return purchases.map { convertPurchase(it) } + return purchases.map { convertPurchase(it, options?.get(it.productId)) } } - override fun convertPurchase(purchase: com.android.billingclient.api.Purchase): Purchase { + override fun convertPurchase(purchase: com.android.billingclient.api.Purchase, options: QPurchaseOptions?): Purchase { return Purchase( storeProductId = purchase.productId, orderId = purchase.orderId ?: "", originalOrderId = formatOriginalTransactionId(purchase.orderId ?: ""), purchaseTime = purchase.purchaseTime.milliSecondsToSeconds(), purchaseToken = purchase.purchaseToken, + contextKeys = options?.contextKeys ) } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/PurchaseConverter.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/PurchaseConverter.kt index edf997a7..7cf8a966 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/PurchaseConverter.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/PurchaseConverter.kt @@ -1,9 +1,10 @@ package com.qonversion.android.sdk.internal.converter +import com.qonversion.android.sdk.dto.QPurchaseOptions import com.qonversion.android.sdk.internal.purchase.Purchase internal interface PurchaseConverter { - fun convertPurchase(purchase: com.android.billingclient.api.Purchase): Purchase + fun convertPurchase(purchase: com.android.billingclient.api.Purchase, options: QPurchaseOptions?): Purchase - fun convertPurchases(purchases: List): List + fun convertPurchases(purchases: List, options: Map?): List } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/di/module/AppModule.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/di/module/AppModule.kt index 8a6c8b15..e9b0ddd6 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/di/module/AppModule.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/di/module/AppModule.kt @@ -62,7 +62,7 @@ internal class AppModule( @ApplicationScope @Provides - fun providePurchasesCache(sharedPreferences: SharedPreferences): PurchasesCache { + fun providePurchasesCache(sharedPreferences: SharedPreferencesCache): PurchasesCache { return PurchasesCache(sharedPreferences) } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QonversionMappingAdapters.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QonversionMappingAdapters.kt index 47249f8f..d3bea9fa 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QonversionMappingAdapters.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QonversionMappingAdapters.kt @@ -139,6 +139,22 @@ internal class QPermissionsAdapter { } } +internal class QPurchaseOptions { + @ToJson + private fun toJson(permissions: Map): List { + return permissions.values.toList() + } + + @FromJson + fun fromJson(permissions: List): Map { + val result = mutableMapOf() + permissions.forEach { + result[it.permissionID] = it + } + return result + } +} + internal class QExperimentGroupTypeAdapter { @ToJson private fun toJson(enum: QExperimentGroupType): String { diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/Inapp.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/Inapp.kt index c483fa1b..66dc7aab 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/Inapp.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/Inapp.kt @@ -15,5 +15,6 @@ internal data class PurchaseDetails( @Json(name = "transaction_id") val transactionId: String, @Json(name = "original_transaction_id") val originalTransactionId: String, @Json(name = "product") val storeProductId: String, - @Json(name = "product_id") val qProductId: String + @Json(name = "product_id") val qProductId: String, + @Json(name = "context_keys") val contextKeys: List?, ) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternal.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternal.kt index f098aa85..0fe83d64 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternal.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternal.kt @@ -1,34 +1,39 @@ package com.qonversion.android.sdk.internal.dto.purchase import com.qonversion.android.sdk.dto.QPurchaseModel +import com.qonversion.android.sdk.dto.QPurchaseOptions import com.qonversion.android.sdk.dto.QPurchaseUpdateModel import com.qonversion.android.sdk.dto.QPurchaseUpdatePolicy import com.qonversion.android.sdk.dto.products.QProduct internal open class PurchaseModelInternal( val productId: String, - val offerId: String?, - val applyOffer: Boolean, val oldProductId: String?, val updatePolicy: QPurchaseUpdatePolicy?, + val options: QPurchaseOptions ) { constructor(purchaseModel: QPurchaseModel) : this( purchaseModel.productId, - purchaseModel.offerId, - purchaseModel.applyOffer, null, null, + QPurchaseOptions(offerId = purchaseModel.offerId, applyOffer = purchaseModel.applyOffer) + ) + + constructor(product: QProduct, options: QPurchaseOptions) : this( + product.qonversionID, + options.oldProduct?.qonversionID, + options.updatePolicy, + options ) constructor(purchaseModel: QPurchaseUpdateModel) : this( purchaseModel.productId, - purchaseModel.offerId, - purchaseModel.applyOffer, purchaseModel.oldProductId, purchaseModel.updatePolicy, + QPurchaseOptions(offerId = purchaseModel.offerId, applyOffer = purchaseModel.applyOffer) ) fun enrich(product: QProduct, oldProduct: QProduct?) = PurchaseModelInternalEnriched( - productId, product, offerId, applyOffer, oldProductId, oldProduct, updatePolicy + productId, product, oldProduct, updatePolicy, options ) } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternalEnriched.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternalEnriched.kt index 6218e874..5a9d1bf7 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternalEnriched.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternalEnriched.kt @@ -1,29 +1,25 @@ package com.qonversion.android.sdk.internal.dto.purchase +import com.qonversion.android.sdk.dto.QPurchaseOptions import com.qonversion.android.sdk.dto.QPurchaseUpdatePolicy import com.qonversion.android.sdk.dto.products.QProduct internal class PurchaseModelInternalEnriched( productId: String, val product: QProduct, - offerId: String?, - applyOffer: Boolean, - oldProductId: String?, val oldProduct: QProduct?, updatePolicy: QPurchaseUpdatePolicy?, -) : PurchaseModelInternal(productId, offerId, applyOffer, oldProductId, updatePolicy) { + options: QPurchaseOptions +) : PurchaseModelInternal(productId, oldProduct?.qonversionID, updatePolicy, options) { constructor( purchaseModel: PurchaseModelInternal, - product: QProduct, - oldProduct: QProduct? + product: QProduct ) : this( purchaseModel.productId, product, - purchaseModel.offerId, - purchaseModel.applyOffer, - purchaseModel.oldProductId, - oldProduct, - purchaseModel.updatePolicy + purchaseModel.options.oldProduct, + purchaseModel.updatePolicy, + purchaseModel.options ) } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/purchase/Purchase.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/purchase/Purchase.kt index d5a7c468..46c252c4 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/purchase/Purchase.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/purchase/Purchase.kt @@ -9,4 +9,5 @@ internal data class Purchase( val originalOrderId: String, val purchaseTime: Long, val purchaseToken: String, + val contextKeys: List?, ) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/repository/DefaultRepository.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/repository/DefaultRepository.kt index 56ec9295..eda39d74 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/repository/DefaultRepository.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/repository/DefaultRepository.kt @@ -598,7 +598,8 @@ internal class DefaultRepository internal constructor( purchase.orderId, purchase.originalOrderId, purchase.storeProductId ?: "", - qProductId ?: "" + qProductId ?: "", + purchase.contextKeys ) } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/PurchasesCache.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/PurchasesCache.kt index e28342b2..b6185528 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/PurchasesCache.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/PurchasesCache.kt @@ -1,6 +1,6 @@ package com.qonversion.android.sdk.internal.storage -import android.content.SharedPreferences +import com.qonversion.android.sdk.dto.QPurchaseOptions import com.qonversion.android.sdk.internal.purchase.Purchase import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi @@ -9,15 +9,22 @@ import java.io.IOException import java.lang.reflect.Type internal class PurchasesCache( - private val preferences: SharedPreferences + private val preferences: SharedPreferencesCache ) { private val moshi = Moshi.Builder().build() private val collectionPurchaseType: Type = Types.newParameterizedType( Set::class.java, Purchase::class.java ) - private val jsonAdapter: JsonAdapter> = + private val collectionPurchaseOptionsType: Type = Types.newParameterizedType( + Map::class.java, + String::class.java, + QPurchaseOptions::class.java + ) + private val purchasesJsonAdapter: JsonAdapter> = moshi.adapter(collectionPurchaseType) + private val purchasesOptionsJsonAdapter: JsonAdapter> = + moshi.adapter(collectionPurchaseOptionsType) fun savePurchase(purchase: Purchase) { val purchases = loadPurchases().toMutableSet() @@ -37,7 +44,7 @@ internal class PurchasesCache( return setOf() } return try { - val purchases: Set? = jsonAdapter.fromJson(json) + val purchases: Set? = purchasesJsonAdapter.fromJson(json) purchases ?: setOf() } catch (e: IOException) { setOf() @@ -51,12 +58,27 @@ internal class PurchasesCache( savePurchasesAsJson(purchases) } + fun saveProcessingPurchasesOptions(options: Map?) { + if (options.isNullOrEmpty()) { + return + } + + preferences.putObject(PURCHASE_OPTIONS_KEY, options, purchasesOptionsJsonAdapter) + } + + fun loadProcessingPurchasesOptions(): Map { + val purchaseOptions = preferences.getObject(PURCHASE_OPTIONS_KEY, purchasesOptionsJsonAdapter) ?: emptyMap() + + return purchaseOptions + } + private fun savePurchasesAsJson(purchases: MutableSet) { - val jsonStr: String = jsonAdapter.toJson(purchases) - preferences.edit().putString(PURCHASE_KEY, jsonStr).apply() + val jsonStr: String = purchasesJsonAdapter.toJson(purchases) + preferences.putString(PURCHASE_KEY, jsonStr) } companion object { + private const val PURCHASE_OPTIONS_KEY = "purchase_options" private const val PURCHASE_KEY = "purchase" private const val MAX_PURCHASES_NUMBER = 5 private const val MAX_OLD_PURCHASES_NUMBER = 1 From 91c348189e6e3674ce7359e202b84c7ab52a6da6 Mon Sep 17 00:00:00 2001 From: Surik Date: Wed, 14 Aug 2024 15:27:07 +0400 Subject: [PATCH 02/11] Fixed warnings --- config/detekt/baseline.xml | 15 ++++++++++++--- .../android/sdk/dto/QPurchaseOptions.kt | 1 - .../android/sdk/internal/QProductCenterManager.kt | 10 ++++++---- .../android/sdk/internal/QonversionInternal.kt | 10 ++++++---- .../sdk/internal/converter/PurchaseConverter.kt | 5 ++++- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 53d16f42..5e90e28a 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -33,7 +33,6 @@ LongParameterList:IBillingClientWrapper.kt$IBillingClientWrapper$( activity: Activity, product: QProduct, offerId: String?, applyOffer: Boolean, updatePurchaseInfo: UpdatePurchaseInfo?, onFailed: (error: BillingError) -> Unit ) LongParameterList:ManagersModule.kt$ManagersModule$( appContext: Application, repository: QRepository, propertiesStorage: UserPropertiesStorage, incrementalDelayCalculator: IncrementalDelayCalculator, appStateProvider: AppStateProvider, logger: Logger ) LongParameterList:ManagersModule.kt$ManagersModule$( repository: QRepository, preferences: SharedPreferences, eventMapper: AutomationsEventMapper, appContext: Application, activityProvider: ActivityProvider, appStateProvider: AppStateProvider ) - LongParameterList:PurchaseModelInternalEnriched.kt$PurchaseModelInternalEnriched$( productId: String, val product: QProduct, offerId: String?, applyOffer: Boolean, oldProductId: String?, val oldProduct: QProduct?, updatePolicy: QPurchaseUpdatePolicy?, ) LongParameterList:QonversionBillingService.kt$QonversionBillingService$( activity: Activity, product: QProduct, offerId: String?, applyOffer: Boolean, oldProduct: QProduct, updatePolicy: QPurchaseUpdatePolicy? ) LongParameterList:QonversionBillingService.kt$QonversionBillingService$( private val mainHandler: Handler, private val purchasesListener: PurchasesListener, private val logger: Logger, private val isAnalyticsMode: Boolean, private val billingClientHolder: BillingClientHolder, private val billingClientWrapper: BillingClientWrapper, private val legacyBillingClientWrapper: LegacyBillingClientWrapper ) LongParameterList:RepositoryModule.kt$RepositoryModule$( retrofit: Retrofit, environmentProvider: EnvironmentProvider, config: InternalConfig, logger: Logger, apiErrorMapper: ApiErrorMapper, sharedPreferences: SharedPreferences, delayCalculator: IncrementalDelayCalculator ) @@ -90,6 +89,8 @@ MaxLineLength:AutomationsEventMapperTest.kt$AutomationsEventMapperTest.GetEventFromRemoteMessage$"{\"name\": \"subscription_started\", \"happened\": $timeInSec}" to AutomationsEventType.SubscriptionStarted MaxLineLength:AutomationsEventMapperTest.kt$AutomationsEventMapperTest.GetEventFromRemoteMessage$"{\"name\": \"subscription_upgraded\", \"happened\": $timeInSec}" to AutomationsEventType.SubscriptionUpgraded MaxLineLength:AutomationsEventMapperTest.kt$AutomationsEventMapperTest.GetEventFromRemoteMessage$"{\"name\": \"trial_billing_retry_entered\", \"happened\": $timeInSec}" to AutomationsEventType.TrialBillingRetry + MaxLineLength:GooglePurchaseConverter.kt$GooglePurchaseConverter$override + MaxLineLength:LaunchResultCacheWrapperTest.kt$LaunchResultCacheWrapperTest$cacheWrapper = LaunchResultCacheWrapper(mockMoshi, mockPrefsCache, mockCacheConfigProvider, mockQFallbacksService) MaxLineLength:OutagerIntegrationTest.kt$OutagerIntegrationTest$"lgeigljfpmeoddkcebkcepjc.AO-J1Oy305qZj99jXTPEVBN8UZGoYAtjDLj4uTjRQvUFaG0vie-nr6VBlN0qnNDMU8eJR-sI7o3CwQyMOEHKl8eJsoQ86KSFzxKBR07PSpHLI_o7agXhNKY" MaxLineLength:OutagerIntegrationTest.kt$OutagerIntegrationTest$purchaseToken = "lgeigljfpmeoddkcebkcepjc.AO-J1Oy305qZj99jXTPEVBN8UZGoYAtjDLj4uTjRQvUFaG0vie-nr6VBlN0qnNDMU8eJR-sI7o3CwQyMOEHKl8eJsoQ86KSFzxKBR07PSpHLI_o7agXhNKY" MaxLineLength:OutagerIntegrationTest.kt$OutagerIntegrationTest$val token = "dt70kovLQdKymNnhIY6I94:APA91bGfg6m108VFio2ZdgLR6U0B2PtqAn0hIPVU7M4jKklkMxqDUrjoThpX_K60M7CfH8IVZqtku31ei2hmjdJZDfm-bdAl7uxLDWFU8yVcA6-3wBMn3nsYmUrhYWom-qgGC7yIUYzR" @@ -101,12 +102,15 @@ MaxLineLength:QAutomationsManager.kt$QAutomationsManager$"To override default animation, please, provide an activity context to AutomationsDelegate.contextForScreenIntent" MaxLineLength:QAutomationsManager.kt$QAutomationsManager$getScreenTransactionAnimations(screenPresentationConfig.presentationStyle) MaxLineLength:QEntitlementsUpdateListener.kt$QEntitlementsUpdateListener$* + MaxLineLength:QProductCenterManager.kt$QProductCenterManager$val oldProduct: QProduct? = purchaseModel.options.oldProduct ?: getProductForPurchase(purchaseModel.oldProductId, products) MaxLineLength:QProductCenterManagerTest.kt$QProductCenterManagerTest${ Assert.assertEquals("Wrong installDate value", installDate.milliSecondsToSeconds(), installDateSlot.captured) } MaxLineLength:QProductCenterManagerTest.kt$QProductCenterManagerTest${ Assert.assertEquals("Wrong purchaseToken value", purchaseToken, entityPurchaseSlot.captured.purchaseToken) } MaxLineLength:QProductStoreDetails.kt$QProductStoreDetails$basePlanSubscriptionOfferDetails?.basePlan?.recurrenceMode == QProductPricingPhase.RecurrenceMode.NonRecurring MaxLineLength:QRemoteConfigManager.kt$QRemoteConfigManager.<no name provided>$val remoteConfigs = baseRemoteConfigList.remoteConfigs.filter { contextKeys.contains(it.source.contextKey) }.toMutableList() MaxLineLength:QUserPropertiesManagerTest.kt$QUserPropertiesManagerTest$fun MaxLineLength:Qonversion.kt$Qonversion$* + MaxLineLength:Qonversion.kt$Qonversion$@Deprecated("Use new purchase() method", replaceWith = ReplaceWith("purchase(context: Activity, product: QProduct, options: QPurchaseOptions, callback: QonversionEntitlementsCallback)")) + MaxLineLength:Qonversion.kt$Qonversion$@Deprecated("Use new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context: Activity, product: QProduct, options: QPurchaseOptions, callback: QonversionEntitlementsCallback)")) MaxLineLength:QonversionBillingService.kt$QonversionBillingService$"updatePurchase() -> Purchase was found successfully for store product: ${purchaseHistoryRecord.productId}" MaxLineLength:QonversionBillingService.kt$QonversionBillingService$logger.debug("queryPurchaseHistoryAsync() -> purchase history for $productType is retrieved ${record.getDescription()}") MaxLineLength:QonversionConfig.kt$QonversionConfig.Builder$* @@ -126,6 +130,8 @@ MaxLineLength:util.kt$Util.Companion$"\"offerings\":[{\"id\":\"main\",\"tag\":1,\"products\":[{\"id\":\"in_app\",\"store_id\":\"qonversion_inapp_consumable\",\"type\":2},{\"id\":\"main\",\"store_id\":\"qonversion_subs_weekly\",\"type\":0,\"duration\":0}]" MaxLineLength:util.kt$Util.Companion$"\"permissions\":[{\"id\":\"standart\",\"associated_product\":\"in_app\",\"renew_state\":-1,\"started_timestamp\":1612880300,\"source\":\"playstore\",\"active\":1},{\"id\":\"Test Permission\",\"associated_product\":\"in_app\",\"renew_state\":-1,\"started_timestamp\":1612880300,\"source\":\"appstore\",\"active\":1}],\"user_products\":[{\"id\":\"in_app\",\"store_id\":\"qonversion_inapp_consumable\",\"type\":2}]," MaxLineLength:utils.kt$"ProductId: ${this.productId}; PurchaseTime: ${this.purchaseTime.convertLongToTime()}; PurchaseToken: ${this.purchaseToken}" + MaximumLineLength:com.qonversion.android.sdk.Qonversion.kt:133 + MaximumLineLength:com.qonversion.android.sdk.Qonversion.kt:87 MaximumLineLength:com.qonversion.android.sdk.automations.internal.AutomationsEventMapperTest.kt:105 MaximumLineLength:com.qonversion.android.sdk.automations.internal.AutomationsEventMapperTest.kt:106 MaximumLineLength:com.qonversion.android.sdk.automations.internal.AutomationsEventMapperTest.kt:107 @@ -145,6 +151,7 @@ MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:370 MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:429 MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:90 + MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManager.kt:341 MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:147 MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:148 MaximumLineLength:com.qonversion.android.sdk.internal.QRemoteConfigManager.kt:225 @@ -159,7 +166,9 @@ MaximumLineLength:com.qonversion.android.sdk.internal.billing.QonversionBillingService.kt:253 MaximumLineLength:com.qonversion.android.sdk.internal.billing.QonversionBillingService.kt:371 MaximumLineLength:com.qonversion.android.sdk.internal.billing.utils.kt:22 + MaximumLineLength:com.qonversion.android.sdk.internal.converter.GooglePurchaseConverter.kt:17 MaximumLineLength:com.qonversion.android.sdk.internal.errors.kt:33 + MaximumLineLength:com.qonversion.android.sdk.internal.storage.LaunchResultCacheWrapperTest.kt:29 MaximumLineLength:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:166 MaximumLineLength:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:188 MaximumLineLength:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:21 @@ -218,7 +227,7 @@ NoWildcardImports:com.qonversion.android.sdk.internal.billing.QonversionBillingService.kt:5 NoWildcardImports:com.qonversion.android.sdk.internal.services.QUserInfoServiceTest.kt:11 NoWildcardImports:com.qonversion.android.sdk.internal.services.QUserInfoServiceTest.kt:5 - NoWildcardImports:com.qonversion.android.sdk.internal.storage.LaunchResultCacheWrapperTest.kt:7 + NoWildcardImports:com.qonversion.android.sdk.internal.storage.LaunchResultCacheWrapperTest.kt:8 NoWildcardImports:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:8 NoWildcardImports:com.qonversion.android.sdk.internal.storage.SharedPreferencesCacheTest.kt:5 NoWildcardImports:com.qonversion.android.sdk.internal.storage.util.kt:24 @@ -269,7 +278,6 @@ TooManyFunctions:Cache.kt$Cache TooManyFunctions:DefaultRepository.kt$DefaultRepository : QRepository TooManyFunctions:LaunchResultCacheWrapper.kt$LaunchResultCacheWrapper - TooManyFunctions:LegacyBillingClientWrapper.kt$LegacyBillingClientWrapper : BillingClientWrapperBaseIBillingClientWrapper TooManyFunctions:QAutomationsManager.kt$QAutomationsManager TooManyFunctions:QProductCenterManager.kt$QProductCenterManager : PurchasesListenerUserStateProvider TooManyFunctions:QRemoteConfigManager.kt$QRemoteConfigManager @@ -296,6 +304,7 @@ UnusedPrivateMember:QonversionMappingAdapters.kt$QPermissionsAdapter$@ToJson private fun toJson(permissions: Map<String, QPermission>): List<QPermission> UnusedPrivateMember:QonversionMappingAdapters.kt$QProductRenewStateAdapter$@ToJson private fun toJson(enum: QProductRenewState): Int UnusedPrivateMember:QonversionMappingAdapters.kt$QProductsAdapter$@ToJson private fun toJson(products: Map<String, QProduct>): List<QProduct> + UnusedPrivateMember:QonversionMappingAdapters.kt$QPurchaseOptions$@ToJson private fun toJson(permissions: Map<String, QPermission>): List<QPermission> UnusedPrivateMember:QonversionMappingAdapters.kt$QRemoteConfigListAdapter$@ToJson private fun toJson(remoteConfigList: QRemoteConfigList?): List<QRemoteConfig> UnusedPrivateMember:QonversionMappingAdapters.kt$QRemoteConfigurationSourceAssignmentTypeAdapter$@ToJson private fun toJson(enum: QRemoteConfigurationAssignmentType): String UnusedPrivateMember:QonversionMappingAdapters.kt$QRemoteConfigurationSourceTypeAdapter$@ToJson private fun toJson(enum: QRemoteConfigurationSourceType): String diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt index 0a16498d..9aba49db 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt @@ -18,7 +18,6 @@ class QPurchaseOptions internal constructor ( private var oldProduct: QProduct? = null private var updatePolicy: QPurchaseUpdatePolicy? = null - fun setContextKeys(contextKeys: List): QPurchaseOptions.Builder = apply { this.contextKeys = contextKeys } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt index 22beebf6..52ec252f 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt @@ -287,10 +287,12 @@ internal class QProductCenterManager internal constructor( return launchResultCache.getActualOfferings() } - fun purchase(context: Activity, - product: QProduct, - options: QPurchaseOptions, - callback: QonversionEntitlementsCallback) { + fun purchase( + context: Activity, + product: QProduct, + options: QPurchaseOptions, + callback: QonversionEntitlementsCallback + ) { val purchaseModel = PurchaseModelInternal(product, options) purchaseProduct(context, purchaseModel, callback) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt index b0451f1a..b74f36bd 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt @@ -171,10 +171,12 @@ internal class QonversionInternal( ) } - override fun purchase(context: Activity, - product: QProduct, - options: QPurchaseOptions, - callback: QonversionEntitlementsCallback) { + override fun purchase( + context: Activity, + product: QProduct, + options: QPurchaseOptions, + callback: QonversionEntitlementsCallback + ) { productCenterManager.purchase(context, product, options, callback) } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/PurchaseConverter.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/PurchaseConverter.kt index 7cf8a966..1dbf0154 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/PurchaseConverter.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/PurchaseConverter.kt @@ -6,5 +6,8 @@ import com.qonversion.android.sdk.internal.purchase.Purchase internal interface PurchaseConverter { fun convertPurchase(purchase: com.android.billingclient.api.Purchase, options: QPurchaseOptions?): Purchase - fun convertPurchases(purchases: List, options: Map?): List + fun convertPurchases( + purchases: List, + options: Map? + ): List } From eb6b239d55fec1742892aa44f06f2422e5bf0b04 Mon Sep 17 00:00:00 2001 From: Surik Date: Wed, 14 Aug 2024 19:17:27 +0400 Subject: [PATCH 03/11] Fixed tests --- .../sdk/internal/QProductCenterManager.kt | 4 ++- .../sdk/internal/QProductCenterManagerTest.kt | 5 ++- .../internal/storage/PurchasesCacheTest.kt | 35 ++++--------------- 3 files changed, 14 insertions(+), 30 deletions(-) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt index 52ec252f..e3c5adf6 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.app.Application import android.content.pm.PackageManager import android.os.Build +import androidx.annotation.VisibleForTesting import com.android.billingclient.api.Purchase import com.qonversion.android.sdk.dto.QPurchaseOptions import com.qonversion.android.sdk.dto.entitlements.QEntitlement @@ -369,7 +370,8 @@ internal class QProductCenterManager internal constructor( billingService.purchase(context, purchaseModel) } - private fun actualPurchaseOptions(): Map { + @VisibleForTesting + fun actualPurchaseOptions(): Map { processingPurchaseOptions?.let { return it } diff --git a/sdk/src/test/java/com/qonversion/android/sdk/internal/QProductCenterManagerTest.kt b/sdk/src/test/java/com/qonversion/android/sdk/internal/QProductCenterManagerTest.kt index 07d1d0b0..c18f6bba 100644 --- a/sdk/src/test/java/com/qonversion/android/sdk/internal/QProductCenterManagerTest.kt +++ b/sdk/src/test/java/com/qonversion/android/sdk/internal/QProductCenterManagerTest.kt @@ -111,6 +111,9 @@ internal class QProductCenterManagerTest { @Test fun `handle pending purchases when launching is finished and query purchases completed`() { + val spykProductCenterManager = spyk(productCenterManager, recordPrivateCalls = true) + every { spykProductCenterManager.actualPurchaseOptions() } returns emptyMap() + val purchase = mockPurchase(Purchase.PurchaseState.PURCHASED, false) val purchases = listOf(purchase) every { @@ -135,7 +138,7 @@ internal class QProductCenterManagerTest { every { mockBillingService.consumePurchases(any()) } just Runs - productCenterManager.onAppForeground() + spykProductCenterManager.onAppForeground() verify(exactly = 1) { mockBillingService.queryPurchases(any(), any()) diff --git a/sdk/src/test/java/com/qonversion/android/sdk/internal/storage/PurchasesCacheTest.kt b/sdk/src/test/java/com/qonversion/android/sdk/internal/storage/PurchasesCacheTest.kt index ef1502d0..3642527c 100644 --- a/sdk/src/test/java/com/qonversion/android/sdk/internal/storage/PurchasesCacheTest.kt +++ b/sdk/src/test/java/com/qonversion/android/sdk/internal/storage/PurchasesCacheTest.kt @@ -11,8 +11,7 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test internal class PurchasesCacheTest { - private val mockPrefs: SharedPreferences = mockk(relaxed = true) - private val mockEditor: SharedPreferences.Editor = mockk(relaxed = true) + private val mockPrefs: SharedPreferencesCache = mockk(relaxed = true) private lateinit var purchasesCache: PurchasesCache @@ -24,8 +23,6 @@ internal class PurchasesCacheTest { fun setUp() { clearAllMocks() - mockSharedPreferences() - purchasesCache = PurchasesCache(mockPrefs) } @@ -38,8 +35,7 @@ internal class PurchasesCacheTest { purchasesCache.savePurchase(purchase) verifyOrder { - mockEditor.putString(purchaseKey, onePurchaseStr) - mockEditor.apply() + mockPrefs.putString(purchaseKey, any()) } } @@ -53,8 +49,7 @@ internal class PurchasesCacheTest { purchasesCache.savePurchase(purchase) verifyOrder { - mockEditor.putString(purchaseKey, onePurchaseStr) - mockEditor.apply() + mockPrefs.putString(purchaseKey, onePurchaseStr) } val purchases = purchasesCache.loadPurchases() assertThat(purchases.size).isEqualTo(1) @@ -72,8 +67,7 @@ internal class PurchasesCacheTest { "[${generatePurchaseJson("2")},${generatePurchaseJson("3")},${generatePurchaseJson("4")},${generatePurchaseJson("5")}]" verifyOrder { - mockEditor.putString(purchaseKey, fourNewestPurchasesStr) - mockEditor.apply() + mockPrefs.putString(purchaseKey, fourNewestPurchasesStr) } } } @@ -138,8 +132,7 @@ internal class PurchasesCacheTest { purchasesCache.clearPurchase(purchase) verifyOrder { - mockEditor.putString(purchaseKey, emptyList) - mockEditor.apply() + mockPrefs.putString(purchaseKey, emptyList) } } @@ -151,8 +144,7 @@ internal class PurchasesCacheTest { purchasesCache.clearPurchase(purchase) verifyOrder { - mockEditor.putString(purchaseKey, emptyList) - mockEditor.apply() + mockPrefs.putString(purchaseKey, emptyList) } } } @@ -164,23 +156,10 @@ internal class PurchasesCacheTest { originalOrderId = "GPA.3375-4436-3573-53474$originalOrderId", purchaseTime = 1611323804, purchaseToken = "gfegjilekkmecbonpfjiaakm.AO-J1OxQCaAn0NPlHTh5CoOiXK0p19X7qEymW9SHtssrggp7S9YafjA1oPBPlWO4Ur3W5rtyNJBzIrVoLOb5In0Jxofv4xV_7t1HaUYYd_f8xOBk7nRIY7g", + contextKeys = listOf("test_1", "test_2") ) } - private fun mockSharedPreferences() { - every { - mockEditor.putString(purchaseKey, any()) - } returns mockEditor - - every { - mockPrefs.edit() - } returns mockEditor - - every { - mockEditor.apply() - } just runs - } - private fun generatePurchaseJson(originalOrderId: String = ""): String { return "{\"orderId\":\"GPA.3375-4436-3573-53474\"," + "\"originalOrderId\":\"GPA.3375-4436-3573-53474$originalOrderId\"," + From c50501fc820cbc112aee58360263eb1ae5946d9c Mon Sep 17 00:00:00 2001 From: Surik Date: Thu, 15 Aug 2024 13:21:25 +0400 Subject: [PATCH 04/11] Updated baseline --- config/detekt/baseline.xml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 5e90e28a..d9388f56 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -151,9 +151,9 @@ MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:370 MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:429 MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:90 - MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManager.kt:341 - MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:147 - MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:148 + MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManager.kt:342 + MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:150 + MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:151 MaximumLineLength:com.qonversion.android.sdk.internal.QRemoteConfigManager.kt:225 MaximumLineLength:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:175 MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:286 @@ -169,10 +169,10 @@ MaximumLineLength:com.qonversion.android.sdk.internal.converter.GooglePurchaseConverter.kt:17 MaximumLineLength:com.qonversion.android.sdk.internal.errors.kt:33 MaximumLineLength:com.qonversion.android.sdk.internal.storage.LaunchResultCacheWrapperTest.kt:29 - MaximumLineLength:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:166 - MaximumLineLength:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:188 - MaximumLineLength:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:21 - MaximumLineLength:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:72 + MaximumLineLength:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:158 + MaximumLineLength:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:167 + MaximumLineLength:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:20 + MaximumLineLength:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:67 MaximumLineLength:com.qonversion.android.sdk.internal.storage.SharedPreferencesCacheTest.kt:219 MaximumLineLength:com.qonversion.android.sdk.internal.storage.SharedPreferencesCacheTest.kt:220 MaximumLineLength:com.qonversion.android.sdk.internal.storage.SharedPreferencesCacheTest.kt:221 @@ -202,6 +202,7 @@ NoConsecutiveBlankLines:com.qonversion.android.sdk.automations.internal.QAutomationsManagerTest.kt:396 NoConsecutiveBlankLines:com.qonversion.android.sdk.internal.QAttributionManagerTest.kt:141 NoConsecutiveBlankLines:com.qonversion.android.sdk.internal.requests.ProviderDataRequestTest.kt:18 + NoUnusedImports:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:4 NoUnusedImports:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:5 NoWildcardImports:com.qonversion.android.sdk.automations.internal.AutomationsEventMapperTest.kt:12 NoWildcardImports:com.qonversion.android.sdk.automations.internal.AutomationsEventMapperTest.kt:6 From 84e66e6f83c2e6fb72616393ee8a219c3a46a685 Mon Sep 17 00:00:00 2001 From: Surik Date: Thu, 15 Aug 2024 19:08:15 +0400 Subject: [PATCH 05/11] Added fixes after review --- .../com/qonversion/android/sdk/Qonversion.kt | 25 ++++++-- .../android/sdk/dto/QPurchaseOptions.kt | 59 ++++++++++++++++++- .../android/sdk/dto/products/QProduct.kt | 3 + .../sdk/internal/QProductCenterManager.kt | 42 +++---------- .../sdk/internal/QonversionInternal.kt | 20 ++++++- .../internal/billing/BillingClientWrapper.kt | 4 +- .../internal/billing/IBillingClientWrapper.kt | 2 +- .../billing/LegacyBillingClientWrapper.kt | 2 +- .../billing/QonversionBillingService.kt | 12 ++-- .../internal/dto/QonversionMappingAdapters.kt | 16 ----- .../dto/purchase/PurchaseModelInternal.kt | 8 +-- .../purchase/PurchaseModelInternalEnriched.kt | 4 +- .../sdk/internal/storage/PurchasesCache.kt | 6 +- .../sdk/internal/QProductCenterManagerTest.kt | 4 +- 14 files changed, 126 insertions(+), 81 deletions(-) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt b/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt index 649c2872..635e0437 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt @@ -80,14 +80,15 @@ interface Qonversion { /** * Make a purchase and validate it through server-to-server using Qonversion's Backend * @param context current activity context - * @param purchaseModel necessary information for purchase + * @param product product for purchase + * @param options necessary information for purchase * @param callback - callback that will be called when response is received * @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases) */ - @Deprecated("Use new purchase() method", replaceWith = ReplaceWith("purchase(context: Activity, product: QProduct, options: QPurchaseOptions, callback: QonversionEntitlementsCallback)")) fun purchase( context: Activity, - purchaseModel: QPurchaseModel, + product: QProduct, + options: QPurchaseOptions, callback: QonversionEntitlementsCallback ) @@ -95,14 +96,12 @@ interface Qonversion { * Make a purchase and validate it through server-to-server using Qonversion's Backend * @param context current activity context * @param product product for purchase - * @param options necessary information for purchase * @param callback - callback that will be called when response is received * @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases) */ fun purchase( context: Activity, product: QProduct, - options: QPurchaseOptions, callback: QonversionEntitlementsCallback ) @@ -122,6 +121,20 @@ interface Qonversion { callback: QonversionEntitlementsCallback ) + /** + * Make a purchase and validate it through server-to-server using Qonversion's Backend + * @param context current activity context + * @param purchaseModel necessary information for purchase + * @param callback - callback that will be called when response is received + * @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases) + */ + @Deprecated("Use new purchase() method", replaceWith = ReplaceWith("purchase(context, TODO(\"pass product here\"), callback)")) + fun purchase( + context: Activity, + purchaseModel: QPurchaseModel, + callback: QonversionEntitlementsCallback + ) + /** * Update (upgrade/downgrade) subscription and validate it through server-to-server using Qonversion's Backend * @param context current activity context @@ -130,7 +143,7 @@ interface Qonversion { * @see [Update policy](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) * @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases) */ - @Deprecated("Use new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context: Activity, product: QProduct, options: QPurchaseOptions, callback: QonversionEntitlementsCallback)")) + @Deprecated("Use new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context, TODO(\"pass product here\"), TODO(\"pass purchase options here\"), callback)")) fun updatePurchase( context: Activity, purchaseUpdateModel: QPurchaseUpdateModel, diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt index 9aba49db..69ad9181 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt @@ -1,8 +1,16 @@ package com.qonversion.android.sdk.dto +import com.qonversion.android.sdk.QonversionConfig +import com.qonversion.android.sdk.QonversionConfig.Builder import com.qonversion.android.sdk.dto.products.QProduct +import com.qonversion.android.sdk.dto.products.QProductOfferDetails +import com.qonversion.android.sdk.dto.products.QProductStoreDetails import com.squareup.moshi.JsonClass +/** + * Purchase options that may be used to modify purchase process. + * To create an instance, use the nested [Builder] class. + */ @JsonClass(generateAdapter = true) class QPurchaseOptions internal constructor ( internal val contextKeys: List? = null, @@ -11,6 +19,12 @@ class QPurchaseOptions internal constructor ( internal val oldProduct: QProduct? = null, internal val updatePolicy: QPurchaseUpdatePolicy? = null ) { + /** + * The builder of QPurchaseOptions instance. + * + * This class contains a variety of methods to customize the purchase behavior. + * You can call them sequentially and call [build] finally to get the [QPurchaseOptions] instance. + */ class Builder { private var contextKeys: List? = null private var offerId: String? = null @@ -18,26 +32,69 @@ class QPurchaseOptions internal constructor ( private var oldProduct: QProduct? = null private var updatePolicy: QPurchaseUpdatePolicy? = null + /** + * Set the context keys associated with a purchase. + * + * @param contextKeys context keys for the purchase. + * @return builder instance for chain calls. + */ fun setContextKeys(contextKeys: List): QPurchaseOptions.Builder = apply { this.contextKeys = contextKeys } + /** + * Set the offer Id to the purchase. + * If [offerId] is not specified, then the default offer will be applied. To know how we choose + * the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails]. + * @param offerId context keys for the purchase. + * @return builder instance for chain calls. + */ fun setOfferId(offerId: String): QPurchaseOptions.Builder = apply { this.offerId = offerId } + /** + * Set context keys associated with a purchase. + * + * @param oldProduct context keys for the purchase. + * @return builder instance for chain calls. + */ fun setOldProduct(oldProduct: QProduct): QPurchaseOptions.Builder = apply { this.oldProduct = oldProduct } + /** + * Set the update policy for the purchase. + * If the [updatePolicy] is not provided, then default one + * will be selected - [QPurchaseUpdatePolicy.WithTimeProration]. + * @param updatePolicy update policy for the purchase. + * @return builder instance for chain calls. + */ fun setUpdatePolicy(updatePolicy: QPurchaseUpdatePolicy): QPurchaseOptions.Builder = apply { this.updatePolicy = updatePolicy } - fun removeOffer() = apply { + /** + * Call this function to remove any intro/trial offer from the purchase (use only a bare base plan). + * @return builder instance for chain calls. + */ + fun removeOffer(): QPurchaseOptions.Builder = apply { this.applyOffer = false } + /** + * Set offer for the purchase. + * @param offer concrete offer which you'd like to purchase. + * @return builder instance for chain calls. + */ + fun setOffer(offer: QProductOfferDetails): QPurchaseOptions.Builder = apply { + this.offerId = offer.offerId + } + + /** + * Generate [QPurchaseOptions] instance with all the provided options. + * @return the complete [QPurchaseOptions] instance. + */ fun build(): QPurchaseOptions { return QPurchaseOptions(contextKeys, offerId, applyOffer, oldProduct, updatePolicy) } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/products/QProduct.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/products/QProduct.kt index 6efa3e79..2f332e85 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/dto/products/QProduct.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/products/QProduct.kt @@ -106,6 +106,7 @@ data class QProduct( * To know how we choose the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails]. * @return purchase model to pass to the purchase method. */ + @Deprecated("Use new QPurchaseOptions object instead", replaceWith = ReplaceWith("QPurchaseOptions.Builder().setOfferId(offerId).build()")) @JvmOverloads fun toPurchaseModel(offerId: String? = null): QPurchaseModel { return QPurchaseModel(qonversionID, offerId) @@ -116,6 +117,7 @@ data class QProduct( * @param offer concrete offer which you'd like to purchase. * @return purchase model to pass to the purchase method. */ + @Deprecated("Use new QPurchaseOptions object instead", replaceWith = ReplaceWith("QPurchaseOptions.Builder().setOffer(offer).build()")) fun toPurchaseModel(offer: QProductOfferDetails?): QPurchaseModel { val model = toPurchaseModel(offer?.offerId) // Remove offer for the case when provided offer details are for bare base plan. @@ -134,6 +136,7 @@ data class QProduct( * @param updatePolicy purchase update policy. * @return purchase model to pass to the update purchase method. */ + @Deprecated("Use new QPurchaseOptions object instead", replaceWith = ReplaceWith("QPurchaseOptions.Builder().setOldProduct(TODO(\"pass old product here\")).build()")) @JvmOverloads fun toPurchaseUpdateModel( oldProductId: String, diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt index e3c5adf6..8daae1f2 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt @@ -96,7 +96,9 @@ internal class QProductCenterManager internal constructor( private var converter: PurchaseConverter = GooglePurchaseConverter() - private var processingPurchaseOptions: Map? = null + private val processingPurchaseOptions: MutableMap by lazy { + purchasesCache.loadProcessingPurchasesOptions().toMutableMap() + } @Volatile lateinit var billingService: BillingService @@ -288,17 +290,6 @@ internal class QProductCenterManager internal constructor( return launchResultCache.getActualOfferings() } - fun purchase( - context: Activity, - product: QProduct, - options: QPurchaseOptions, - callback: QonversionEntitlementsCallback - ) { - val purchaseModel = PurchaseModelInternal(product, options) - - purchaseProduct(context, purchaseModel, callback) - } - fun purchaseProduct( context: Activity, purchaseModel: PurchaseModelInternal, @@ -339,7 +330,7 @@ internal class QProductCenterManager internal constructor( callback.onError(QonversionError(QonversionErrorCode.ProductNotFound)) return } - val oldProduct: QProduct? = purchaseModel.options.oldProduct ?: getProductForPurchase(purchaseModel.oldProductId, products) + val oldProduct: QProduct? = purchaseModel.options?.oldProduct ?: getProductForPurchase(purchaseModel.oldProductId, products) val purchaseModelEnriched = purchaseModel.enrich(product, oldProduct) processPurchase(context, purchaseModelEnriched, callback) } @@ -370,28 +361,14 @@ internal class QProductCenterManager internal constructor( billingService.purchase(context, purchaseModel) } - @VisibleForTesting - fun actualPurchaseOptions(): Map { - processingPurchaseOptions?.let { - return it - } - - val cachedPurchaseOptions = purchasesCache.loadProcessingPurchasesOptions() - processingPurchaseOptions = cachedPurchaseOptions - - return cachedPurchaseOptions - } - private fun updatePurchaseOptions(options: QPurchaseOptions?, storeProductId: String?) { storeProductId?.let { productId -> - val actualOptions = actualPurchaseOptions().toMutableMap() options?.let { - actualOptions[productId] = it + processingPurchaseOptions[productId] = it } ?: run { - actualOptions.remove(productId) + processingPurchaseOptions.remove(productId) } - processingPurchaseOptions = actualOptions.toMap() purchasesCache.saveProcessingPurchasesOptions(processingPurchaseOptions) } } @@ -699,8 +676,6 @@ internal class QProductCenterManager internal constructor( processingPurchases = completedPurchases - val processingPurchaseOptions = actualPurchaseOptions() - val purchasesInfo = converter.convertPurchases(completedPurchases, processingPurchaseOptions) val handledPurchasesCallback = @@ -1012,7 +987,6 @@ internal class QProductCenterManager internal constructor( val product: QProduct? = launchResultCache.getActualProducts()?.values?.find { it.storeID == purchase.productId } - val processingPurchaseOptions = actualPurchaseOptions() val currentPurchaseOptions = processingPurchaseOptions[purchase.productId] val purchaseInfo = converter.convertPurchase(purchase, currentPurchaseOptions) repository.purchase( @@ -1037,9 +1011,7 @@ internal class QProductCenterManager internal constructor( override fun onError(error: QonversionError) { storeFailedPurchaseIfNecessary(purchase, purchaseInfo, product) - product?.storeID?.let { - removePurchaseOptions(it) - } + removePurchaseOptions(product?.storeID) if (shouldCalculatePermissionsLocally(error)) { calculatePurchasePermissionsLocally( diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt index b74f36bd..39a25c11 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt @@ -159,6 +159,7 @@ internal class QonversionInternal( }) } + @Deprecated("Use new purchase() method", replaceWith = ReplaceWith("purchase(context, TODO(\"pass product here\"), callback)")) override fun purchase( context: Activity, purchaseModel: QPurchaseModel, @@ -177,7 +178,23 @@ internal class QonversionInternal( options: QPurchaseOptions, callback: QonversionEntitlementsCallback ) { - productCenterManager.purchase(context, product, options, callback) + productCenterManager.purchaseProduct( + context, + PurchaseModelInternal(product, options), + mainEntitlementsCallback(callback) + ) + } + + override fun purchase( + context: Activity, + product: QProduct, + callback: QonversionEntitlementsCallback + ) { + productCenterManager.purchaseProduct( + context, + PurchaseModelInternal(product), + mainEntitlementsCallback(callback) + ) } override fun updatePurchase( @@ -193,6 +210,7 @@ internal class QonversionInternal( ) } + @Deprecated("Use new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context, TODO(\"pass product here\"), TODO(\"pass purchase options here\"), callback)")) override fun updatePurchase( context: Activity, purchaseUpdateModel: QPurchaseUpdateModel, diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/BillingClientWrapper.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/BillingClientWrapper.kt index 14b58231..396e7888 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/BillingClientWrapper.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/BillingClientWrapper.kt @@ -54,7 +54,7 @@ internal class BillingClientWrapper( activity: Activity, product: QProduct, offerId: String?, - applyOffer: Boolean, + applyOffer: Boolean?, updatePurchaseInfo: UpdatePurchaseInfo?, onFailed: (error: BillingError) -> Unit ) { @@ -76,7 +76,7 @@ internal class BillingClientWrapper( val offerDetails: QProductOfferDetails? = when { storeDetails.isInApp -> null - !applyOffer -> { + applyOffer == false -> { storeDetails.basePlanSubscriptionOfferDetails ?: run { fireError("Failed to find base plan offer for Qonversion product ${product.qonversionID}") return diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/IBillingClientWrapper.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/IBillingClientWrapper.kt index 4bae185f..e6be0ba6 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/IBillingClientWrapper.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/IBillingClientWrapper.kt @@ -21,7 +21,7 @@ internal interface IBillingClientWrapper { activity: Activity, product: QProduct, offerId: String?, - applyOffer: Boolean, + applyOffer: Boolean? = true, updatePurchaseInfo: UpdatePurchaseInfo?, onFailed: (error: BillingError) -> Unit ) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/LegacyBillingClientWrapper.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/LegacyBillingClientWrapper.kt index a999ce57..8b061dc8 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/LegacyBillingClientWrapper.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/LegacyBillingClientWrapper.kt @@ -46,7 +46,7 @@ internal class LegacyBillingClientWrapper( activity: Activity, product: QProduct, offerId: String?, // ignored - applyOffer: Boolean, // ignored + applyOffer: Boolean?, // ignored updatePurchaseInfo: UpdatePurchaseInfo?, onFailed: (error: BillingError) -> Unit ) { diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/QonversionBillingService.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/QonversionBillingService.kt index 99c25756..7a9a5655 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/QonversionBillingService.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/QonversionBillingService.kt @@ -103,16 +103,16 @@ internal class QonversionBillingService internal constructor( updatePurchase( activity, purchaseModel.product, - purchaseModel.options.offerId, - purchaseModel.options.applyOffer, + purchaseModel.options?.offerId, + purchaseModel.options?.applyOffer, purchaseModel.oldProduct, purchaseModel.updatePolicy) } else { makePurchase( activity, purchaseModel.product, - purchaseModel.options.offerId, - purchaseModel.options.applyOffer + purchaseModel.options?.offerId, + purchaseModel.options?.applyOffer ) } } @@ -232,7 +232,7 @@ internal class QonversionBillingService internal constructor( activity: Activity, product: QProduct, offerId: String?, - applyOffer: Boolean, + applyOffer: Boolean?, oldProduct: QProduct, updatePolicy: QPurchaseUpdatePolicy? ) { @@ -274,7 +274,7 @@ internal class QonversionBillingService internal constructor( activity: Activity, product: QProduct, offerId: String?, - applyOffer: Boolean, + applyOffer: Boolean?, updatePurchaseInfo: UpdatePurchaseInfo? = null ) { executeOnMainThread { billingSetupError -> diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QonversionMappingAdapters.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QonversionMappingAdapters.kt index d3bea9fa..47249f8f 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QonversionMappingAdapters.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QonversionMappingAdapters.kt @@ -139,22 +139,6 @@ internal class QPermissionsAdapter { } } -internal class QPurchaseOptions { - @ToJson - private fun toJson(permissions: Map): List { - return permissions.values.toList() - } - - @FromJson - fun fromJson(permissions: List): Map { - val result = mutableMapOf() - permissions.forEach { - result[it.permissionID] = it - } - return result - } -} - internal class QExperimentGroupTypeAdapter { @ToJson private fun toJson(enum: QExperimentGroupType): String { diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternal.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternal.kt index 0fe83d64..d4fa4c02 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternal.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternal.kt @@ -10,7 +10,7 @@ internal open class PurchaseModelInternal( val productId: String, val oldProductId: String?, val updatePolicy: QPurchaseUpdatePolicy?, - val options: QPurchaseOptions + val options: QPurchaseOptions? ) { constructor(purchaseModel: QPurchaseModel) : this( purchaseModel.productId, @@ -19,10 +19,10 @@ internal open class PurchaseModelInternal( QPurchaseOptions(offerId = purchaseModel.offerId, applyOffer = purchaseModel.applyOffer) ) - constructor(product: QProduct, options: QPurchaseOptions) : this( + constructor(product: QProduct, options: QPurchaseOptions? = null) : this( product.qonversionID, - options.oldProduct?.qonversionID, - options.updatePolicy, + options?.oldProduct?.qonversionID, + options?.updatePolicy, options ) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternalEnriched.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternalEnriched.kt index 5a9d1bf7..f0ab2a67 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternalEnriched.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternalEnriched.kt @@ -9,7 +9,7 @@ internal class PurchaseModelInternalEnriched( val product: QProduct, val oldProduct: QProduct?, updatePolicy: QPurchaseUpdatePolicy?, - options: QPurchaseOptions + options: QPurchaseOptions? ) : PurchaseModelInternal(productId, oldProduct?.qonversionID, updatePolicy, options) { constructor( @@ -18,7 +18,7 @@ internal class PurchaseModelInternalEnriched( ) : this( purchaseModel.productId, product, - purchaseModel.options.oldProduct, + purchaseModel.options?.oldProduct, purchaseModel.updatePolicy, purchaseModel.options ) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/PurchasesCache.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/PurchasesCache.kt index b6185528..450eb7d7 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/PurchasesCache.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/PurchasesCache.kt @@ -58,11 +58,7 @@ internal class PurchasesCache( savePurchasesAsJson(purchases) } - fun saveProcessingPurchasesOptions(options: Map?) { - if (options.isNullOrEmpty()) { - return - } - + fun saveProcessingPurchasesOptions(options: Map) { preferences.putObject(PURCHASE_OPTIONS_KEY, options, purchasesOptionsJsonAdapter) } diff --git a/sdk/src/test/java/com/qonversion/android/sdk/internal/QProductCenterManagerTest.kt b/sdk/src/test/java/com/qonversion/android/sdk/internal/QProductCenterManagerTest.kt index c18f6bba..619ba9fc 100644 --- a/sdk/src/test/java/com/qonversion/android/sdk/internal/QProductCenterManagerTest.kt +++ b/sdk/src/test/java/com/qonversion/android/sdk/internal/QProductCenterManagerTest.kt @@ -7,6 +7,7 @@ import android.os.Build import com.android.billingclient.api.BillingClient import com.android.billingclient.api.Purchase import com.android.billingclient.api.* +import com.qonversion.android.sdk.dto.QPurchaseOptions import com.qonversion.android.sdk.listeners.QonversionLaunchCallback import com.qonversion.android.sdk.internal.billing.BillingError import com.qonversion.android.sdk.internal.billing.QonversionBillingService @@ -18,6 +19,7 @@ import com.qonversion.android.sdk.internal.repository.QRepository import com.qonversion.android.sdk.internal.services.QUserInfoService import com.qonversion.android.sdk.internal.storage.LaunchResultCacheWrapper import com.qonversion.android.sdk.internal.storage.PurchasesCache +import com.qonversion.android.sdk.mockPrivateField import io.mockk.* import org.junit.Assert import org.junit.Before @@ -112,7 +114,7 @@ internal class QProductCenterManagerTest { @Test fun `handle pending purchases when launching is finished and query purchases completed`() { val spykProductCenterManager = spyk(productCenterManager, recordPrivateCalls = true) - every { spykProductCenterManager.actualPurchaseOptions() } returns emptyMap() + spykProductCenterManager.mockPrivateField("processingPurchaseOptions", emptyMap()) val purchase = mockPurchase(Purchase.PurchaseState.PURCHASED, false) val purchases = listOf(purchase) From 9197d9e54c5efcce0829ee4c815a6e4ebb8ffe15 Mon Sep 17 00:00:00 2001 From: Surik Date: Thu, 15 Aug 2024 19:08:59 +0400 Subject: [PATCH 06/11] Fixed warnings --- config/detekt/baseline.xml | 37 ++++++++++++------- .../android/sdk/dto/QPurchaseOptions.kt | 1 - .../sdk/internal/QProductCenterManager.kt | 1 - 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index d9388f56..e493f0fa 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -30,10 +30,10 @@ FinalNewline:com.qonversion.android.sdk.internal.storage.util.kt:1 FinalNewline:com.qonversion.android.sdk.utils.kt:1 LargeClass:QProductCenterManager.kt$QProductCenterManager : PurchasesListenerUserStateProvider - LongParameterList:IBillingClientWrapper.kt$IBillingClientWrapper$( activity: Activity, product: QProduct, offerId: String?, applyOffer: Boolean, updatePurchaseInfo: UpdatePurchaseInfo?, onFailed: (error: BillingError) -> Unit ) + LongParameterList:IBillingClientWrapper.kt$IBillingClientWrapper$( activity: Activity, product: QProduct, offerId: String?, applyOffer: Boolean? = true, updatePurchaseInfo: UpdatePurchaseInfo?, onFailed: (error: BillingError) -> Unit ) LongParameterList:ManagersModule.kt$ManagersModule$( appContext: Application, repository: QRepository, propertiesStorage: UserPropertiesStorage, incrementalDelayCalculator: IncrementalDelayCalculator, appStateProvider: AppStateProvider, logger: Logger ) LongParameterList:ManagersModule.kt$ManagersModule$( repository: QRepository, preferences: SharedPreferences, eventMapper: AutomationsEventMapper, appContext: Application, activityProvider: ActivityProvider, appStateProvider: AppStateProvider ) - LongParameterList:QonversionBillingService.kt$QonversionBillingService$( activity: Activity, product: QProduct, offerId: String?, applyOffer: Boolean, oldProduct: QProduct, updatePolicy: QPurchaseUpdatePolicy? ) + LongParameterList:QonversionBillingService.kt$QonversionBillingService$( activity: Activity, product: QProduct, offerId: String?, applyOffer: Boolean?, oldProduct: QProduct, updatePolicy: QPurchaseUpdatePolicy? ) LongParameterList:QonversionBillingService.kt$QonversionBillingService$( private val mainHandler: Handler, private val purchasesListener: PurchasesListener, private val logger: Logger, private val isAnalyticsMode: Boolean, private val billingClientHolder: BillingClientHolder, private val billingClientWrapper: BillingClientWrapper, private val legacyBillingClientWrapper: LegacyBillingClientWrapper ) LongParameterList:RepositoryModule.kt$RepositoryModule$( retrofit: Retrofit, environmentProvider: EnvironmentProvider, config: InternalConfig, logger: Logger, apiErrorMapper: ApiErrorMapper, sharedPreferences: SharedPreferences, delayCalculator: IncrementalDelayCalculator ) LongParameterList:RepositoryModule.kt$RepositoryModule$( retrofit: Retrofit, environmentProvider: EnvironmentProvider, config: InternalConfig, logger: Logger, apiErrorMapper: ApiErrorMapper, sharedPreferences: SharedPreferences, delayCalculator: IncrementalDelayCalculator, rateLimiter: RateLimiter ) @@ -102,20 +102,25 @@ MaxLineLength:QAutomationsManager.kt$QAutomationsManager$"To override default animation, please, provide an activity context to AutomationsDelegate.contextForScreenIntent" MaxLineLength:QAutomationsManager.kt$QAutomationsManager$getScreenTransactionAnimations(screenPresentationConfig.presentationStyle) MaxLineLength:QEntitlementsUpdateListener.kt$QEntitlementsUpdateListener$* - MaxLineLength:QProductCenterManager.kt$QProductCenterManager$val oldProduct: QProduct? = purchaseModel.options.oldProduct ?: getProductForPurchase(purchaseModel.oldProductId, products) + MaxLineLength:QProduct.kt$QProduct$@Deprecated("Use new QPurchaseOptions object instead", replaceWith = ReplaceWith("QPurchaseOptions.Builder().setOffer(offer).build()")) + MaxLineLength:QProduct.kt$QProduct$@Deprecated("Use new QPurchaseOptions object instead", replaceWith = ReplaceWith("QPurchaseOptions.Builder().setOfferId(offerId).build()")) + MaxLineLength:QProduct.kt$QProduct$@Deprecated("Use new QPurchaseOptions object instead", replaceWith = ReplaceWith("QPurchaseOptions.Builder().setOldProduct(TODO(\"pass old product here\")).build()")) + MaxLineLength:QProductCenterManager.kt$QProductCenterManager$val oldProduct: QProduct? = purchaseModel.options?.oldProduct ?: getProductForPurchase(purchaseModel.oldProductId, products) MaxLineLength:QProductCenterManagerTest.kt$QProductCenterManagerTest${ Assert.assertEquals("Wrong installDate value", installDate.milliSecondsToSeconds(), installDateSlot.captured) } MaxLineLength:QProductCenterManagerTest.kt$QProductCenterManagerTest${ Assert.assertEquals("Wrong purchaseToken value", purchaseToken, entityPurchaseSlot.captured.purchaseToken) } MaxLineLength:QProductStoreDetails.kt$QProductStoreDetails$basePlanSubscriptionOfferDetails?.basePlan?.recurrenceMode == QProductPricingPhase.RecurrenceMode.NonRecurring MaxLineLength:QRemoteConfigManager.kt$QRemoteConfigManager.<no name provided>$val remoteConfigs = baseRemoteConfigList.remoteConfigs.filter { contextKeys.contains(it.source.contextKey) }.toMutableList() MaxLineLength:QUserPropertiesManagerTest.kt$QUserPropertiesManagerTest$fun MaxLineLength:Qonversion.kt$Qonversion$* - MaxLineLength:Qonversion.kt$Qonversion$@Deprecated("Use new purchase() method", replaceWith = ReplaceWith("purchase(context: Activity, product: QProduct, options: QPurchaseOptions, callback: QonversionEntitlementsCallback)")) - MaxLineLength:Qonversion.kt$Qonversion$@Deprecated("Use new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context: Activity, product: QProduct, options: QPurchaseOptions, callback: QonversionEntitlementsCallback)")) + MaxLineLength:Qonversion.kt$Qonversion$@Deprecated("Use new purchase() method", replaceWith = ReplaceWith("purchase(context, TODO(\"pass product here\"), callback)")) + MaxLineLength:Qonversion.kt$Qonversion$@Deprecated("Use new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context, TODO(\"pass product here\"), TODO(\"pass purchase options here\"), callback)")) MaxLineLength:QonversionBillingService.kt$QonversionBillingService$"updatePurchase() -> Purchase was found successfully for store product: ${purchaseHistoryRecord.productId}" MaxLineLength:QonversionBillingService.kt$QonversionBillingService$logger.debug("queryPurchaseHistoryAsync() -> purchase history for $productType is retrieved ${record.getDescription()}") MaxLineLength:QonversionConfig.kt$QonversionConfig.Builder$* MaxLineLength:QonversionError.kt$QonversionErrorCode$* MaxLineLength:QonversionError.kt$QonversionErrorCode$RemoteConfigurationNotAvailable : QonversionErrorCode + MaxLineLength:QonversionInternal.kt$QonversionInternal$@Deprecated("Use new purchase() method", replaceWith = ReplaceWith("purchase(context, TODO(\"pass product here\"), callback)")) + MaxLineLength:QonversionInternal.kt$QonversionInternal$@Deprecated("Use new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context, TODO(\"pass product here\"), TODO(\"pass purchase options here\"), callback)")) MaxLineLength:QonversionRepositoryIntegrationTest.kt$QonversionRepositoryIntegrationTest$"""HTTP status code=400, data={"message":"Invalid access token received","code":10003,"status":400,"extra":[]}. """ MaxLineLength:QonversionRepositoryIntegrationTest.kt$QonversionRepositoryIntegrationTest$"lcbfeigohklhpdgmpildjabg.AO-J1OyV-EE2bKGqDcRCvqjZ2NI1uHDRuvonRn5RorP6LNsyK7yHK8FaFlXp6bjTEX3-4JvZKtbY_bpquKBfux09Mfkx05M9YGZsfsr5BJk74r719m77Oyo" MaxLineLength:QonversionRepositoryIntegrationTest.kt$QonversionRepositoryIntegrationTest$"lgeigljfpmeoddkcebkcepjc.AO-J1Oy305qZj99jXTPEVBN8UZGoYAtjDLj4uTjRQvUFaG0vie-nr6VBlN0qnNDMU8eJR-sI7o3CwQyMOEHKl8eJsoQ86KSFzxKBR07PSpHLI_o7agXhNKY" @@ -130,8 +135,8 @@ MaxLineLength:util.kt$Util.Companion$"\"offerings\":[{\"id\":\"main\",\"tag\":1,\"products\":[{\"id\":\"in_app\",\"store_id\":\"qonversion_inapp_consumable\",\"type\":2},{\"id\":\"main\",\"store_id\":\"qonversion_subs_weekly\",\"type\":0,\"duration\":0}]" MaxLineLength:util.kt$Util.Companion$"\"permissions\":[{\"id\":\"standart\",\"associated_product\":\"in_app\",\"renew_state\":-1,\"started_timestamp\":1612880300,\"source\":\"playstore\",\"active\":1},{\"id\":\"Test Permission\",\"associated_product\":\"in_app\",\"renew_state\":-1,\"started_timestamp\":1612880300,\"source\":\"appstore\",\"active\":1}],\"user_products\":[{\"id\":\"in_app\",\"store_id\":\"qonversion_inapp_consumable\",\"type\":2}]," MaxLineLength:utils.kt$"ProductId: ${this.productId}; PurchaseTime: ${this.purchaseTime.convertLongToTime()}; PurchaseToken: ${this.purchaseToken}" - MaximumLineLength:com.qonversion.android.sdk.Qonversion.kt:133 - MaximumLineLength:com.qonversion.android.sdk.Qonversion.kt:87 + MaximumLineLength:com.qonversion.android.sdk.Qonversion.kt:131 + MaximumLineLength:com.qonversion.android.sdk.Qonversion.kt:146 MaximumLineLength:com.qonversion.android.sdk.automations.internal.AutomationsEventMapperTest.kt:105 MaximumLineLength:com.qonversion.android.sdk.automations.internal.AutomationsEventMapperTest.kt:106 MaximumLineLength:com.qonversion.android.sdk.automations.internal.AutomationsEventMapperTest.kt:107 @@ -146,16 +151,21 @@ MaximumLineLength:com.qonversion.android.sdk.automations.internal.QAutomationsManager.kt:142 MaximumLineLength:com.qonversion.android.sdk.automations.mvp.ScreenPresenterTest.kt:159 MaximumLineLength:com.qonversion.android.sdk.dto.QonversionError.kt:46 + MaximumLineLength:com.qonversion.android.sdk.dto.products.QProduct.kt:109 + MaximumLineLength:com.qonversion.android.sdk.dto.products.QProduct.kt:120 + MaximumLineLength:com.qonversion.android.sdk.dto.products.QProduct.kt:139 MaximumLineLength:com.qonversion.android.sdk.dto.products.QProductStoreDetails.kt:130 MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:213 MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:370 MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:429 MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:90 - MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManager.kt:342 - MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:150 - MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:151 + MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManager.kt:332 + MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:152 + MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:153 MaximumLineLength:com.qonversion.android.sdk.internal.QRemoteConfigManager.kt:225 MaximumLineLength:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:175 + MaximumLineLength:com.qonversion.android.sdk.internal.QonversionInternal.kt:162 + MaximumLineLength:com.qonversion.android.sdk.internal.QonversionInternal.kt:213 MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:286 MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:355 MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:684 @@ -219,8 +229,8 @@ NoWildcardImports:com.qonversion.android.sdk.internal.QHandledPurchasesCacheTest.kt:4 NoWildcardImports:com.qonversion.android.sdk.internal.QIdentityManagerTest.kt:7 NoWildcardImports:com.qonversion.android.sdk.internal.QIdentityManagerTest.kt:9 - NoWildcardImports:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:21 - NoWildcardImports:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:29 + NoWildcardImports:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:23 + NoWildcardImports:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:31 NoWildcardImports:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:9 NoWildcardImports:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:18 NoWildcardImports:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:19 @@ -234,7 +244,7 @@ NoWildcardImports:com.qonversion.android.sdk.internal.storage.util.kt:24 NoWildcardImports:com.qonversion.android.sdk.utils.kt:4 ReturnCount:AutomationsEventMapper.kt$AutomationsEventMapper$fun getEventFromRemoteMessage(messageData: Map<String, String>): AutomationsEvent? - ReturnCount:BillingClientWrapper.kt$BillingClientWrapper$override fun makePurchase( activity: Activity, product: QProduct, offerId: String?, applyOffer: Boolean, updatePurchaseInfo: UpdatePurchaseInfo?, onFailed: (error: BillingError) -> Unit ) + ReturnCount:BillingClientWrapper.kt$BillingClientWrapper$override fun makePurchase( activity: Activity, product: QProduct, offerId: String?, applyOffer: Boolean?, updatePurchaseInfo: UpdatePurchaseInfo?, onFailed: (error: BillingError) -> Unit ) ReturnCount:ExceptionHandler.kt$ExceptionHandler$private fun isQonversionException(exception: Throwable): Boolean ReturnCount:QExceptionManager.kt$QExceptionManager$private fun getContentOfCrashReport(filename: String): CrashRequest.ExceptionInfo? ReturnCount:QProductCenterManager.kt$QProductCenterManager$@Synchronized private fun executeProductsBlocks(loadStoreProductsError: QonversionError? = null) @@ -305,7 +315,6 @@ UnusedPrivateMember:QonversionMappingAdapters.kt$QPermissionsAdapter$@ToJson private fun toJson(permissions: Map<String, QPermission>): List<QPermission> UnusedPrivateMember:QonversionMappingAdapters.kt$QProductRenewStateAdapter$@ToJson private fun toJson(enum: QProductRenewState): Int UnusedPrivateMember:QonversionMappingAdapters.kt$QProductsAdapter$@ToJson private fun toJson(products: Map<String, QProduct>): List<QProduct> - UnusedPrivateMember:QonversionMappingAdapters.kt$QPurchaseOptions$@ToJson private fun toJson(permissions: Map<String, QPermission>): List<QPermission> UnusedPrivateMember:QonversionMappingAdapters.kt$QRemoteConfigListAdapter$@ToJson private fun toJson(remoteConfigList: QRemoteConfigList?): List<QRemoteConfig> UnusedPrivateMember:QonversionMappingAdapters.kt$QRemoteConfigurationSourceAssignmentTypeAdapter$@ToJson private fun toJson(enum: QRemoteConfigurationAssignmentType): String UnusedPrivateMember:QonversionMappingAdapters.kt$QRemoteConfigurationSourceTypeAdapter$@ToJson private fun toJson(enum: QRemoteConfigurationSourceType): String diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt index 69ad9181..a1a12c4f 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt @@ -1,6 +1,5 @@ package com.qonversion.android.sdk.dto -import com.qonversion.android.sdk.QonversionConfig import com.qonversion.android.sdk.QonversionConfig.Builder import com.qonversion.android.sdk.dto.products.QProduct import com.qonversion.android.sdk.dto.products.QProductOfferDetails diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt index 8daae1f2..30b3f08f 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt @@ -4,7 +4,6 @@ import android.app.Activity import android.app.Application import android.content.pm.PackageManager import android.os.Build -import androidx.annotation.VisibleForTesting import com.android.billingclient.api.Purchase import com.qonversion.android.sdk.dto.QPurchaseOptions import com.qonversion.android.sdk.dto.entitlements.QEntitlement From 800960f7f39f48df2e5cd8bf3b71accf22c1451e Mon Sep 17 00:00:00 2001 From: Surik Sarkisyan Date: Mon, 19 Aug 2024 01:28:23 +0400 Subject: [PATCH 07/11] Apply suggestions from code review Co-authored-by: Kamo Spertsyan --- sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt | 4 ++-- .../java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt b/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt index 635e0437..7389a7dd 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt @@ -128,7 +128,7 @@ interface Qonversion { * @param callback - callback that will be called when response is received * @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases) */ - @Deprecated("Use new purchase() method", replaceWith = ReplaceWith("purchase(context, TODO(\"pass product here\"), callback)")) + @Deprecated("Use the new purchase() method", replaceWith = ReplaceWith("purchase(context, TODO(\"pass product here\"), callback)")) fun purchase( context: Activity, purchaseModel: QPurchaseModel, @@ -143,7 +143,7 @@ interface Qonversion { * @see [Update policy](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) * @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases) */ - @Deprecated("Use new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context, TODO(\"pass product here\"), TODO(\"pass purchase options here\"), callback)")) + @Deprecated("Use the new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context, TODO(\"pass product here\"), TODO(\"pass purchase options here\"), callback)")) fun updatePurchase( context: Activity, purchaseUpdateModel: QPurchaseUpdateModel, diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt index a1a12c4f..bc98db44 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt @@ -42,7 +42,7 @@ class QPurchaseOptions internal constructor ( } /** - * Set the offer Id to the purchase. + * Set the offer id to the purchase. * If [offerId] is not specified, then the default offer will be applied. To know how we choose * the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails]. * @param offerId context keys for the purchase. From 2ef6427ed606ac92d87a2f72c28f31b44201cf82 Mon Sep 17 00:00:00 2001 From: Surik Date: Mon, 19 Aug 2024 01:28:36 +0400 Subject: [PATCH 08/11] Added fixes after review --- .../android/sdk/dto/QPurchaseOptions.kt | 39 ++++++++++--------- .../sdk/internal/QonversionInternal.kt | 4 +- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt index a1a12c4f..efcfed96 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt @@ -41,21 +41,11 @@ class QPurchaseOptions internal constructor ( this.contextKeys = contextKeys } - /** - * Set the offer Id to the purchase. - * If [offerId] is not specified, then the default offer will be applied. To know how we choose - * the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails]. - * @param offerId context keys for the purchase. - * @return builder instance for chain calls. - */ - fun setOfferId(offerId: String): QPurchaseOptions.Builder = apply { - this.offerId = offerId - } - /** * Set context keys associated with a purchase. * - * @param oldProduct context keys for the purchase. + * @param oldProduct Qonversion product from which the upgrade/downgrade + * will be initialized. * @return builder instance for chain calls. */ fun setOldProduct(oldProduct: QProduct): QPurchaseOptions.Builder = apply { @@ -74,20 +64,31 @@ class QPurchaseOptions internal constructor ( } /** - * Call this function to remove any intro/trial offer from the purchase (use only a bare base plan). + * Set offer for the purchase. + * @param offer concrete offer which you'd like to purchase. * @return builder instance for chain calls. */ - fun removeOffer(): QPurchaseOptions.Builder = apply { - this.applyOffer = false + fun setOffer(offer: QProductOfferDetails): QPurchaseOptions.Builder = apply { + this.offerId = offer.offerId } /** - * Set offer for the purchase. - * @param offer concrete offer which you'd like to purchase. + * Set the offer Id to the purchase. + * If [offerId] is not specified, then the default offer will be applied. To know how we choose + * the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails]. + * @param offerId concrete offer Id which you'd like to purchase. * @return builder instance for chain calls. */ - fun setOffer(offer: QProductOfferDetails): QPurchaseOptions.Builder = apply { - this.offerId = offer.offerId + fun setOfferId(offerId: String): QPurchaseOptions.Builder = apply { + this.offerId = offerId + } + + /** + * Call this function to remove any intro/trial offer from the purchase (use only a bare base plan). + * @return builder instance for chain calls. + */ + fun removeOffer(): QPurchaseOptions.Builder = apply { + this.applyOffer = false } /** diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt index 39a25c11..3862bdb4 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt @@ -158,8 +158,7 @@ internal class QonversionInternal( } }) } - - @Deprecated("Use new purchase() method", replaceWith = ReplaceWith("purchase(context, TODO(\"pass product here\"), callback)")) + override fun purchase( context: Activity, purchaseModel: QPurchaseModel, @@ -210,7 +209,6 @@ internal class QonversionInternal( ) } - @Deprecated("Use new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context, TODO(\"pass product here\"), TODO(\"pass purchase options here\"), callback)")) override fun updatePurchase( context: Activity, purchaseUpdateModel: QPurchaseUpdateModel, From 3aa24162449680327079a1588254b86ff5da85e1 Mon Sep 17 00:00:00 2001 From: Surik Date: Mon, 19 Aug 2024 01:33:44 +0400 Subject: [PATCH 09/11] Fixed warnings --- config/detekt/baseline.xml | 8 ++------ .../qonversion/android/sdk/internal/QonversionInternal.kt | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index e493f0fa..4c50fef9 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -112,15 +112,13 @@ MaxLineLength:QRemoteConfigManager.kt$QRemoteConfigManager.<no name provided>$val remoteConfigs = baseRemoteConfigList.remoteConfigs.filter { contextKeys.contains(it.source.contextKey) }.toMutableList() MaxLineLength:QUserPropertiesManagerTest.kt$QUserPropertiesManagerTest$fun MaxLineLength:Qonversion.kt$Qonversion$* - MaxLineLength:Qonversion.kt$Qonversion$@Deprecated("Use new purchase() method", replaceWith = ReplaceWith("purchase(context, TODO(\"pass product here\"), callback)")) - MaxLineLength:Qonversion.kt$Qonversion$@Deprecated("Use new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context, TODO(\"pass product here\"), TODO(\"pass purchase options here\"), callback)")) + MaxLineLength:Qonversion.kt$Qonversion$@Deprecated("Use the new purchase() method", replaceWith = ReplaceWith("purchase(context, TODO(\"pass product here\"), callback)")) + MaxLineLength:Qonversion.kt$Qonversion$@Deprecated("Use the new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context, TODO(\"pass product here\"), TODO(\"pass purchase options here\"), callback)")) MaxLineLength:QonversionBillingService.kt$QonversionBillingService$"updatePurchase() -> Purchase was found successfully for store product: ${purchaseHistoryRecord.productId}" MaxLineLength:QonversionBillingService.kt$QonversionBillingService$logger.debug("queryPurchaseHistoryAsync() -> purchase history for $productType is retrieved ${record.getDescription()}") MaxLineLength:QonversionConfig.kt$QonversionConfig.Builder$* MaxLineLength:QonversionError.kt$QonversionErrorCode$* MaxLineLength:QonversionError.kt$QonversionErrorCode$RemoteConfigurationNotAvailable : QonversionErrorCode - MaxLineLength:QonversionInternal.kt$QonversionInternal$@Deprecated("Use new purchase() method", replaceWith = ReplaceWith("purchase(context, TODO(\"pass product here\"), callback)")) - MaxLineLength:QonversionInternal.kt$QonversionInternal$@Deprecated("Use new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context, TODO(\"pass product here\"), TODO(\"pass purchase options here\"), callback)")) MaxLineLength:QonversionRepositoryIntegrationTest.kt$QonversionRepositoryIntegrationTest$"""HTTP status code=400, data={"message":"Invalid access token received","code":10003,"status":400,"extra":[]}. """ MaxLineLength:QonversionRepositoryIntegrationTest.kt$QonversionRepositoryIntegrationTest$"lcbfeigohklhpdgmpildjabg.AO-J1OyV-EE2bKGqDcRCvqjZ2NI1uHDRuvonRn5RorP6LNsyK7yHK8FaFlXp6bjTEX3-4JvZKtbY_bpquKBfux09Mfkx05M9YGZsfsr5BJk74r719m77Oyo" MaxLineLength:QonversionRepositoryIntegrationTest.kt$QonversionRepositoryIntegrationTest$"lgeigljfpmeoddkcebkcepjc.AO-J1Oy305qZj99jXTPEVBN8UZGoYAtjDLj4uTjRQvUFaG0vie-nr6VBlN0qnNDMU8eJR-sI7o3CwQyMOEHKl8eJsoQ86KSFzxKBR07PSpHLI_o7agXhNKY" @@ -164,8 +162,6 @@ MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:153 MaximumLineLength:com.qonversion.android.sdk.internal.QRemoteConfigManager.kt:225 MaximumLineLength:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:175 - MaximumLineLength:com.qonversion.android.sdk.internal.QonversionInternal.kt:162 - MaximumLineLength:com.qonversion.android.sdk.internal.QonversionInternal.kt:213 MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:286 MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:355 MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:684 diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt index 3862bdb4..f0b6b97d 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt @@ -158,7 +158,7 @@ internal class QonversionInternal( } }) } - + override fun purchase( context: Activity, purchaseModel: QPurchaseModel, From 2cfb99f65256583f87f0c0e19b4b2f6d28b00f93 Mon Sep 17 00:00:00 2001 From: Surik Date: Mon, 19 Aug 2024 11:44:12 +0400 Subject: [PATCH 10/11] Fixed sample --- .../qonversion/android/app/HomeFragment.kt | 39 +++++++------------ 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/qonversion/android/app/HomeFragment.kt b/app/src/main/java/com/qonversion/android/app/HomeFragment.kt index d8cd7797..8f71fa40 100644 --- a/app/src/main/java/com/qonversion/android/app/HomeFragment.kt +++ b/app/src/main/java/com/qonversion/android/app/HomeFragment.kt @@ -175,34 +175,21 @@ class HomeFragment : Fragment() { } private fun purchase(productId: String) { - val purchaseOptions = QPurchaseOptions.Builder().setContextKeys(listOf("test_1","test_2")).build() - - Qonversion.shared.products(object : QonversionProductsCallback { - override fun onSuccess(products: Map) { - val product = products[productId] - product?.let { - Qonversion.shared.purchase( - requireActivity(), - it, -// it.toPurchaseModel(), - purchaseOptions, - callback = object : QonversionEntitlementsCallback { - override fun onSuccess(entitlements: Map) { - // handle result here - } - - override fun onError(error: QonversionError) { - // handle error here - } - }) + Qonversion.shared.purchase( + requireActivity(), + QPurchaseModel(productId), + callback = object : QonversionEntitlementsCallback { + override fun onSuccess(entitlements: Map) { + when (productId) { + productIdSubs -> binding.buttonSubscribe.toSuccessState() + productIdInApp -> binding.buttonInApp.toSuccessState() + } } - } - - override fun onError(error: QonversionError) { - - } - }) + override fun onError(error: QonversionError) { + showError(requireContext(), error, TAG) + } + }) } From 54f3337d566429f94c916b744caa397302836213 Mon Sep 17 00:00:00 2001 From: suriksarkisyan Date: Mon, 19 Aug 2024 08:08:35 +0000 Subject: [PATCH 11/11] [create-pull-request] automated change --- build.gradle | 2 +- fastlane/report.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 8168fcee..ebc71c84 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask buildscript { ext { release = [ - versionName: "8.0.2", + versionName: "8.1.0", versionCode: 1 ] } diff --git a/fastlane/report.xml b/fastlane/report.xml index 2d8c36f5..1814b4fe 100644 --- a/fastlane/report.xml +++ b/fastlane/report.xml @@ -5,7 +5,7 @@ - +