Skip to content

Commit 3016b01

Browse files
Merge pull request #91 from qonversion/release/3.3.0
Release/3.3.0
2 parents 5e25056 + a6b4bd3 commit 3016b01

23 files changed

+400
-103
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 3.3.0
2+
* Add `purchaseProduct()`, `updatePurchaseWithProduct()`
3+
* Deprecate `resetUser()`
4+
15
## 3.2.1
26
* Fix enum dependencies
37

android/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
group 'com.qonversion.flutter.sdk.qonversion_flutter_sdk'
2-
version '3.2.1'
2+
version '3.3.0'
33

44
buildscript {
55
ext.kotlin_version = '1.3.50'
6-
ext.qonversion_version = '2.8.0'
6+
ext.qonversion_version = '2.9.1'
77
repositories {
88
google()
99
jcenter()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.qonversion.flutter.sdk.qonversion_flutter_sdk
2+
3+
object ProductFields {
4+
const val ID = "id"
5+
const val STORE_ID = "store_id"
6+
const val TYPE = "type"
7+
const val DURATION = "duration"
8+
const val SKU_DETAILS = "sku_details"
9+
const val PRETTY_PRICE = "pretty_price"
10+
const val TRIAL_DURATION = "trial_duration"
11+
const val OFFERING_ID = "offering_id"
12+
}
13+
14+
object SkuDetailsFields {
15+
const val ORIGINAL_JSON = "originalJson"
16+
}

android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/FlutterResult+CustomErrors.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ fun MethodChannel.Result.noProviderError() {
2828
return this.error("5", "Could not find provider", passValidValue)
2929
}
3030

31+
fun MethodChannel.Result.noProduct() {
32+
return this.error("ProductNotProvided", "Could not find product", "Please provide a valid product")
33+
}
34+
3135
fun MethodChannel.Result.noProductIdError() {
3236
return this.error("8", "Could not find productId value", "Please provide valid productId")
3337
}
@@ -41,7 +45,7 @@ fun MethodChannel.Result.noNewProductIdError() {
4145
}
4246

4347
fun MethodChannel.Result.noOldProductIdError() {
44-
return this.error("11", "Could not find new product id", passValidValue)
48+
return this.error("11", "Could not find old product id", passValidValue)
4549
}
4650

4751
fun MethodChannel.Result.parsingError(message: String?) {
@@ -57,9 +61,17 @@ fun MethodChannel.Result.noPropertyValue() {
5761
}
5862

5963
fun MethodChannel.Result.offeringsError(description: String?, message: String?) {
60-
return this.error("OFFERINGS", "Could not get offerings", "$description $message")
64+
return this.error("Offerings", "Could not get offerings", "$description $message")
6165
}
6266

6367
fun MethodChannel.Result.noSdkInfo() {
6468
return this.error("15", "Could not find sdk info", passValidValue)
6569
}
70+
71+
fun MethodChannel.Result.noProductIdField(details: String?) {
72+
return this.error("NoProductIdField", "Could not find qonversionId in Product", details)
73+
}
74+
75+
fun MethodChannel.Result.jsonSerializationError(details: String?) {
76+
return this.error("JSONSerialization", "JSON Serialization Error", details)
77+
}

android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/Mapper.kt

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.qonversion.flutter.sdk.qonversion_flutter_sdk
22

33
import com.android.billingclient.api.SkuDetails
4+
import com.google.gson.Gson
5+
import com.google.gson.JsonSyntaxException
6+
import com.google.gson.reflect.TypeToken
47
import com.qonversion.android.sdk.QonversionError
58
import com.qonversion.android.sdk.dto.QLaunchResult
69
import com.qonversion.android.sdk.dto.QPermission
@@ -9,6 +12,9 @@ import com.qonversion.android.sdk.dto.eligibility.QIntroEligibilityStatus
912
import com.qonversion.android.sdk.dto.offerings.QOffering
1013
import com.qonversion.android.sdk.dto.offerings.QOfferings
1114
import com.qonversion.android.sdk.dto.products.QProduct
15+
import com.qonversion.android.sdk.dto.products.QProductDuration
16+
import com.qonversion.android.sdk.dto.products.QProductType
17+
import com.qonversion.android.sdk.dto.products.QTrialDuration
1218

1319
data class PurchaseResult(val permissions: Map<String, QPermission>? = null, val error: QonversionError? = null) {
1420
fun toMap(): Map<String, Any?> {
@@ -31,13 +37,14 @@ fun QLaunchResult.toMap(): Map<String, Any> {
3137

3238
fun QProduct.toMap(): Map<String, Any?> {
3339
return mapOf(
34-
"id" to qonversionID,
35-
"store_id" to storeID,
36-
"type" to type.type,
37-
"duration" to duration?.type,
38-
"sku_details" to skuDetail?.toMap(),
39-
"pretty_price" to prettyPrice,
40-
"trial_duration" to trialDuration?.type
40+
ProductFields.ID to qonversionID,
41+
ProductFields.STORE_ID to storeID,
42+
ProductFields.TYPE to type.type,
43+
ProductFields.DURATION to duration?.type,
44+
ProductFields.SKU_DETAILS to skuDetail?.toMap(),
45+
ProductFields.PRETTY_PRICE to prettyPrice,
46+
ProductFields.TRIAL_DURATION to trialDuration?.type,
47+
ProductFields.OFFERING_ID to offeringID
4148
)
4249
}
4350

@@ -96,6 +103,40 @@ fun SkuDetails.toMap(): Map<String, Any?> {
96103
"type" to type,
97104
"subscriptionPeriod" to subscriptionPeriod,
98105
"originalPrice" to originalPrice,
99-
"originalPriceAmountMicros" to originalPriceAmountMicros
106+
"originalPriceAmountMicros" to originalPriceAmountMicros,
107+
SkuDetailsFields.ORIGINAL_JSON to originalJson
100108
)
101109
}
110+
111+
@Throws(JsonSyntaxException::class, IllegalArgumentException::class, ClassCastException::class)
112+
fun mapQProduct(jsonProduct: String): QProduct? {
113+
val mapType = object : TypeToken<Map<String, Any?>>() {}.type
114+
val mappedProduct: Map<String, Any?> = Gson().fromJson(jsonProduct, mapType)
115+
116+
val qonversionId = mappedProduct[ProductFields.ID] as? String ?: return null
117+
118+
val storeId = mappedProduct[ProductFields.STORE_ID] as? String
119+
120+
val type = mappedProduct[ProductFields.TYPE] as Double
121+
val productType = QProductType.fromType(type.toInt())
122+
123+
val duration = mappedProduct[ProductFields.DURATION] as? Double
124+
val productDuration = duration?.toInt()?.let { QProductDuration.fromType(it) }
125+
126+
val prettyPrice = mappedProduct[ProductFields.PRETTY_PRICE] as? String
127+
128+
val trialDuration = mappedProduct[ProductFields.TRIAL_DURATION] as? Double
129+
val productTrialDuration = trialDuration?.toInt()?.let { QTrialDuration.fromType(it) }
130+
131+
val offeringId = mappedProduct[ProductFields.OFFERING_ID] as String
132+
133+
val originalSkuDetails = mappedProduct[SkuDetailsFields.ORIGINAL_JSON] as? String
134+
val skuDetails = originalSkuDetails?.let { SkuDetails(it) }
135+
136+
return QProduct(qonversionId, storeId, productType, productDuration).also {
137+
it.skuDetail = skuDetails
138+
it.offeringID = offeringId
139+
it.prettyPrice = prettyPrice
140+
it.trialDuration = productTrialDuration
141+
}
142+
}

android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/QonversionFlutterSdkPlugin.kt

Lines changed: 100 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package com.qonversion.flutter.sdk.qonversion_flutter_sdk
22

33
import android.app.Activity
44
import android.app.Application
5+
import android.util.Log
56
import androidx.annotation.NonNull
67
import androidx.preference.PreferenceManager
78
import com.google.gson.Gson
9+
import com.google.gson.JsonSyntaxException
810
import com.qonversion.android.sdk.*
911
import com.qonversion.android.sdk.dto.QLaunchResult
1012
import com.qonversion.android.sdk.dto.QPermission
@@ -20,6 +22,7 @@ import io.flutter.plugin.common.MethodChannel
2022
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
2123
import io.flutter.plugin.common.MethodChannel.Result
2224
import io.flutter.plugin.common.PluginRegistry.Registrar
25+
import java.lang.Exception
2326

2427
/** QonversionFlutterSdkPlugin */
2528
class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAware {
@@ -93,10 +96,6 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
9396
Qonversion.logout()
9497
return result.success(null)
9598
}
96-
"resetUser" -> {
97-
Qonversion.resetUser()
98-
return result.success(null)
99-
}
10099
}
101100

102101
// Methods with args
@@ -106,7 +105,9 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
106105
when (call.method) {
107106
"launch" -> launch(args, result)
108107
"purchase" -> purchase(args["productId"] as? String, result)
108+
"purchaseProduct" -> purchaseProduct(args["product"] as? String, result)
109109
"updatePurchase" -> updatePurchase(args, result)
110+
"updatePurchaseWithProduct" -> updatePurchaseWithProduct(args, result)
110111
"setUserId" -> setUserId(args["userId"] as? String, result)
111112
"setProperty" -> setProperty(args, result)
112113
"setUserProperty" -> setUserProperty(args, result)
@@ -162,43 +163,71 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
162163

163164
private fun purchase(productId: String?, result: Result) {
164165
if (productId == null) {
165-
result.noProductIdError()
166-
return
166+
return result.noProductIdError()
167167
}
168168

169-
activity?.let { activity ->
170-
Qonversion.purchase(activity, productId, callback = object : QonversionPermissionsCallback {
171-
override fun onSuccess(permissions: Map<String, QPermission>) {
172-
result.success(PurchaseResult(permissions).toMap())
173-
}
169+
activity?.let {
170+
Qonversion.purchase(it, productId, getPurchasesListener(result))
171+
} ?: handleMissingActivityOnPurchase(result, object {}.javaClass.enclosingMethod?.name)
172+
}
173+
174+
private fun purchaseProduct(jsonProduct: String?, result: Result) {
175+
if (jsonProduct == null) {
176+
return result.noProduct()
177+
}
178+
179+
val funcName = object {}.javaClass.enclosingMethod?.name
180+
try {
181+
val product = mapQProduct(jsonProduct)
182+
?: return handleMissingProductIdField(result, funcName)
183+
184+
activity?.let {
185+
Qonversion.purchase(it, product, getPurchasesListener(result))
186+
} ?: handleMissingActivityOnPurchase(result, funcName)
174187

175-
override fun onError(error: QonversionError) {
176-
result.success(PurchaseResult(error = error).toMap())
177-
}
178-
})
179-
} ?: result.error(QonversionErrorCode.PurchaseInvalid.name, "Couldn't make purchase. There is no Activity context", null)
188+
} catch (e: JsonSyntaxException) {
189+
handleJsonExceptionOnPurchase(result, e, funcName)
190+
} catch (e: IllegalArgumentException) {
191+
handleExceptionOnPurchase(result, e, funcName)
192+
} catch (e: ClassCastException) {
193+
handleExceptionOnPurchase(result, e, funcName)
194+
}
180195
}
181196

182197
private fun updatePurchase(args: Map<String, Any>, result: Result) {
183198
val newProductId = args["newProductId"] as? String ?: return result.noNewProductIdError()
184199
val oldProductId = args["oldProductId"] as? String ?: return result.noOldProductIdError()
185200
val prorationMode = args["proration_mode"] as? Int
186201

187-
activity?.let { activity ->
188-
Qonversion.updatePurchase(activity, newProductId, oldProductId, prorationMode, callback = object : QonversionPermissionsCallback {
189-
override fun onSuccess(permissions: Map<String, QPermission>) {
190-
result.success(permissions.mapValues { it.value.toMap() })
191-
}
202+
activity?.let {
203+
Qonversion.updatePurchase(it, newProductId, oldProductId, prorationMode, getUpdatePurchasesListener(result))
204+
} ?: handleMissingActivityOnPurchase(result, object {}.javaClass.enclosingMethod?.name)
205+
}
206+
207+
private fun updatePurchaseWithProduct(args: Map<String, Any>, result: Result) {
208+
val jsonProduct = args["product"] as? String ?: return result.noProduct()
209+
val oldProductId = args["oldProductId"] as? String ?: return result.noOldProductIdError()
210+
val prorationMode = args["proration_mode"] as? Int
192211

193-
override fun onError(error: QonversionError) {
194-
result.qonversionError(error.description, error.additionalMessage)
195-
}
196-
})
197-
} ?: result.error(QonversionErrorCode.PurchaseInvalid.name, "Couldn't update purchase. There is no Activity context", null)
212+
val funcName = object {}.javaClass.enclosingMethod?.name
213+
try {
214+
val product = mapQProduct(jsonProduct)
215+
?: return handleMissingProductIdField(result, funcName)
216+
217+
activity?.let {
218+
Qonversion.updatePurchase(it, product, oldProductId, prorationMode, getUpdatePurchasesListener(result))
219+
} ?: handleMissingActivityOnPurchase(result, funcName)
220+
} catch (e: JsonSyntaxException) {
221+
handleJsonExceptionOnPurchase(result, e, funcName)
222+
} catch (e: IllegalArgumentException) {
223+
handleExceptionOnPurchase(result, e, funcName)
224+
} catch (e: ClassCastException) {
225+
handleExceptionOnPurchase(result, e, funcName)
226+
}
198227
}
199228

200229
private fun checkPermissions(result: Result) {
201-
Qonversion.checkPermissions(object: QonversionPermissionsCallback {
230+
Qonversion.checkPermissions(object : QonversionPermissionsCallback {
202231
override fun onSuccess(permissions: Map<String, QPermission>) {
203232
result.success(permissions.mapValues { it.value.toMap() })
204233
}
@@ -210,7 +239,7 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
210239
}
211240

212241
private fun restore(result: Result) {
213-
Qonversion.restore(object: QonversionPermissionsCallback {
242+
Qonversion.restore(object : QonversionPermissionsCallback {
214243
override fun onSuccess(permissions: Map<String, QPermission>) {
215244
result.success(permissions.mapValues { it.value.toMap() })
216245
}
@@ -222,7 +251,7 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
222251
}
223252

224253
private fun offerings(result: Result) {
225-
Qonversion.offerings(callback = object: QonversionOfferingsCallback {
254+
Qonversion.offerings(callback = object : QonversionOfferingsCallback {
226255
override fun onSuccess(offerings: QOfferings) {
227256
val jsonOfferings = Gson().toJson(offerings.toMap())
228257
result.success(jsonOfferings)
@@ -235,7 +264,7 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
235264
}
236265

237266
private fun products(result: Result) {
238-
Qonversion.products(callback = object: QonversionProductsCallback {
267+
Qonversion.products(callback = object : QonversionProductsCallback {
239268
override fun onSuccess(products: Map<String, QProduct>) {
240269
result.success(products.mapValues { it.value.toMap() })
241270
}
@@ -306,7 +335,7 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
306335
private fun checkTrialIntroEligibility(args: Map<String, Any>, result: Result) {
307336
val ids = args["ids"] as? List<String> ?: return result.noDataError()
308337

309-
Qonversion.checkTrialIntroEligibilityForProductIds(ids, callback = object: QonversionEligibilityCallback {
338+
Qonversion.checkTrialIntroEligibilityForProductIds(ids, callback = object : QonversionEligibilityCallback {
310339
override fun onSuccess(eligibilities: Map<String, QEligibility>) {
311340
val jsonEligibilities = Gson().toJson(eligibilities.mapValues { it.value.toMap() })
312341
result.success(jsonEligibilities)
@@ -318,6 +347,46 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
318347
})
319348
}
320349

350+
private fun getPurchasesListener(result: Result) = object : QonversionPermissionsCallback {
351+
override fun onSuccess(permissions: Map<String, QPermission>) =
352+
result.success(PurchaseResult(permissions).toMap())
353+
354+
override fun onError(error: QonversionError) =
355+
result.success(PurchaseResult(error = error).toMap())
356+
}
357+
358+
private fun getUpdatePurchasesListener(result: Result) = object : QonversionPermissionsCallback {
359+
override fun onSuccess(permissions: Map<String, QPermission>) =
360+
result.success(permissions.mapValues { it.value.toMap() })
361+
362+
override fun onError(error: QonversionError) =
363+
result.qonversionError(error.description, error.additionalMessage)
364+
}
365+
366+
private fun handleMissingProductIdField(result: Result, functionName: String?) {
367+
val errorMessage = "Failed to deserialize Qonversion Product. There is no qonversionId"
368+
Log.d("Qonversion", "$functionName() -> $errorMessage")
369+
result.noProductIdField(errorMessage)
370+
}
371+
372+
private fun handleMissingActivityOnPurchase(result: Result, functionName: String?) {
373+
val errorMessage = "Couldn't make a purchase. There is no Activity context"
374+
Log.d("Qonversion", "$functionName() -> $errorMessage")
375+
result.error(QonversionErrorCode.PurchaseInvalid.name, errorMessage, null)
376+
}
377+
378+
private fun handleExceptionOnPurchase(result: Result, e: Exception, functionName: String?) {
379+
val errorMessage = "Couldn't make a purchase as an Exception occurred. ${e.localizedMessage}."
380+
Log.d("Qonversion", "$functionName() -> $errorMessage")
381+
result.error(QonversionErrorCode.PurchaseInvalid.name, errorMessage, null)
382+
}
383+
384+
private fun handleJsonExceptionOnPurchase(result: Result, e: JsonSyntaxException, functionName: String?) {
385+
val errorMessage = "Failed to deserialize Qonversion Product: ${e.localizedMessage}."
386+
Log.d("Qonversion", "$functionName() -> $errorMessage")
387+
result.jsonSerializationError(errorMessage)
388+
}
389+
321390
private fun storeSdkInfo(args: Map<String, Any>, result: Result) {
322391
val version = args["version"] as? String ?: return result.noSdkInfo()
323392
val versionKey = args["versionKey"] as? String ?: return result.noSdkInfo()

ios/Classes/Constants.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// Constants.swift
3+
// qonversion_flutter
4+
//
5+
// Created by Maria on 30.06.2021.
6+
//
7+
8+
struct ProductFields {
9+
static let id = "id"
10+
static let storeId = "store_id"
11+
static let type = "type"
12+
static let duration = "duration"
13+
static let skProduct = "sk_product"
14+
static let prettyPrice = "pretty_price"
15+
static let trialDuration = "trial_duration"
16+
static let offeringId = "offering_id"
17+
}

0 commit comments

Comments
 (0)