Skip to content

Commit

Permalink
Merge pull request #629 from qonversion/release/8.1.0
Browse files Browse the repository at this point in the history
Release 8.1.0
  • Loading branch information
suriksarkisyan authored Aug 19, 2024
2 parents 2112686 + 54f3337 commit 75391ec
Show file tree
Hide file tree
Showing 24 changed files with 348 additions and 91 deletions.
2 changes: 2 additions & 0 deletions app/src/main/java/com/qonversion/android/app/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.qonversion.android.sdk.automations.dto.QActionResultType
import com.qonversion.android.sdk.automations.dto.QScreenPresentationConfig
import com.qonversion.android.sdk.automations.dto.QScreenPresentationStyle
import com.qonversion.android.sdk.dto.QPurchaseModel
import com.qonversion.android.sdk.dto.QPurchaseOptions
import com.qonversion.android.sdk.dto.entitlements.QEntitlement
import com.qonversion.android.sdk.dto.QonversionError
import com.qonversion.android.sdk.dto.products.QProduct
Expand Down Expand Up @@ -189,6 +190,7 @@ class HomeFragment : Fragment() {
showError(requireContext(), error, TAG)
}
})

}

private fun showLoading(isLoading: Boolean) {
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask
buildscript {
ext {
release = [
versionName: "8.0.2",
versionName: "8.1.0",
versionCode: 1
]
}
Expand Down
43 changes: 29 additions & 14 deletions config/detekt/baseline.xml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion fastlane/report.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@



<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000201">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000173">

</testcase>

Expand Down
54 changes: 53 additions & 1 deletion sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -75,14 +77,63 @@ interface Qonversion {
*/
fun syncHistoricalData()

/**
* 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
)

/**
* 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 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,
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
)

/**
* 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)
*/
fun purchase(context: Activity, purchaseModel: QPurchaseModel, callback: QonversionEntitlementsCallback)
@Deprecated("Use the 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
Expand All @@ -92,6 +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 the new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context, TODO(\"pass product here\"), TODO(\"pass purchase options here\"), callback)"))
fun updatePurchase(
context: Activity,
purchaseUpdateModel: QPurchaseUpdateModel,
Expand Down
102 changes: 102 additions & 0 deletions sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseOptions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.qonversion.android.sdk.dto

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<String>? = null,
internal val offerId: String? = null,
internal val applyOffer: Boolean = true,
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<String>? = null
private var offerId: String? = null
private var applyOffer: Boolean = true
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<String>): QPurchaseOptions.Builder = apply {
this.contextKeys = contextKeys
}

/**
* Set context keys associated with a 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 {
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
}

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

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

/**
* Generate [QPurchaseOptions] instance with all the provided options.
* @return the complete [QPurchaseOptions] instance.
*/
fun build(): QPurchaseOptions {
return QPurchaseOptions(contextKeys, offerId, applyOffer, oldProduct, updatePolicy)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -94,6 +95,10 @@ internal class QProductCenterManager internal constructor(

private var converter: PurchaseConverter = GooglePurchaseConverter()

private val processingPurchaseOptions: MutableMap<String, QPurchaseOptions> by lazy {
purchasesCache.loadProcessingPurchasesOptions().toMutableMap()
}

@Volatile
lateinit var billingService: BillingService
@Synchronized set
Expand Down Expand Up @@ -324,7 +329,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)
}
Expand All @@ -349,9 +354,28 @@ internal class QProductCenterManager internal constructor(
}

purchasingCallbacks[purchaseModel.product.storeID] = callback

updatePurchaseOptions(purchaseModel.options, purchaseModel.product.storeID)

billingService.purchase(context, purchaseModel)
}

private fun updatePurchaseOptions(options: QPurchaseOptions?, storeProductId: String?) {
storeProductId?.let { productId ->
options?.let {
processingPurchaseOptions[productId] = it
} ?: run {
processingPurchaseOptions.remove(productId)
}

purchasesCache.saveProcessingPurchasesOptions(processingPurchaseOptions)
}
}

private fun removePurchaseOptions(productId: String?) {
updatePurchaseOptions(null, productId)
}

private fun getProductForPurchase(
productId: String?,
products: Map<String, QProduct>
Expand Down Expand Up @@ -651,7 +675,7 @@ internal class QProductCenterManager internal constructor(

processingPurchases = completedPurchases

val purchasesInfo = converter.convertPurchases(completedPurchases)
val purchasesInfo = converter.convertPurchases(completedPurchases, processingPurchaseOptions)

val handledPurchasesCallback =
getWrappedPurchasesCallback(completedPurchases, callback)
Expand All @@ -673,6 +697,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)
}

Expand Down Expand Up @@ -959,7 +986,8 @@ internal class QProductCenterManager internal constructor(
val product: QProduct? = launchResultCache.getActualProducts()?.values?.find {
it.storeID == purchase.productId
}
val purchaseInfo = converter.convertPurchase(purchase)
val currentPurchaseOptions = processingPurchaseOptions[purchase.productId]
val purchaseInfo = converter.convertPurchase(purchase, currentPurchaseOptions)
repository.purchase(
installDate,
purchaseInfo,
Expand All @@ -970,6 +998,7 @@ internal class QProductCenterManager internal constructor(

val entitlements = launchResult.permissions.toEntitlementsMap()

removePurchaseOptions(product?.storeID)
purchaseCallback?.onSuccess(entitlements) ?: run {
internalConfig.entitlementsUpdateListener?.onEntitlementsUpdated(
entitlements
Expand All @@ -981,6 +1010,8 @@ internal class QProductCenterManager internal constructor(
override fun onError(error: QonversionError) {
storeFailedPurchaseIfNecessary(purchase, purchaseInfo, product)

removePurchaseOptions(product?.storeID)

if (shouldCalculatePermissionsLocally(error)) {
calculatePurchasePermissionsLocally(
purchase,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -170,6 +171,44 @@ internal class QonversionInternal(
)
}

override fun purchase(
context: Activity,
product: QProduct,
options: QPurchaseOptions,
callback: QonversionEntitlementsCallback
) {
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(
context: Activity,
product: QProduct,
options: QPurchaseOptions,
callback: QonversionEntitlementsCallback
) {
productCenterManager.purchaseProduct(
context,
PurchaseModelInternal(product, options),
mainEntitlementsCallback(callback)
)
}

override fun updatePurchase(
context: Activity,
purchaseUpdateModel: QPurchaseUpdateModel,
Expand Down
Loading

0 comments on commit 75391ec

Please sign in to comment.