Skip to content

Commit 75391ec

Browse files
Merge pull request #629 from qonversion/release/8.1.0
Release 8.1.0
2 parents 2112686 + 54f3337 commit 75391ec

24 files changed

+348
-91
lines changed

app/src/main/java/com/qonversion/android/app/HomeFragment.kt

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.qonversion.android.sdk.automations.dto.QActionResultType
2323
import com.qonversion.android.sdk.automations.dto.QScreenPresentationConfig
2424
import com.qonversion.android.sdk.automations.dto.QScreenPresentationStyle
2525
import com.qonversion.android.sdk.dto.QPurchaseModel
26+
import com.qonversion.android.sdk.dto.QPurchaseOptions
2627
import com.qonversion.android.sdk.dto.entitlements.QEntitlement
2728
import com.qonversion.android.sdk.dto.QonversionError
2829
import com.qonversion.android.sdk.dto.products.QProduct
@@ -189,6 +190,7 @@ class HomeFragment : Fragment() {
189190
showError(requireContext(), error, TAG)
190191
}
191192
})
193+
192194
}
193195

194196
private fun showLoading(isLoading: Boolean) {

build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask
66
buildscript {
77
ext {
88
release = [
9-
versionName: "8.0.2",
9+
versionName: "8.1.0",
1010
versionCode: 1
1111
]
1212
}

config/detekt/baseline.xml

+29-14
Large diffs are not rendered by default.

fastlane/report.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77

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

1010
</testcase>
1111

sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt

+53-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import android.app.Activity
44
import android.util.Log
55
import com.qonversion.android.sdk.dto.QAttributionProvider
66
import com.qonversion.android.sdk.dto.QPurchaseModel
7+
import com.qonversion.android.sdk.dto.QPurchaseOptions
78
import com.qonversion.android.sdk.dto.QPurchaseUpdateModel
9+
import com.qonversion.android.sdk.dto.products.QProduct
810
import com.qonversion.android.sdk.dto.properties.QUserPropertyKey
911
import com.qonversion.android.sdk.internal.InternalConfig
1012
import com.qonversion.android.sdk.internal.QonversionInternal
@@ -75,14 +77,63 @@ interface Qonversion {
7577
*/
7678
fun syncHistoricalData()
7779

80+
/**
81+
* Make a purchase and validate it through server-to-server using Qonversion's Backend
82+
* @param context current activity context
83+
* @param product product for purchase
84+
* @param options necessary information for purchase
85+
* @param callback - callback that will be called when response is received
86+
* @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases)
87+
*/
88+
fun purchase(
89+
context: Activity,
90+
product: QProduct,
91+
options: QPurchaseOptions,
92+
callback: QonversionEntitlementsCallback
93+
)
94+
95+
/**
96+
* Make a purchase and validate it through server-to-server using Qonversion's Backend
97+
* @param context current activity context
98+
* @param product product for purchase
99+
* @param callback - callback that will be called when response is received
100+
* @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases)
101+
*/
102+
fun purchase(
103+
context: Activity,
104+
product: QProduct,
105+
callback: QonversionEntitlementsCallback
106+
)
107+
108+
/**
109+
* Update (upgrade/downgrade) subscription and validate it through server-to-server using Qonversion's Backend
110+
* @param context current activity context
111+
* @param product product for purchase
112+
* @param options necessary information for purchase
113+
* @param callback - callback that will be called when response is received
114+
* @see [Update policy](https://developer.android.com/google/play/billing/subscriptions#replacement-modes)
115+
* @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases)
116+
*/
117+
fun updatePurchase(
118+
context: Activity,
119+
product: QProduct,
120+
options: QPurchaseOptions,
121+
callback: QonversionEntitlementsCallback
122+
)
123+
78124
/**
79125
* Make a purchase and validate it through server-to-server using Qonversion's Backend
80126
* @param context current activity context
81127
* @param purchaseModel necessary information for purchase
82128
* @param callback - callback that will be called when response is received
83129
* @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases)
84130
*/
85-
fun purchase(context: Activity, purchaseModel: QPurchaseModel, callback: QonversionEntitlementsCallback)
131+
@Deprecated("Use the new purchase() method", replaceWith = ReplaceWith("purchase(context, TODO(\"pass product here\"), callback)"))
132+
fun purchase(
133+
context: Activity,
134+
purchaseModel: QPurchaseModel,
135+
callback: QonversionEntitlementsCallback
136+
)
86137

87138
/**
88139
* Update (upgrade/downgrade) subscription and validate it through server-to-server using Qonversion's Backend
@@ -92,6 +143,7 @@ interface Qonversion {
92143
* @see [Update policy](https://developer.android.com/google/play/billing/subscriptions#replacement-modes)
93144
* @see [Making Purchases](https://documentation.qonversion.io/docs/making-purchases)
94145
*/
146+
@Deprecated("Use the new updatePurchase() method", replaceWith = ReplaceWith("updatePurchase(context, TODO(\"pass product here\"), TODO(\"pass purchase options here\"), callback)"))
95147
fun updatePurchase(
96148
context: Activity,
97149
purchaseUpdateModel: QPurchaseUpdateModel,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.qonversion.android.sdk.dto
2+
3+
import com.qonversion.android.sdk.QonversionConfig.Builder
4+
import com.qonversion.android.sdk.dto.products.QProduct
5+
import com.qonversion.android.sdk.dto.products.QProductOfferDetails
6+
import com.qonversion.android.sdk.dto.products.QProductStoreDetails
7+
import com.squareup.moshi.JsonClass
8+
9+
/**
10+
* Purchase options that may be used to modify purchase process.
11+
* To create an instance, use the nested [Builder] class.
12+
*/
13+
@JsonClass(generateAdapter = true)
14+
class QPurchaseOptions internal constructor (
15+
internal val contextKeys: List<String>? = null,
16+
internal val offerId: String? = null,
17+
internal val applyOffer: Boolean = true,
18+
internal val oldProduct: QProduct? = null,
19+
internal val updatePolicy: QPurchaseUpdatePolicy? = null
20+
) {
21+
/**
22+
* The builder of QPurchaseOptions instance.
23+
*
24+
* This class contains a variety of methods to customize the purchase behavior.
25+
* You can call them sequentially and call [build] finally to get the [QPurchaseOptions] instance.
26+
*/
27+
class Builder {
28+
private var contextKeys: List<String>? = null
29+
private var offerId: String? = null
30+
private var applyOffer: Boolean = true
31+
private var oldProduct: QProduct? = null
32+
private var updatePolicy: QPurchaseUpdatePolicy? = null
33+
34+
/**
35+
* Set the context keys associated with a purchase.
36+
*
37+
* @param contextKeys context keys for the purchase.
38+
* @return builder instance for chain calls.
39+
*/
40+
fun setContextKeys(contextKeys: List<String>): QPurchaseOptions.Builder = apply {
41+
this.contextKeys = contextKeys
42+
}
43+
44+
/**
45+
* Set context keys associated with a purchase.
46+
*
47+
* @param oldProduct Qonversion product from which the upgrade/downgrade
48+
* will be initialized.
49+
* @return builder instance for chain calls.
50+
*/
51+
fun setOldProduct(oldProduct: QProduct): QPurchaseOptions.Builder = apply {
52+
this.oldProduct = oldProduct
53+
}
54+
55+
/**
56+
* Set the update policy for the purchase.
57+
* If the [updatePolicy] is not provided, then default one
58+
* will be selected - [QPurchaseUpdatePolicy.WithTimeProration].
59+
* @param updatePolicy update policy for the purchase.
60+
* @return builder instance for chain calls.
61+
*/
62+
fun setUpdatePolicy(updatePolicy: QPurchaseUpdatePolicy): QPurchaseOptions.Builder = apply {
63+
this.updatePolicy = updatePolicy
64+
}
65+
66+
/**
67+
* Set offer for the purchase.
68+
* @param offer concrete offer which you'd like to purchase.
69+
* @return builder instance for chain calls.
70+
*/
71+
fun setOffer(offer: QProductOfferDetails): QPurchaseOptions.Builder = apply {
72+
this.offerId = offer.offerId
73+
}
74+
75+
/**
76+
* Set the offer Id to the purchase.
77+
* If [offerId] is not specified, then the default offer will be applied. To know how we choose
78+
* the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails].
79+
* @param offerId concrete offer Id which you'd like to purchase.
80+
* @return builder instance for chain calls.
81+
*/
82+
fun setOfferId(offerId: String): QPurchaseOptions.Builder = apply {
83+
this.offerId = offerId
84+
}
85+
86+
/**
87+
* Call this function to remove any intro/trial offer from the purchase (use only a bare base plan).
88+
* @return builder instance for chain calls.
89+
*/
90+
fun removeOffer(): QPurchaseOptions.Builder = apply {
91+
this.applyOffer = false
92+
}
93+
94+
/**
95+
* Generate [QPurchaseOptions] instance with all the provided options.
96+
* @return the complete [QPurchaseOptions] instance.
97+
*/
98+
fun build(): QPurchaseOptions {
99+
return QPurchaseOptions(contextKeys, offerId, applyOffer, oldProduct, updatePolicy)
100+
}
101+
}
102+
}

sdk/src/main/java/com/qonversion/android/sdk/dto/products/QProduct.kt

+3
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ data class QProduct(
106106
* To know how we choose the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails].
107107
* @return purchase model to pass to the purchase method.
108108
*/
109+
@Deprecated("Use new QPurchaseOptions object instead", replaceWith = ReplaceWith("QPurchaseOptions.Builder().setOfferId(offerId).build()"))
109110
@JvmOverloads
110111
fun toPurchaseModel(offerId: String? = null): QPurchaseModel {
111112
return QPurchaseModel(qonversionID, offerId)
@@ -116,6 +117,7 @@ data class QProduct(
116117
* @param offer concrete offer which you'd like to purchase.
117118
* @return purchase model to pass to the purchase method.
118119
*/
120+
@Deprecated("Use new QPurchaseOptions object instead", replaceWith = ReplaceWith("QPurchaseOptions.Builder().setOffer(offer).build()"))
119121
fun toPurchaseModel(offer: QProductOfferDetails?): QPurchaseModel {
120122
val model = toPurchaseModel(offer?.offerId)
121123
// Remove offer for the case when provided offer details are for bare base plan.
@@ -134,6 +136,7 @@ data class QProduct(
134136
* @param updatePolicy purchase update policy.
135137
* @return purchase model to pass to the update purchase method.
136138
*/
139+
@Deprecated("Use new QPurchaseOptions object instead", replaceWith = ReplaceWith("QPurchaseOptions.Builder().setOldProduct(TODO(\"pass old product here\")).build()"))
137140
@JvmOverloads
138141
fun toPurchaseUpdateModel(
139142
oldProductId: String,

sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt

+34-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.app.Application
55
import android.content.pm.PackageManager
66
import android.os.Build
77
import com.android.billingclient.api.Purchase
8+
import com.qonversion.android.sdk.dto.QPurchaseOptions
89
import com.qonversion.android.sdk.dto.entitlements.QEntitlement
910
import com.qonversion.android.sdk.listeners.QonversionEligibilityCallback
1011
import com.qonversion.android.sdk.dto.QonversionError
@@ -94,6 +95,10 @@ internal class QProductCenterManager internal constructor(
9495

9596
private var converter: PurchaseConverter = GooglePurchaseConverter()
9697

98+
private val processingPurchaseOptions: MutableMap<String, QPurchaseOptions> by lazy {
99+
purchasesCache.loadProcessingPurchasesOptions().toMutableMap()
100+
}
101+
97102
@Volatile
98103
lateinit var billingService: BillingService
99104
@Synchronized set
@@ -324,7 +329,7 @@ internal class QProductCenterManager internal constructor(
324329
callback.onError(QonversionError(QonversionErrorCode.ProductNotFound))
325330
return
326331
}
327-
val oldProduct: QProduct? = getProductForPurchase(purchaseModel.oldProductId, products)
332+
val oldProduct: QProduct? = purchaseModel.options?.oldProduct ?: getProductForPurchase(purchaseModel.oldProductId, products)
328333
val purchaseModelEnriched = purchaseModel.enrich(product, oldProduct)
329334
processPurchase(context, purchaseModelEnriched, callback)
330335
}
@@ -349,9 +354,28 @@ internal class QProductCenterManager internal constructor(
349354
}
350355

351356
purchasingCallbacks[purchaseModel.product.storeID] = callback
357+
358+
updatePurchaseOptions(purchaseModel.options, purchaseModel.product.storeID)
359+
352360
billingService.purchase(context, purchaseModel)
353361
}
354362

363+
private fun updatePurchaseOptions(options: QPurchaseOptions?, storeProductId: String?) {
364+
storeProductId?.let { productId ->
365+
options?.let {
366+
processingPurchaseOptions[productId] = it
367+
} ?: run {
368+
processingPurchaseOptions.remove(productId)
369+
}
370+
371+
purchasesCache.saveProcessingPurchasesOptions(processingPurchaseOptions)
372+
}
373+
}
374+
375+
private fun removePurchaseOptions(productId: String?) {
376+
updatePurchaseOptions(null, productId)
377+
}
378+
355379
private fun getProductForPurchase(
356380
productId: String?,
357381
products: Map<String, QProduct>
@@ -651,7 +675,7 @@ internal class QProductCenterManager internal constructor(
651675

652676
processingPurchases = completedPurchases
653677

654-
val purchasesInfo = converter.convertPurchases(completedPurchases)
678+
val purchasesInfo = converter.convertPurchases(completedPurchases, processingPurchaseOptions)
655679

656680
val handledPurchasesCallback =
657681
getWrappedPurchasesCallback(completedPurchases, callback)
@@ -673,6 +697,9 @@ internal class QProductCenterManager internal constructor(
673697
return object : QonversionLaunchCallback {
674698
override fun onSuccess(launchResult: QLaunchResult) {
675699
handledPurchasesCache.saveHandledPurchases(trackingPurchases)
700+
trackingPurchases.forEach {
701+
removePurchaseOptions(it.productId)
702+
}
676703
outerCallback?.onSuccess(launchResult)
677704
}
678705

@@ -959,7 +986,8 @@ internal class QProductCenterManager internal constructor(
959986
val product: QProduct? = launchResultCache.getActualProducts()?.values?.find {
960987
it.storeID == purchase.productId
961988
}
962-
val purchaseInfo = converter.convertPurchase(purchase)
989+
val currentPurchaseOptions = processingPurchaseOptions[purchase.productId]
990+
val purchaseInfo = converter.convertPurchase(purchase, currentPurchaseOptions)
963991
repository.purchase(
964992
installDate,
965993
purchaseInfo,
@@ -970,6 +998,7 @@ internal class QProductCenterManager internal constructor(
970998

971999
val entitlements = launchResult.permissions.toEntitlementsMap()
9721000

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

1013+
removePurchaseOptions(product?.storeID)
1014+
9841015
if (shouldCalculatePermissionsLocally(error)) {
9851016
calculatePurchasePermissionsLocally(
9861017
purchase,

sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt

+39
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.qonversion.android.sdk.Qonversion
99
import com.qonversion.android.sdk.automations.internal.QAutomationsManager
1010
import com.qonversion.android.sdk.dto.QAttributionProvider
1111
import com.qonversion.android.sdk.dto.QPurchaseModel
12+
import com.qonversion.android.sdk.dto.QPurchaseOptions
1213
import com.qonversion.android.sdk.dto.QPurchaseUpdateModel
1314
import com.qonversion.android.sdk.dto.entitlements.QEntitlement
1415
import com.qonversion.android.sdk.dto.QRemoteConfig
@@ -170,6 +171,44 @@ internal class QonversionInternal(
170171
)
171172
}
172173

174+
override fun purchase(
175+
context: Activity,
176+
product: QProduct,
177+
options: QPurchaseOptions,
178+
callback: QonversionEntitlementsCallback
179+
) {
180+
productCenterManager.purchaseProduct(
181+
context,
182+
PurchaseModelInternal(product, options),
183+
mainEntitlementsCallback(callback)
184+
)
185+
}
186+
187+
override fun purchase(
188+
context: Activity,
189+
product: QProduct,
190+
callback: QonversionEntitlementsCallback
191+
) {
192+
productCenterManager.purchaseProduct(
193+
context,
194+
PurchaseModelInternal(product),
195+
mainEntitlementsCallback(callback)
196+
)
197+
}
198+
199+
override fun updatePurchase(
200+
context: Activity,
201+
product: QProduct,
202+
options: QPurchaseOptions,
203+
callback: QonversionEntitlementsCallback
204+
) {
205+
productCenterManager.purchaseProduct(
206+
context,
207+
PurchaseModelInternal(product, options),
208+
mainEntitlementsCallback(callback)
209+
)
210+
}
211+
173212
override fun updatePurchase(
174213
context: Activity,
175214
purchaseUpdateModel: QPurchaseUpdateModel,

0 commit comments

Comments
 (0)