@purchasely/cordova-plugin-purchasely 5.7.3 → 6.0.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -8
- package/__tests__/Purchasely.test.js +307 -92
- package/example/config.xml +1 -11
- package/example/e2e/README.md +44 -0
- package/example/e2e/helpers/driver.js +70 -0
- package/example/e2e/package.json +19 -0
- package/example/e2e/specs/bridge.e2e.js +51 -0
- package/example/e2e/specs/dismiss.e2e.js +37 -0
- package/example/e2e/tools/ci_run_e2e.sh +49 -0
- package/example/e2e/tools/ci_run_e2e_ios.sh +46 -0
- package/example/e2e/wdio.android.conf.js +21 -0
- package/example/e2e/wdio.ios.conf.js +19 -0
- package/example/e2e/wdio.shared.conf.js +28 -0
- package/example/package-lock.json +59 -205
- package/example/package.json +7 -6
- package/example/www/index.html +1 -7
- package/example/www/js/index.js +113 -92
- package/package.json +1 -1
- package/plugin.xml +4 -22
- package/src/android/PurchaselyPlugin.kt +529 -440
- package/src/ios/CDVPurchasely+Events.h +1 -1
- package/src/ios/CDVPurchasely+UserAttributes.h +1 -1
- package/src/ios/CDVPurchasely+UserAttributes.m +2 -0
- package/src/ios/CDVPurchasely.h +21 -16
- package/src/ios/CDVPurchasely.m +497 -261
- package/www/Purchasely.js +208 -40
- package/src/android/PLYProductActivity.kt +0 -168
- package/src/android/PLYSubscriptionsActivity.java +0 -37
- package/src/android/activity_ply_product_activity.xml +0 -6
- package/src/android/activity_ply_subscriptions_activity.xml +0 -6
- package/src/android/theme_purchasely_fullscreen.xml +0 -6
- package/src/android/theme_purchasely_fullscreen_v23.xml +0 -6
- package/src/android/theme_purchasely_fullscreen_v23_light_status_bar.xml +0 -7
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
package cordova.plugin.purchasely
|
|
2
2
|
|
|
3
|
-
import android.app.Activity
|
|
4
|
-
import android.content.Intent
|
|
5
3
|
import android.net.Uri
|
|
6
4
|
import android.os.Build
|
|
7
5
|
import android.util.Log
|
|
@@ -10,28 +8,24 @@ import io.purchasely.ext.Attribute
|
|
|
10
8
|
import io.purchasely.ext.DistributionType
|
|
11
9
|
import io.purchasely.ext.EventListener
|
|
12
10
|
import io.purchasely.ext.LogLevel
|
|
11
|
+
import io.purchasely.ext.PLYActionInterceptorCallback
|
|
13
12
|
import io.purchasely.ext.PLYAppTechnology
|
|
14
|
-
import io.purchasely.ext.PLYCompletionHandler
|
|
15
13
|
import io.purchasely.ext.PLYDataProcessingLegalBasis
|
|
16
14
|
import io.purchasely.ext.PLYDataProcessingPurpose
|
|
17
15
|
import io.purchasely.ext.PLYEvent
|
|
18
|
-
import io.purchasely.ext.
|
|
19
|
-
import io.purchasely.ext.
|
|
20
|
-
import io.purchasely.ext.PLYPresentationType
|
|
21
|
-
import io.purchasely.ext.PLYPresentationProperties
|
|
22
|
-
import io.purchasely.ext.PLYProductViewResult
|
|
16
|
+
import io.purchasely.ext.PLYInterceptResult
|
|
17
|
+
import io.purchasely.ext.PLYInterceptorInfo
|
|
23
18
|
import io.purchasely.ext.PLYRunningMode
|
|
24
|
-
import io.purchasely.ext.PLYRunningMode.Full
|
|
25
|
-
import io.purchasely.ext.PLYRunningMode.PaywallObserver
|
|
26
|
-
import io.purchasely.ext.PlanListener
|
|
27
|
-
import io.purchasely.ext.ProductListener
|
|
28
|
-
import io.purchasely.ext.ProductsListener
|
|
29
19
|
import io.purchasely.ext.PurchaseListener
|
|
30
20
|
import io.purchasely.ext.Purchasely
|
|
31
21
|
import io.purchasely.ext.State
|
|
32
22
|
import io.purchasely.ext.StoreType
|
|
33
|
-
import io.purchasely.ext.SubscriptionsListener
|
|
34
23
|
import io.purchasely.ext.UserAttributeListener
|
|
24
|
+
import io.purchasely.ext.presentation.PLYPresentationAction
|
|
25
|
+
import io.purchasely.ext.presentation.PLYPresentationBase
|
|
26
|
+
import io.purchasely.ext.presentation.PLYPresentationOutcome
|
|
27
|
+
import io.purchasely.ext.presentation.display
|
|
28
|
+
import io.purchasely.ext.presentation.preload
|
|
35
29
|
import io.purchasely.storage.userData.PLYUserAttributeSource
|
|
36
30
|
import io.purchasely.storage.userData.PLYUserAttributeType
|
|
37
31
|
import io.purchasely.models.PLYError
|
|
@@ -40,26 +34,52 @@ import io.purchasely.models.PLYPresentationPlan
|
|
|
40
34
|
import io.purchasely.models.PLYProduct
|
|
41
35
|
import io.purchasely.models.PLYSubscriptionData
|
|
42
36
|
import io.purchasely.views.presentation.PLYThemeMode
|
|
37
|
+
import io.purchasely.views.presentation.models.PLYDimensionType
|
|
38
|
+
import io.purchasely.views.presentation.models.PLYTransition
|
|
39
|
+
import io.purchasely.views.presentation.models.PLYTransitionDimension
|
|
40
|
+
import io.purchasely.views.presentation.models.PLYTransitionType
|
|
41
|
+
import kotlinx.coroutines.CoroutineScope
|
|
42
|
+
import kotlinx.coroutines.Dispatchers
|
|
43
|
+
import kotlinx.coroutines.SupervisorJob
|
|
44
|
+
import kotlinx.coroutines.launch
|
|
43
45
|
import org.apache.cordova.CallbackContext
|
|
44
|
-
import org.apache.cordova.CordovaInterface
|
|
45
46
|
import org.apache.cordova.CordovaPlugin
|
|
46
47
|
import org.apache.cordova.PluginResult
|
|
47
48
|
import org.json.JSONArray
|
|
48
49
|
import org.json.JSONException
|
|
49
50
|
import org.json.JSONObject
|
|
50
|
-
import java.lang.ref.WeakReference
|
|
51
51
|
import java.text.SimpleDateFormat
|
|
52
52
|
import java.util.Calendar
|
|
53
53
|
import java.util.Date
|
|
54
54
|
import java.util.Locale
|
|
55
55
|
import java.util.TimeZone
|
|
56
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* This class echoes a string called from JavaScript.
|
|
59
60
|
*/
|
|
60
|
-
class PurchaselyPlugin : CordovaPlugin() {
|
|
61
|
-
|
|
62
|
-
private
|
|
61
|
+
class PurchaselyPlugin : CordovaPlugin(), CoroutineScope {
|
|
62
|
+
|
|
63
|
+
private val job = SupervisorJob()
|
|
64
|
+
override val coroutineContext = job + Dispatchers.Main
|
|
65
|
+
|
|
66
|
+
// v6 per-action interceptor state. Each registered action kind keeps its own
|
|
67
|
+
// Cordova callback (to emit intercept events); each intercepted invocation stashes
|
|
68
|
+
// its completion under a unique id so concurrent intercepts resolve independently.
|
|
69
|
+
private val actionInterceptorCallbacks = ConcurrentHashMap<String, CallbackContext>()
|
|
70
|
+
private val pendingInterceptCompletions = ConcurrentHashMap<String, (PLYInterceptResult) -> Unit>()
|
|
71
|
+
private var interceptorInvocationCounter = 0
|
|
72
|
+
|
|
73
|
+
// Presentation currently displayed (used for back()/close()).
|
|
74
|
+
private var displayedPresentation: PLYPresentationBase.Loaded? = null
|
|
75
|
+
// Presentations preloaded by fetchPresentation, keyed by a synthetic handle
|
|
76
|
+
// (the "id" we send back to JS) so presentPresentation can re-display them.
|
|
77
|
+
private val loadedPresentations = ConcurrentHashMap<String, PLYPresentationBase.Loaded>()
|
|
78
|
+
|
|
79
|
+
override fun onDestroy() {
|
|
80
|
+
job.cancel()
|
|
81
|
+
super.onDestroy()
|
|
82
|
+
}
|
|
63
83
|
|
|
64
84
|
override fun execute(
|
|
65
85
|
action: String,
|
|
@@ -68,16 +88,7 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
68
88
|
): Boolean {
|
|
69
89
|
try {
|
|
70
90
|
when (action) {
|
|
71
|
-
"start" -> start(
|
|
72
|
-
getStringFromJson(args.getString(0)),
|
|
73
|
-
args.getJSONArray(1),
|
|
74
|
-
args.getBoolean(2),
|
|
75
|
-
getStringFromJson(args.getString(3)),
|
|
76
|
-
args.getInt(4),
|
|
77
|
-
args.getInt(5),
|
|
78
|
-
getStringFromJson(args.getString(6)),
|
|
79
|
-
callbackContext
|
|
80
|
-
)
|
|
91
|
+
"start" -> start(args.getJSONObject(0), callbackContext)
|
|
81
92
|
|
|
82
93
|
"close" -> close()
|
|
83
94
|
"addEventsListener" -> addEventsListener(callbackContext)
|
|
@@ -91,42 +102,35 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
91
102
|
"setLogLevel" -> setLogLevel(args.getInt(0))
|
|
92
103
|
"setThemeMode" -> setThemeMode(args.getInt(0))
|
|
93
104
|
"setAttribute" -> setAttribute(args.getInt(0), getStringFromJson(args.getString(1)))
|
|
94
|
-
"
|
|
105
|
+
"setDefaultPresentationDismissHandler" -> setDefaultPresentationDismissHandler(
|
|
95
106
|
callbackContext
|
|
96
107
|
)
|
|
108
|
+
"removeDefaultPresentationDismissHandler" -> removeDefaultPresentationDismissHandler(callbackContext)
|
|
97
109
|
|
|
98
110
|
"purchasedSubscription" -> purchasedSubscription(callbackContext)
|
|
99
|
-
"
|
|
100
|
-
"
|
|
111
|
+
"allowDeeplink" -> allowDeeplink(args.getBoolean(0))
|
|
112
|
+
"allowCampaigns" -> allowCampaigns(args.getBoolean(0))
|
|
113
|
+
"synchronize" -> synchronize(callbackContext)
|
|
101
114
|
"presentPresentationWithIdentifier" -> presentPresentationWithIdentifier(
|
|
102
115
|
getStringFromJson(args.getString(0)),
|
|
103
116
|
getStringFromJson(args.getString(1)),
|
|
104
|
-
args.
|
|
117
|
+
args.optJSONObject(2),
|
|
105
118
|
callbackContext
|
|
106
119
|
)
|
|
107
120
|
|
|
108
121
|
"presentPresentationForPlacement" -> presentPresentationForPlacement(
|
|
109
122
|
getStringFromJson(args.getString(0)),
|
|
110
123
|
getStringFromJson(args.getString(1)),
|
|
111
|
-
args.
|
|
124
|
+
args.optJSONObject(2),
|
|
112
125
|
callbackContext
|
|
113
126
|
)
|
|
114
127
|
|
|
115
|
-
"
|
|
128
|
+
"presentPresentationForDefault" -> presentPresentationForDefault(
|
|
116
129
|
getStringFromJson(args.getString(0)),
|
|
117
|
-
|
|
118
|
-
getStringFromJson(args.getString(2)),
|
|
119
|
-
args.getBoolean(3),
|
|
130
|
+
args.optJSONObject(1),
|
|
120
131
|
callbackContext
|
|
121
132
|
)
|
|
122
133
|
|
|
123
|
-
"presentPlanWithIdentifier" -> presentPlanWithIdentifier(
|
|
124
|
-
getStringFromJson(args.getString(0)),
|
|
125
|
-
getStringFromJson(args.getString(1)),
|
|
126
|
-
getStringFromJson(args.getString(2)),
|
|
127
|
-
args.getBoolean(3),
|
|
128
|
-
callbackContext
|
|
129
|
-
)
|
|
130
134
|
"fetchPresentation" -> fetchPresentation(
|
|
131
135
|
getStringFromJson(args.getString(0)),
|
|
132
136
|
getStringFromJson(args.getString(1)),
|
|
@@ -135,16 +139,15 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
135
139
|
)
|
|
136
140
|
"presentPresentation" -> presentPresentation(
|
|
137
141
|
args.getJSONObject(0),
|
|
138
|
-
args.
|
|
142
|
+
args.optJSONObject(1),
|
|
139
143
|
getStringFromJson(args.getString(2)),
|
|
140
144
|
callbackContext
|
|
141
145
|
)
|
|
142
|
-
"presentSubscriptions" -> presentSubscriptions()
|
|
143
146
|
"restoreAllProducts" -> restoreAllProducts(callbackContext)
|
|
144
147
|
"silentRestoreAllProducts" -> restoreAllProducts(callbackContext)
|
|
145
148
|
"userSubscriptions" -> userSubscriptions(callbackContext)
|
|
146
149
|
"userSubscriptionsHistory" -> userSubscriptionsHistory(callbackContext)
|
|
147
|
-
"
|
|
150
|
+
"handleDeeplink" -> handleDeeplink(
|
|
148
151
|
getStringFromJson(args.getString(0)),
|
|
149
152
|
callbackContext
|
|
150
153
|
)
|
|
@@ -166,11 +169,11 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
166
169
|
getStringFromJson(args.getString(2)),
|
|
167
170
|
callbackContext
|
|
168
171
|
)
|
|
169
|
-
"
|
|
170
|
-
"
|
|
172
|
+
"registerActionInterceptor" -> registerActionInterceptor(getStringFromJson(args.getString(0)), callbackContext)
|
|
173
|
+
"unregisterActionInterceptor" -> unregisterActionInterceptor(getStringFromJson(args.getString(0)))
|
|
174
|
+
"completeActionInterceptor" -> completeActionInterceptor(getStringFromJson(args.getString(0)), getStringFromJson(args.getString(1)))
|
|
171
175
|
"closePresentation" -> closePresentation(callbackContext)
|
|
172
|
-
"
|
|
173
|
-
"showPresentation" -> showPresentation()
|
|
176
|
+
"backPresentation" -> backPresentation(callbackContext)
|
|
174
177
|
"userDidConsumeSubscriptionContent" -> userDidConsumeSubscriptionContent()
|
|
175
178
|
"setUserAttributeWithString" -> setUserAttributeWithString(getStringFromJson(args.getString(0)), getStringFromJson(args.getString(1)), getStringFromJson(args.optString(2)))
|
|
176
179
|
"setUserAttributeWithBoolean" -> setUserAttributeWithBoolean(getStringFromJson(args.getString(0)), args.getBoolean(1), getStringFromJson(args.optString(2)))
|
|
@@ -203,52 +206,83 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
203
206
|
} else value
|
|
204
207
|
}
|
|
205
208
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
callbackContext: CallbackContext
|
|
215
|
-
) {
|
|
209
|
+
//region start
|
|
210
|
+
private fun logLevelFrom(raw: Any?): LogLevel {
|
|
211
|
+
return when (raw) {
|
|
212
|
+
is Number -> LogLevel.values().getOrElse(raw.toInt()) { LogLevel.ERROR }
|
|
213
|
+
is String -> LogLevel.values().firstOrNull { it.name.equals(raw, ignoreCase = true) } ?: LogLevel.ERROR
|
|
214
|
+
else -> LogLevel.ERROR
|
|
215
|
+
}
|
|
216
|
+
}
|
|
216
217
|
|
|
217
|
-
|
|
218
|
+
// v6: running mode is passed by NAME ("observer"/"full"). The old int mapping is
|
|
219
|
+
// gone; PLYRunningMode has only Observer (default) and Full.
|
|
220
|
+
private fun runningModeFrom(raw: Any?): PLYRunningMode {
|
|
221
|
+
return when (raw) {
|
|
222
|
+
is Number -> when (raw.toInt()) {
|
|
223
|
+
1 -> PLYRunningMode.Full
|
|
224
|
+
else -> PLYRunningMode.Observer
|
|
225
|
+
}
|
|
226
|
+
is String -> when (raw.lowercase(Locale.US)) {
|
|
227
|
+
"full" -> PLYRunningMode.Full
|
|
228
|
+
else -> PLYRunningMode.Observer
|
|
229
|
+
}
|
|
230
|
+
else -> PLYRunningMode.Observer
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private fun start(options: JSONObject, callbackContext: CallbackContext) {
|
|
235
|
+
val apiKey = getStringFromJson(options.optString("apiKey"))
|
|
236
|
+
if (apiKey == null) {
|
|
218
237
|
callbackContext.error("API Key is null")
|
|
219
238
|
return
|
|
220
239
|
}
|
|
221
240
|
|
|
222
|
-
val
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
241
|
+
val userId = getStringFromJson(options.optString("appUserId"))
|
|
242
|
+
val logLevel = logLevelFrom(options.opt("logLevel"))
|
|
243
|
+
val runningMode = runningModeFrom(options.opt("runningMode"))
|
|
244
|
+
|
|
245
|
+
val storesList = ArrayList<String>()
|
|
246
|
+
options.optJSONArray("stores")?.let { arr ->
|
|
247
|
+
for (i in 0 until arr.length()) {
|
|
248
|
+
try {
|
|
249
|
+
storesList.add(arr.getString(i))
|
|
250
|
+
} catch (e: JSONException) {
|
|
251
|
+
Log.e("Purchasely", "Error in store array" + e.message, e)
|
|
252
|
+
}
|
|
228
253
|
}
|
|
229
254
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (
|
|
255
|
+
|
|
256
|
+
val allowDeeplink = if (options.has("allowDeeplink")) options.optBoolean("allowDeeplink") else null
|
|
257
|
+
val allowCampaigns = if (options.has("allowCampaigns")) options.optBoolean("allowCampaigns") else null
|
|
258
|
+
val deeplink = getStringFromJson(options.optString("deeplink"))
|
|
259
|
+
val sdkVersion = getStringFromJson(options.optString("sdkVersion"))
|
|
260
|
+
|
|
233
261
|
Purchasely.Builder(cordova.context)
|
|
234
262
|
.apiKey(apiKey)
|
|
235
|
-
.stores(
|
|
263
|
+
.stores(getStoresInstances(storesList))
|
|
236
264
|
.userId(userId)
|
|
237
|
-
.runningMode(
|
|
238
|
-
.logLevel(
|
|
265
|
+
.runningMode(runningMode)
|
|
266
|
+
.logLevel(logLevel)
|
|
267
|
+
.apply {
|
|
268
|
+
allowDeeplink?.let { this.allowDeeplink(it) }
|
|
269
|
+
allowCampaigns?.let { this.allowCampaigns(it) }
|
|
270
|
+
// Cold-start deeplink: replayed automatically once started.
|
|
271
|
+
deeplink?.let { this.handleDeeplink(Uri.parse(it)) }
|
|
272
|
+
}
|
|
239
273
|
.build()
|
|
240
|
-
|
|
274
|
+
|
|
275
|
+
Purchasely.sdkBridgeVersion = sdkVersion
|
|
241
276
|
Purchasely.appTechnology = PLYAppTechnology.CORDOVA
|
|
242
|
-
Purchasely.start {
|
|
243
|
-
if (
|
|
244
|
-
callbackContext.
|
|
277
|
+
Purchasely.start { error: PLYError? ->
|
|
278
|
+
if (error == null) {
|
|
279
|
+
callbackContext.sendPluginResult(PluginResult(PluginResult.Status.OK, true))
|
|
245
280
|
} else {
|
|
246
|
-
callbackContext.error(
|
|
247
|
-
if (error != null) error.message else "Purchasely SDK not configured"
|
|
248
|
-
)
|
|
281
|
+
callbackContext.error(error.message ?: "Purchasely SDK not configured")
|
|
249
282
|
}
|
|
250
283
|
}
|
|
251
284
|
}
|
|
285
|
+
//endregion
|
|
252
286
|
|
|
253
287
|
private fun getStoresInstances(stores: List<String>): ArrayList<Store> {
|
|
254
288
|
val result = ArrayList<Store>()
|
|
@@ -279,9 +313,12 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
279
313
|
private fun close() {
|
|
280
314
|
defaultCallback = null
|
|
281
315
|
purchaseCallback = null
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
316
|
+
actionInterceptorCallbacks.clear()
|
|
317
|
+
pendingInterceptCompletions.clear()
|
|
318
|
+
displayedPresentation = null
|
|
319
|
+
// TODO(v6-verify): the global Purchasely.close() teardown was removed/lumped with
|
|
320
|
+
// presentation close in v6 (Flutter never calls it). Not invoked here to avoid a
|
|
321
|
+
// reference to a symbol that may no longer exist.
|
|
285
322
|
}
|
|
286
323
|
|
|
287
324
|
private fun addUserAttributesListener(callbackContext: CallbackContext) {
|
|
@@ -358,7 +395,7 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
358
395
|
}
|
|
359
396
|
|
|
360
397
|
private fun userLogout() {
|
|
361
|
-
Purchasely.userLogout()
|
|
398
|
+
Purchasely.userLogout(true)
|
|
362
399
|
}
|
|
363
400
|
|
|
364
401
|
private fun setLogLevel(logLevel: Int) {
|
|
@@ -373,8 +410,12 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
373
410
|
}
|
|
374
411
|
}
|
|
375
412
|
|
|
376
|
-
private fun
|
|
377
|
-
Purchasely.
|
|
413
|
+
private fun allowDeeplink(allow: Boolean) {
|
|
414
|
+
Purchasely.allowDeeplink = allow
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private fun allowCampaigns(allow: Boolean) {
|
|
418
|
+
Purchasely.allowCampaigns = allow
|
|
378
419
|
}
|
|
379
420
|
|
|
380
421
|
private fun setThemeMode(mode: Int) {
|
|
@@ -414,15 +455,16 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
414
455
|
}
|
|
415
456
|
}
|
|
416
457
|
|
|
417
|
-
|
|
458
|
+
//region Default presentation dismiss handler
|
|
459
|
+
private fun setDefaultPresentationDismissHandler(callbackContext: CallbackContext) {
|
|
418
460
|
defaultCallback = callbackContext
|
|
419
|
-
Purchasely.
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
)
|
|
461
|
+
Purchasely.setDefaultPresentationDismissHandler { outcome: PLYPresentationOutcome ->
|
|
462
|
+
val pluginResult = PluginResult(PluginResult.Status.OK, JSONObject(outcomeToMap(outcome)))
|
|
463
|
+
pluginResult.keepCallback = true
|
|
464
|
+
defaultCallback?.sendPluginResult(pluginResult)
|
|
424
465
|
}
|
|
425
466
|
}
|
|
467
|
+
//endregion
|
|
426
468
|
|
|
427
469
|
private fun purchasedSubscription(callbackContext: CallbackContext) {
|
|
428
470
|
Purchasely.purchaseListener = object : PurchaseListener {
|
|
@@ -437,72 +479,115 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
437
479
|
}
|
|
438
480
|
}
|
|
439
481
|
|
|
440
|
-
private fun synchronize() {
|
|
441
|
-
Purchasely.synchronize(
|
|
482
|
+
private fun synchronize(callbackContext: CallbackContext) {
|
|
483
|
+
Purchasely.synchronize(
|
|
484
|
+
onSuccess = {
|
|
485
|
+
callbackContext.sendPluginResult(PluginResult(PluginResult.Status.OK, true))
|
|
486
|
+
},
|
|
487
|
+
onError = { error ->
|
|
488
|
+
callbackContext.error(error?.message ?: "Synchronization failed")
|
|
489
|
+
}
|
|
490
|
+
)
|
|
442
491
|
}
|
|
443
492
|
|
|
444
493
|
private fun userDidConsumeSubscriptionContent() {
|
|
445
494
|
Purchasely.userDidConsumeSubscriptionContent()
|
|
446
495
|
}
|
|
447
496
|
|
|
497
|
+
//region Presentation lifecycle
|
|
498
|
+
// v6: the presentation activities are gone. A presentation is built via the
|
|
499
|
+
// builder DSL, preloaded, then displayed. `displayMode` maps to a PLYTransition.
|
|
448
500
|
private fun presentPresentationWithIdentifier(
|
|
449
501
|
presentationVendorId: String?,
|
|
450
502
|
contentId: String?,
|
|
451
|
-
|
|
503
|
+
transition: JSONObject?,
|
|
452
504
|
callbackContext: CallbackContext
|
|
453
505
|
) {
|
|
454
506
|
purchaseCallback = callbackContext
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
507
|
+
displayPresentation(
|
|
508
|
+
screenId = presentationVendorId,
|
|
509
|
+
placementId = null,
|
|
510
|
+
contentId = contentId,
|
|
511
|
+
transition = transition,
|
|
512
|
+
callbackContext = callbackContext
|
|
513
|
+
)
|
|
460
514
|
}
|
|
461
515
|
|
|
462
516
|
private fun presentPresentationForPlacement(
|
|
463
517
|
placementVendorId: String?,
|
|
464
518
|
contentId: String?,
|
|
465
|
-
|
|
519
|
+
transition: JSONObject?,
|
|
466
520
|
callbackContext: CallbackContext
|
|
467
521
|
) {
|
|
468
522
|
purchaseCallback = callbackContext
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
523
|
+
displayPresentation(
|
|
524
|
+
screenId = null,
|
|
525
|
+
placementId = placementVendorId,
|
|
526
|
+
contentId = contentId,
|
|
527
|
+
transition = transition,
|
|
528
|
+
callbackContext = callbackContext
|
|
529
|
+
)
|
|
474
530
|
}
|
|
475
531
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
presentationVendorId: String?,
|
|
532
|
+
// v6: present the default (audience-targeted) presentation — no placement/screen id.
|
|
533
|
+
private fun presentPresentationForDefault(
|
|
479
534
|
contentId: String?,
|
|
480
|
-
|
|
535
|
+
transition: JSONObject?,
|
|
481
536
|
callbackContext: CallbackContext
|
|
482
537
|
) {
|
|
483
538
|
purchaseCallback = callbackContext
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
539
|
+
displayPresentation(
|
|
540
|
+
screenId = null,
|
|
541
|
+
placementId = null,
|
|
542
|
+
contentId = contentId,
|
|
543
|
+
transition = transition,
|
|
544
|
+
callbackContext = callbackContext
|
|
545
|
+
)
|
|
490
546
|
}
|
|
491
547
|
|
|
492
|
-
private fun
|
|
493
|
-
|
|
494
|
-
|
|
548
|
+
private fun displayPresentation(
|
|
549
|
+
screenId: String?,
|
|
550
|
+
placementId: String?,
|
|
495
551
|
contentId: String?,
|
|
496
|
-
|
|
552
|
+
transition: JSONObject?,
|
|
497
553
|
callbackContext: CallbackContext
|
|
498
554
|
) {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
555
|
+
launch {
|
|
556
|
+
try {
|
|
557
|
+
val builder = PLYPresentationBase.builder().apply {
|
|
558
|
+
when {
|
|
559
|
+
placementId != null -> this.placementId(placementId)
|
|
560
|
+
screenId != null -> this.screenId(screenId)
|
|
561
|
+
// else: default (audience-targeted) source — no id
|
|
562
|
+
}
|
|
563
|
+
contentId(contentId)
|
|
564
|
+
// v6: stream the presentation lifecycle to the JS callback via keep-alive
|
|
565
|
+
// envelopes; the dismiss outcome is delivered as the final (non-kept)
|
|
566
|
+
// result by sendPurchaseResult.
|
|
567
|
+
onPresented { presentation, error ->
|
|
568
|
+
val env = mutableMapOf<String, Any?>("event" to "presented")
|
|
569
|
+
presentation?.let { env["presentation"] = presentationToMap(it) }
|
|
570
|
+
error?.let { env["error"] = it.message }
|
|
571
|
+
val r = PluginResult(PluginResult.Status.OK, JSONObject(env))
|
|
572
|
+
r.keepCallback = true
|
|
573
|
+
callbackContext.sendPluginResult(r)
|
|
574
|
+
}
|
|
575
|
+
onCloseRequested {
|
|
576
|
+
val r = PluginResult(PluginResult.Status.OK, JSONObject(mapOf("event" to "closeRequested")))
|
|
577
|
+
r.keepCallback = true
|
|
578
|
+
callbackContext.sendPluginResult(r)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
val loaded = builder.build().preload()
|
|
582
|
+
displayedPresentation = loaded
|
|
583
|
+
loaded.display(cordova.activity, transitionFromMap(transition)) { outcome ->
|
|
584
|
+
sendPurchaseResult(outcome)
|
|
585
|
+
}
|
|
586
|
+
} catch (t: Throwable) {
|
|
587
|
+
if (purchaseCallback === callbackContext) purchaseCallback = null
|
|
588
|
+
callbackContext.error(t.message ?: "Unable to present presentation")
|
|
589
|
+
}
|
|
590
|
+
}
|
|
506
591
|
}
|
|
507
592
|
|
|
508
593
|
private fun fetchPresentation(
|
|
@@ -510,43 +595,31 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
510
595
|
presentationId: String?,
|
|
511
596
|
contentId: String?,
|
|
512
597
|
callbackContext: CallbackContext) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
val map = presentation.toMap().mapValues {
|
|
523
|
-
val value = it.value
|
|
524
|
-
if(value is PLYPresentationType) value.ordinal
|
|
525
|
-
else value
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
val mutableMap = map.toMutableMap().apply {
|
|
529
|
-
//this["metadata"] = presentation.metadata?.toMap()
|
|
530
|
-
this["plans"] = (this["plans"] as List<PLYPresentationPlan>).map { it.toMap() }
|
|
598
|
+
launch {
|
|
599
|
+
try {
|
|
600
|
+
val builder = PLYPresentationBase.builder().apply {
|
|
601
|
+
when {
|
|
602
|
+
placementId != null -> this.placementId(placementId)
|
|
603
|
+
presentationId != null -> this.screenId(presentationId)
|
|
604
|
+
// else: neither id → the default (audience-targeted) presentation
|
|
605
|
+
}
|
|
606
|
+
contentId(contentId)
|
|
531
607
|
}
|
|
608
|
+
val loaded = builder.build().preload()
|
|
609
|
+
// Synthetic handle so presentPresentation can re-display this preloaded presentation.
|
|
610
|
+
val handle = "ply_fetch_${System.nanoTime()}"
|
|
611
|
+
loadedPresentations[handle] = loaded
|
|
612
|
+
val map = presentationToMap(loaded).toMutableMap()
|
|
613
|
+
map["id"] = handle
|
|
532
614
|
callbackContext.success(JSONObject(map))
|
|
615
|
+
} catch (t: Throwable) {
|
|
616
|
+
callbackContext.error(t.message ?: "Unable to fetch presentation")
|
|
533
617
|
}
|
|
534
|
-
if(error != null) callbackContext.error(error.message ?: "Unable to fetch presentation")
|
|
535
618
|
}
|
|
536
619
|
}
|
|
537
620
|
|
|
538
|
-
// Delete when available in Android SDK
|
|
539
|
-
fun PLYPresentationPlan.toMap() : Map<String, String?> {
|
|
540
|
-
return mapOf(
|
|
541
|
-
Pair("planVendorId", planVendorId),
|
|
542
|
-
Pair("storeProductId", storeProductId),
|
|
543
|
-
Pair("basePlanId", basePlanId),
|
|
544
|
-
//Pair("offerId", offerId)
|
|
545
|
-
)
|
|
546
|
-
}
|
|
547
|
-
|
|
548
621
|
private fun presentPresentation(presentationMap: JSONObject?,
|
|
549
|
-
|
|
622
|
+
transition: JSONObject?,
|
|
550
623
|
loadingBackgroundColor: String?,
|
|
551
624
|
callbackContext: CallbackContext) {
|
|
552
625
|
if (presentationMap == null) {
|
|
@@ -554,112 +627,130 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
554
627
|
return
|
|
555
628
|
}
|
|
556
629
|
|
|
557
|
-
val
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
if(presentation == null) {
|
|
630
|
+
val handle = if (presentationMap.has("id")) presentationMap.optString("id") else null
|
|
631
|
+
val loaded = handle?.let { loadedPresentations[it] }
|
|
632
|
+
if (loaded == null) {
|
|
562
633
|
callbackContext.error("presentation cannot be found")
|
|
563
634
|
return
|
|
564
635
|
}
|
|
565
636
|
|
|
566
637
|
purchaseCallback = callbackContext
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
putExtra("presentation", presentation)
|
|
577
|
-
}
|
|
578
|
-
activity.startActivity(intent)
|
|
638
|
+
displayedPresentation = loaded
|
|
639
|
+
|
|
640
|
+
// TODO(v6-verify): loadingBackgroundColor cannot be applied to an already-preloaded
|
|
641
|
+
// presentation in v6 (colors are builder options set before preload). The re-display
|
|
642
|
+
// path delivers the dismiss outcome only; onPresented/onCloseRequested are wired on the
|
|
643
|
+
// direct present* methods (builder-seeded before preload).
|
|
644
|
+
cordova.activity?.runOnUiThread {
|
|
645
|
+
loaded.display(cordova.activity, transitionFromMap(transition)) { outcome ->
|
|
646
|
+
sendPurchaseResult(outcome)
|
|
579
647
|
}
|
|
580
648
|
}
|
|
581
|
-
|
|
582
649
|
}
|
|
583
650
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
651
|
+
// v6: stop receiving default (campaign/deeplink) presentation dismiss outcomes.
|
|
652
|
+
private fun removeDefaultPresentationDismissHandler(callbackContext: CallbackContext) {
|
|
653
|
+
// Android's setter is non-null; install a no-op handler and drop the JS callback
|
|
654
|
+
// so default (campaign/deeplink) dismiss outcomes are no longer forwarded.
|
|
655
|
+
Purchasely.setDefaultPresentationDismissHandler { }
|
|
656
|
+
defaultCallback = null
|
|
657
|
+
callbackContext.success()
|
|
588
658
|
}
|
|
589
659
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
660
|
+
// Maps the JS transition object to a v6 PLYTransition. Shape:
|
|
661
|
+
// { type, dismissible?, width?: {type,value}, height?: {type,value} }
|
|
662
|
+
// width is popin-only; height drives drawer+popin. Null → surface default.
|
|
663
|
+
private fun transitionFromMap(map: JSONObject?): PLYTransition? {
|
|
664
|
+
if (map == null) return null
|
|
665
|
+
val type = when (map.optString("type")) {
|
|
666
|
+
"fullScreen" -> PLYTransitionType.FULLSCREEN
|
|
667
|
+
"push" -> PLYTransitionType.PUSH
|
|
668
|
+
"modal" -> PLYTransitionType.MODAL
|
|
669
|
+
"drawer" -> PLYTransitionType.DRAWER
|
|
670
|
+
"popin" -> PLYTransitionType.POPIN
|
|
671
|
+
"inlinePaywall" -> PLYTransitionType.INLINE_PAYWALL
|
|
672
|
+
else -> return null
|
|
595
673
|
}
|
|
674
|
+
return PLYTransition(
|
|
675
|
+
type = type,
|
|
676
|
+
width = dimensionFromMap(map.optJSONObject("width")),
|
|
677
|
+
height = dimensionFromMap(map.optJSONObject("height")),
|
|
678
|
+
dismissible = map.optBoolean("dismissible", true)
|
|
679
|
+
)
|
|
596
680
|
}
|
|
597
681
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
val data = list[i]
|
|
606
|
-
val map = HashMap(data.toMap())
|
|
607
|
-
map["plan"] = transformPlanToMap(data.plan)
|
|
608
|
-
map["product"] = data.product.toMap()
|
|
609
|
-
if (data.data.storeType == StoreType.GOOGLE_PLAY_STORE) {
|
|
610
|
-
map["subscriptionSource"] = StoreType.GOOGLE_PLAY_STORE.ordinal
|
|
611
|
-
} else if (data.data.storeType == StoreType.AMAZON_APP_STORE) {
|
|
612
|
-
map["subscriptionSource"] = StoreType.AMAZON_APP_STORE.ordinal
|
|
613
|
-
} else if (data.data.storeType == StoreType.HUAWEI_APP_GALLERY) {
|
|
614
|
-
map["subscriptionSource"] = StoreType.HUAWEI_APP_GALLERY.ordinal
|
|
615
|
-
} else if (data.data.storeType == StoreType.APPLE_APP_STORE) {
|
|
616
|
-
map["subscriptionSource"] = StoreType.APPLE_APP_STORE.ordinal
|
|
617
|
-
}
|
|
618
|
-
result.put(JSONObject(map))
|
|
619
|
-
}
|
|
620
|
-
callbackContext.success(result)
|
|
621
|
-
}
|
|
682
|
+
// { type: 'pixel'|'percentage', value: Number } → PLYTransitionDimension. Null when absent.
|
|
683
|
+
private fun dimensionFromMap(map: JSONObject?): PLYTransitionDimension? {
|
|
684
|
+
if (map == null || !map.has("value")) return null
|
|
685
|
+
val value = map.optDouble("value").toFloat()
|
|
686
|
+
val type = if (map.optString("type") == "pixel") PLYDimensionType.PIXEL else PLYDimensionType.PERCENTAGE
|
|
687
|
+
return PLYTransitionDimension(type, value)
|
|
688
|
+
}
|
|
622
689
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
690
|
+
private fun closePresentation(callbackContext: CallbackContext) {
|
|
691
|
+
Purchasely.closeAllScreens()
|
|
692
|
+
displayedPresentation = null
|
|
693
|
+
callbackContext.success()
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
private fun backPresentation(callbackContext: CallbackContext) {
|
|
697
|
+
cordova.activity?.runOnUiThread { displayedPresentation?.back() }
|
|
698
|
+
callbackContext.success()
|
|
699
|
+
}
|
|
700
|
+
//endregion
|
|
701
|
+
|
|
702
|
+
private fun restoreAllProducts(callbackContext: CallbackContext) {
|
|
703
|
+
Purchasely.restoreAllProducts(
|
|
704
|
+
onSuccess = {
|
|
705
|
+
callbackContext.success()
|
|
706
|
+
},
|
|
707
|
+
onError = { plyError: PLYError? ->
|
|
708
|
+
callbackContext.error(plyError?.message)
|
|
626
709
|
}
|
|
627
710
|
)
|
|
628
711
|
}
|
|
629
712
|
|
|
713
|
+
private fun userSubscriptions(callbackContext: CallbackContext) {
|
|
714
|
+
launch {
|
|
715
|
+
try {
|
|
716
|
+
val list = Purchasely.userSubscriptions(true)
|
|
717
|
+
callbackContext.success(transformSubscriptionsToJson(list))
|
|
718
|
+
} catch (e: Exception) {
|
|
719
|
+
callbackContext.error(e.message)
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
630
724
|
private fun userSubscriptionsHistory(callbackContext: CallbackContext) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
map["product"] = data.product.toMap()
|
|
641
|
-
if (data.data.storeType == StoreType.GOOGLE_PLAY_STORE) {
|
|
642
|
-
map["subscriptionSource"] = StoreType.GOOGLE_PLAY_STORE.ordinal
|
|
643
|
-
} else if (data.data.storeType == StoreType.AMAZON_APP_STORE) {
|
|
644
|
-
map["subscriptionSource"] = StoreType.AMAZON_APP_STORE.ordinal
|
|
645
|
-
} else if (data.data.storeType == StoreType.HUAWEI_APP_GALLERY) {
|
|
646
|
-
map["subscriptionSource"] = StoreType.HUAWEI_APP_GALLERY.ordinal
|
|
647
|
-
} else if (data.data.storeType == StoreType.APPLE_APP_STORE) {
|
|
648
|
-
map["subscriptionSource"] = StoreType.APPLE_APP_STORE.ordinal
|
|
649
|
-
}
|
|
650
|
-
result.put(JSONObject(map))
|
|
651
|
-
}
|
|
652
|
-
callbackContext.success(result)
|
|
653
|
-
}
|
|
725
|
+
launch {
|
|
726
|
+
try {
|
|
727
|
+
val list = Purchasely.userSubscriptionsHistory(true)
|
|
728
|
+
callbackContext.success(transformSubscriptionsToJson(list))
|
|
729
|
+
} catch (e: Exception) {
|
|
730
|
+
callbackContext.error(e.message)
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
654
734
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
735
|
+
private fun transformSubscriptionsToJson(list: List<PLYSubscriptionData>): JSONArray {
|
|
736
|
+
val result = JSONArray()
|
|
737
|
+
for (data in list) {
|
|
738
|
+
val map = HashMap(data.data.toMap())
|
|
739
|
+
map["plan"] = transformPlanToMap(data.plan)
|
|
740
|
+
map["product"] = data.product.toMap()
|
|
741
|
+
map["subscriptionSource"] = when (data.data.storeType) {
|
|
742
|
+
StoreType.GOOGLE_PLAY_STORE -> StoreType.GOOGLE_PLAY_STORE.ordinal
|
|
743
|
+
StoreType.AMAZON_APP_STORE -> StoreType.AMAZON_APP_STORE.ordinal
|
|
744
|
+
StoreType.HUAWEI_APP_GALLERY -> StoreType.HUAWEI_APP_GALLERY.ordinal
|
|
745
|
+
StoreType.APPLE_APP_STORE -> StoreType.APPLE_APP_STORE.ordinal
|
|
746
|
+
else -> null
|
|
658
747
|
}
|
|
659
|
-
|
|
748
|
+
result.put(JSONObject(map))
|
|
749
|
+
}
|
|
750
|
+
return result
|
|
660
751
|
}
|
|
661
752
|
|
|
662
|
-
private fun
|
|
753
|
+
private fun handleDeeplink(deeplink: String?, callbackContext: CallbackContext) {
|
|
663
754
|
if (deeplink == null) {
|
|
664
755
|
callbackContext.error("Deeplink must not be null")
|
|
665
756
|
return
|
|
@@ -668,25 +759,24 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
668
759
|
callbackContext.sendPluginResult(
|
|
669
760
|
PluginResult(
|
|
670
761
|
PluginResult.Status.OK,
|
|
671
|
-
Purchasely.
|
|
762
|
+
Purchasely.handleDeeplink(uri, cordova.activity)
|
|
672
763
|
)
|
|
673
764
|
)
|
|
674
765
|
}
|
|
675
766
|
|
|
676
767
|
private fun allProducts(callbackContext: CallbackContext) {
|
|
677
|
-
|
|
678
|
-
|
|
768
|
+
launch {
|
|
769
|
+
try {
|
|
770
|
+
val list = Purchasely.allProducts()
|
|
679
771
|
val result = JSONArray()
|
|
680
|
-
for (
|
|
681
|
-
result.put(JSONObject(
|
|
772
|
+
for (product in list) {
|
|
773
|
+
result.put(JSONObject(product.toMap()))
|
|
682
774
|
}
|
|
683
775
|
callbackContext.success(result)
|
|
776
|
+
} catch (e: Exception) {
|
|
777
|
+
callbackContext.error(e.message)
|
|
684
778
|
}
|
|
685
|
-
|
|
686
|
-
override fun onFailure(throwable: Throwable) {
|
|
687
|
-
callbackContext.error(throwable.message)
|
|
688
|
-
}
|
|
689
|
-
})
|
|
779
|
+
}
|
|
690
780
|
}
|
|
691
781
|
|
|
692
782
|
private fun productWithIdentifier(vendorId: String?, callbackContext: CallbackContext) {
|
|
@@ -694,19 +784,18 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
694
784
|
callbackContext.error("No product found with $vendorId")
|
|
695
785
|
return
|
|
696
786
|
}
|
|
697
|
-
|
|
698
|
-
|
|
787
|
+
launch {
|
|
788
|
+
try {
|
|
789
|
+
val product: PLYProduct? = Purchasely.product(vendorId)
|
|
699
790
|
if (product != null) {
|
|
700
791
|
callbackContext.success(JSONObject(product.toMap()))
|
|
701
792
|
} else {
|
|
702
793
|
callbackContext.error("No product found with $vendorId")
|
|
703
794
|
}
|
|
795
|
+
} catch (e: Exception) {
|
|
796
|
+
callbackContext.error(e.message)
|
|
704
797
|
}
|
|
705
|
-
|
|
706
|
-
override fun onFailure(throwable: Throwable) {
|
|
707
|
-
callbackContext.error(throwable.message)
|
|
708
|
-
}
|
|
709
|
-
})
|
|
798
|
+
}
|
|
710
799
|
}
|
|
711
800
|
|
|
712
801
|
private fun planWithIdentifier(vendorId: String?, callbackContext: CallbackContext) {
|
|
@@ -714,19 +803,18 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
714
803
|
callbackContext.error("No plan found with $vendorId")
|
|
715
804
|
return
|
|
716
805
|
}
|
|
717
|
-
|
|
718
|
-
|
|
806
|
+
launch {
|
|
807
|
+
try {
|
|
808
|
+
val plan: PLYPlan? = Purchasely.plan(vendorId)
|
|
719
809
|
if (plan != null) {
|
|
720
810
|
callbackContext.success(JSONObject(transformPlanToMap(plan)))
|
|
721
811
|
} else {
|
|
722
812
|
callbackContext.error("No plan found with $vendorId")
|
|
723
813
|
}
|
|
814
|
+
} catch (e: Exception) {
|
|
815
|
+
callbackContext.error(e.message)
|
|
724
816
|
}
|
|
725
|
-
|
|
726
|
-
override fun onFailure(throwable: Throwable) {
|
|
727
|
-
callbackContext.error(throwable.message)
|
|
728
|
-
}
|
|
729
|
-
})
|
|
817
|
+
}
|
|
730
818
|
}
|
|
731
819
|
|
|
732
820
|
private fun purchaseWithPlanVendorId(
|
|
@@ -740,114 +828,129 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
740
828
|
return
|
|
741
829
|
}
|
|
742
830
|
|
|
743
|
-
|
|
744
|
-
|
|
831
|
+
launch {
|
|
832
|
+
try {
|
|
833
|
+
val plyPlan: PLYPlan? = Purchasely.plan(planVendorId)
|
|
745
834
|
if (plyPlan != null) {
|
|
746
835
|
val offer = plyPlan.promoOffers.firstOrNull { it.vendorId == offerId }
|
|
747
836
|
Purchasely.purchase(cordova.activity, plyPlan, offer, contentId,
|
|
748
|
-
{ plyPlan1: PLYPlan? ->
|
|
837
|
+
onSuccess = { plyPlan1: PLYPlan? ->
|
|
749
838
|
callbackContext.success(JSONObject(transformPlanToMap(plyPlan1)))
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
|
|
839
|
+
},
|
|
840
|
+
onError = { plyError: PLYError? ->
|
|
841
|
+
callbackContext.error(plyError?.message)
|
|
842
|
+
})
|
|
753
843
|
} else {
|
|
754
844
|
callbackContext.error("No plan found with $planVendorId")
|
|
755
845
|
}
|
|
846
|
+
} catch (e: Exception) {
|
|
847
|
+
callbackContext.error(e.message)
|
|
756
848
|
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
757
851
|
|
|
758
|
-
|
|
759
|
-
|
|
852
|
+
//region Action interceptor
|
|
853
|
+
// v6: register a JS handler for a single action kind. Each intercept emits an
|
|
854
|
+
// event carrying a unique callbackId; JS replies via completeActionInterceptor.
|
|
855
|
+
private fun registerActionInterceptor(kind: String?, callbackContext: CallbackContext) {
|
|
856
|
+
if (kind == null) return
|
|
857
|
+
val clazz = PLYPresentationAction.fromValue(kind)?.java
|
|
858
|
+
if (clazz == null) {
|
|
859
|
+
Log.w("Purchasely", "Unknown interceptor kind: $kind")
|
|
860
|
+
return
|
|
861
|
+
}
|
|
862
|
+
actionInterceptorCallbacks[kind] = callbackContext
|
|
863
|
+
Purchasely.interceptAction(clazz, object : PLYActionInterceptorCallback {
|
|
864
|
+
override fun onIntercept(
|
|
865
|
+
info: PLYInterceptorInfo,
|
|
866
|
+
action: PLYPresentationAction,
|
|
867
|
+
completion: (PLYInterceptResult) -> Unit
|
|
868
|
+
) {
|
|
869
|
+
val callback = actionInterceptorCallbacks[kind]
|
|
870
|
+
if (callback == null) {
|
|
871
|
+
completion(PLYInterceptResult.NOT_HANDLED)
|
|
872
|
+
return
|
|
873
|
+
}
|
|
874
|
+
// Unique id per intercepted invocation so concurrent intercepts resolve independently.
|
|
875
|
+
val invocationId = "$kind#${++interceptorInvocationCounter}"
|
|
876
|
+
pendingInterceptCompletions[invocationId] = completion
|
|
877
|
+
|
|
878
|
+
val map = hashMapOf<String, Any?>(
|
|
879
|
+
"action" to kind,
|
|
880
|
+
"callbackId" to invocationId,
|
|
881
|
+
"info" to mapOf(
|
|
882
|
+
// TODO(v6-verify): presentationId derived from info.presentation?.screenId;
|
|
883
|
+
// v6 PLYInterceptorInfo exposes {contentId, presentation}, not a raw id.
|
|
884
|
+
"contentId" to info.contentId,
|
|
885
|
+
"presentationId" to info.presentation?.screenId
|
|
886
|
+
),
|
|
887
|
+
"parameters" to (actionPayloadToMap(action) ?: emptyMap<String, Any?>())
|
|
888
|
+
)
|
|
889
|
+
val result = PluginResult(PluginResult.Status.OK, JSONObject(map))
|
|
890
|
+
result.keepCallback = true
|
|
891
|
+
callback.sendPluginResult(result)
|
|
760
892
|
}
|
|
761
893
|
})
|
|
762
894
|
}
|
|
763
895
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
896
|
+
// v6: stop intercepting a single action kind.
|
|
897
|
+
private fun unregisterActionInterceptor(kind: String?) {
|
|
898
|
+
if (kind == null) return
|
|
899
|
+
val clazz = PLYPresentationAction.fromValue(kind)?.java ?: return
|
|
900
|
+
actionInterceptorCallbacks.remove(kind)
|
|
901
|
+
runCatching { Purchasely.removeActionInterceptor(clazz) }.onFailure {
|
|
902
|
+
Log.w("Purchasely", "removeActionInterceptor($kind) failed: ${it.message}")
|
|
903
|
+
}
|
|
904
|
+
}
|
|
770
905
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
"vendorId" to parameters.offer?.vendorId,
|
|
777
|
-
"storeOfferId" to parameters.offer?.storeOfferId
|
|
906
|
+
private fun actionPayloadToMap(action: PLYPresentationAction): Map<String, Any?>? {
|
|
907
|
+
return when (action) {
|
|
908
|
+
is PLYPresentationAction.Navigate -> mapOf(
|
|
909
|
+
"url" to action.url?.toString(),
|
|
910
|
+
"title" to action.title
|
|
778
911
|
)
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
val result = PluginResult(
|
|
788
|
-
PluginResult.Status.OK,
|
|
789
|
-
JSONObject(
|
|
790
|
-
hashMapOf<String, Any?>(
|
|
791
|
-
Pair("info", mapOf(
|
|
792
|
-
Pair("contentId", info?.contentId),
|
|
793
|
-
Pair("presentationId", info?.presentationId),
|
|
794
|
-
Pair("placementId", info?.placementId),
|
|
795
|
-
Pair("abTestId", info?.abTestId),
|
|
796
|
-
Pair("abTestVariantId", info?.abTestVariantId)
|
|
797
|
-
)),
|
|
798
|
-
Pair("action", action.value),
|
|
799
|
-
Pair("parameters", parametersForCordova.filterNot { it.value == null })
|
|
912
|
+
is PLYPresentationAction.Purchase -> mapOf(
|
|
913
|
+
"plan" to transformPlanToMap(action.plan),
|
|
914
|
+
"subscriptionOffer" to action.subscriptionOffer?.toMap(),
|
|
915
|
+
"offer" to action.offer?.let { offer ->
|
|
916
|
+
mapOf(
|
|
917
|
+
"vendorId" to offer.vendorId,
|
|
918
|
+
"storeOfferId" to offer.storeOfferId
|
|
800
919
|
)
|
|
801
|
-
|
|
920
|
+
}
|
|
802
921
|
)
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
private fun onProcessAction(processAction: Boolean) {
|
|
815
|
-
val activityHandler = interceptorActivity?.get() ?: productActivity?.activity?.get() ?: cordova.activity
|
|
816
|
-
activityHandler?.runOnUiThread {
|
|
817
|
-
paywallActionHandler?.invoke(processAction)
|
|
818
|
-
|
|
819
|
-
interceptorActivity?.clear()
|
|
820
|
-
interceptorActivity = null
|
|
922
|
+
is PLYPresentationAction.Close -> mapOf("closeReason" to action.closeReason.value)
|
|
923
|
+
is PLYPresentationAction.CloseAll -> mapOf("closeReason" to action.closeReason.value)
|
|
924
|
+
is PLYPresentationAction.OpenPresentation -> mapOf("presentationId" to action.presentationId)
|
|
925
|
+
is PLYPresentationAction.OpenPlacement -> mapOf("placementId" to action.placementId)
|
|
926
|
+
is PLYPresentationAction.WebCheckout -> mapOf(
|
|
927
|
+
"url" to action.url?.toString(),
|
|
928
|
+
"clientReferenceId" to action.clientReferenceId,
|
|
929
|
+
"queryParameterKey" to action.queryParameterKey,
|
|
930
|
+
"webCheckoutProvider" to action.webCheckoutProvider?.name
|
|
931
|
+
)
|
|
932
|
+
else -> null
|
|
821
933
|
}
|
|
822
934
|
}
|
|
823
935
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
if (
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
)
|
|
835
|
-
}
|
|
936
|
+
// v6: JS reports how an intercepted action was handled, keyed by the invocation id
|
|
937
|
+
// carried on the intercept event. Result is "success"/"failed"/"notHandled".
|
|
938
|
+
private fun completeActionInterceptor(callbackId: String?, result: String?) {
|
|
939
|
+
if (callbackId == null) return
|
|
940
|
+
val completion = pendingInterceptCompletions.remove(callbackId) ?: return
|
|
941
|
+
val plyResult = when (result) {
|
|
942
|
+
"success" -> PLYInterceptResult.SUCCESS
|
|
943
|
+
"failed" -> PLYInterceptResult.FAILED
|
|
944
|
+
else -> PLYInterceptResult.NOT_HANDLED
|
|
836
945
|
}
|
|
837
|
-
|
|
838
|
-
|
|
946
|
+
val activity = cordova.activity
|
|
947
|
+
if (activity != null) {
|
|
948
|
+
activity.runOnUiThread { completion.invoke(plyResult) }
|
|
949
|
+
} else {
|
|
950
|
+
completion.invoke(plyResult)
|
|
839
951
|
}
|
|
840
952
|
}
|
|
841
|
-
|
|
842
|
-
private fun hidePresentation() {
|
|
843
|
-
val cordovaActivity = cordova.activity
|
|
844
|
-
val activity = productActivity?.activity?.get() ?: cordovaActivity
|
|
845
|
-
cordovaActivity?.startActivity(
|
|
846
|
-
Intent(activity, cordovaActivity::class.java).apply {
|
|
847
|
-
flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
|
|
848
|
-
}
|
|
849
|
-
)
|
|
850
|
-
}
|
|
953
|
+
//endregion
|
|
851
954
|
|
|
852
955
|
fun setUserAttributeWithStringArray(key: String?, value: JSONArray?, legalBasisString: String?) {
|
|
853
956
|
if(key == null || value == null) return
|
|
@@ -1033,14 +1136,15 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
1033
1136
|
}
|
|
1034
1137
|
|
|
1035
1138
|
private fun isEligibleForIntroOffer(planId: String?, callbackContext: CallbackContext) {
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1139
|
+
launch {
|
|
1140
|
+
try {
|
|
1141
|
+
val plan = Purchasely.plan(planId ?: "")
|
|
1142
|
+
val eligible = plan?.isEligibleToOffer(null) ?: false
|
|
1143
|
+
callbackContext.sendPluginResult(PluginResult(PluginResult.Status.OK, eligible))
|
|
1144
|
+
} catch (e: Exception) {
|
|
1145
|
+
callbackContext.error(e.message ?: "Unable to fetch plan")
|
|
1042
1146
|
}
|
|
1043
|
-
|
|
1147
|
+
}
|
|
1044
1148
|
}
|
|
1045
1149
|
|
|
1046
1150
|
private fun signPromotionalOffer(storeProductId: String?, storeOfferId: String?, callbackContext: CallbackContext) {
|
|
@@ -1074,103 +1178,88 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
1074
1178
|
Purchasely.debugMode = enabled
|
|
1075
1179
|
}
|
|
1076
1180
|
|
|
1077
|
-
class ProductActivity {
|
|
1078
|
-
var presentationId: String? = null
|
|
1079
|
-
var placementId: String? = null
|
|
1080
|
-
var productId: String? = null
|
|
1081
|
-
var planId: String? = null
|
|
1082
|
-
var contentId: String? = null
|
|
1083
|
-
var presentation: PLYPresentation? = null
|
|
1084
|
-
var isFullScreen = false
|
|
1085
|
-
var loadingBackgroundColor: String? = null
|
|
1086
|
-
var activity: WeakReference<PLYProductActivity>? = null
|
|
1087
|
-
fun relaunch(cordova: CordovaInterface): Boolean {
|
|
1088
|
-
var backgroundActivity: PLYProductActivity? = null
|
|
1089
|
-
if (activity != null) {
|
|
1090
|
-
backgroundActivity = activity?.get()
|
|
1091
|
-
}
|
|
1092
|
-
return if (backgroundActivity != null && !backgroundActivity.isFinishing
|
|
1093
|
-
&& !backgroundActivity.isDestroyed
|
|
1094
|
-
) {
|
|
1095
|
-
val intent = Intent(cordova.activity, PLYProductActivity::class.java)
|
|
1096
|
-
intent.putExtra("presentation", presentation)
|
|
1097
|
-
intent.putExtra("presentationId", presentationId)
|
|
1098
|
-
intent.putExtra("placementId", placementId)
|
|
1099
|
-
intent.putExtra("productId", productId)
|
|
1100
|
-
intent.putExtra("planId", planId)
|
|
1101
|
-
intent.putExtra("contentId", contentId)
|
|
1102
|
-
intent.putExtra("isFullScreen", isFullScreen)
|
|
1103
|
-
intent.putExtra("background_color", loadingBackgroundColor)
|
|
1104
|
-
intent.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
|
|
1105
|
-
cordova.activity.startActivity(intent)
|
|
1106
|
-
true
|
|
1107
|
-
} else {
|
|
1108
|
-
val intent = PLYProductActivity.newIntent(cordova.activity)
|
|
1109
|
-
intent.putExtra("presentation", presentation)
|
|
1110
|
-
intent.putExtra("presentationId", presentationId)
|
|
1111
|
-
intent.putExtra("placementId", placementId)
|
|
1112
|
-
intent.putExtra("productId", productId)
|
|
1113
|
-
intent.putExtra("planId", planId)
|
|
1114
|
-
intent.putExtra("contentId", contentId)
|
|
1115
|
-
intent.putExtra("isFullScreen", isFullScreen)
|
|
1116
|
-
intent.putExtra("background_color", loadingBackgroundColor)
|
|
1117
|
-
cordova.activity.startActivity(intent)
|
|
1118
|
-
false
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
1181
|
companion object {
|
|
1124
1182
|
var defaultCallback: CallbackContext? = null
|
|
1125
1183
|
var purchaseCallback: CallbackContext? = null
|
|
1126
1184
|
var eventsCallback: CallbackContext? = null
|
|
1127
1185
|
var attributesCallback: CallbackContext? = null
|
|
1128
|
-
var productActivity: ProductActivity? = null
|
|
1129
|
-
|
|
1130
|
-
var interceptorActivity: WeakReference<Activity>? = null
|
|
1131
1186
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
if (result == PLYProductViewResult.PURCHASED) {
|
|
1140
|
-
productViewResult = PLYProductViewResult.PURCHASED.ordinal
|
|
1141
|
-
} else if (result == PLYProductViewResult.CANCELLED) {
|
|
1142
|
-
productViewResult = PLYProductViewResult.CANCELLED.ordinal
|
|
1143
|
-
} else if (result == PLYProductViewResult.RESTORED) {
|
|
1144
|
-
productViewResult = PLYProductViewResult.RESTORED.ordinal
|
|
1145
|
-
}
|
|
1146
|
-
val map = HashMap<String?, Any?>()
|
|
1147
|
-
map["result"] = productViewResult
|
|
1148
|
-
map["plan"] = transformPlanToMap(plan)
|
|
1149
|
-
if (purchaseCallback != null) {
|
|
1150
|
-
purchaseCallback?.success(JSONObject(map))
|
|
1187
|
+
// Resolves a present*/presentPresentation call at dismissal. One-shot for the
|
|
1188
|
+
// purchase callback; falls back to the (repeatable) default dismiss handler.
|
|
1189
|
+
fun sendPurchaseResult(outcome: PLYPresentationOutcome) {
|
|
1190
|
+
val json = JSONObject(outcomeToMap(outcome))
|
|
1191
|
+
val oneShot = purchaseCallback
|
|
1192
|
+
if (oneShot != null) {
|
|
1193
|
+
oneShot.success(json)
|
|
1151
1194
|
purchaseCallback = null
|
|
1152
|
-
|
|
1153
|
-
|
|
1195
|
+
return
|
|
1196
|
+
}
|
|
1197
|
+
defaultCallback?.let {
|
|
1198
|
+
val pluginResult = PluginResult(PluginResult.Status.OK, json)
|
|
1154
1199
|
pluginResult.keepCallback = true
|
|
1155
|
-
|
|
1200
|
+
it.sendPluginResult(pluginResult)
|
|
1156
1201
|
}
|
|
1157
1202
|
}
|
|
1158
1203
|
|
|
1204
|
+
// Serializes a v6 PLYPresentationOutcome to the wire contract. `result` is kept as an
|
|
1205
|
+
// int (PurchaseResult 0/1/2) for back-compat with the pre-6.0 JS layer.
|
|
1206
|
+
fun outcomeToMap(outcome: PLYPresentationOutcome): Map<String?, Any?> {
|
|
1207
|
+
val result = when (outcome.purchaseResult?.name?.lowercase(Locale.US)) {
|
|
1208
|
+
"purchased" -> 0
|
|
1209
|
+
"cancelled" -> 1
|
|
1210
|
+
"restored" -> 2
|
|
1211
|
+
else -> 1 // no purchase (plain dismiss) → cancelled
|
|
1212
|
+
}
|
|
1213
|
+
val map = HashMap<String?, Any?>()
|
|
1214
|
+
map["result"] = result
|
|
1215
|
+
// v6: also expose the string purchaseResult ('purchased'|'cancelled'|'restored'|null)
|
|
1216
|
+
// alongside the legacy int `result`, matching the Flutter outcome contract.
|
|
1217
|
+
map["purchaseResult"] = outcome.purchaseResult?.name?.lowercase(Locale.US)
|
|
1218
|
+
map["plan"] = transformPlanToMap(outcome.plan)
|
|
1219
|
+
map["closeReason"] = outcome.closeReason?.value
|
|
1220
|
+
map["error"] = outcome.error?.message
|
|
1221
|
+
map["presentation"] = outcome.presentation?.let { presentationToMap(it) }
|
|
1222
|
+
return map
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
fun presentationToMap(p: PLYPresentationBase.Loaded): Map<String, Any?> {
|
|
1226
|
+
return mapOf(
|
|
1227
|
+
"screenId" to p.screenId,
|
|
1228
|
+
"placementId" to p.placementId,
|
|
1229
|
+
"contentId" to p.contentId,
|
|
1230
|
+
"audienceId" to p.audienceId,
|
|
1231
|
+
"abTestId" to p.abTestId,
|
|
1232
|
+
"abTestVariantId" to p.abTestVariantId,
|
|
1233
|
+
"campaignId" to p.campaignId,
|
|
1234
|
+
"flowId" to p.flowId,
|
|
1235
|
+
"language" to p.language,
|
|
1236
|
+
"type" to p.type.ordinal,
|
|
1237
|
+
"height" to p.height,
|
|
1238
|
+
"plans" to p.plans.map { presentationPlanToMap(it) }
|
|
1239
|
+
)
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
private fun presentationPlanToMap(plan: PLYPresentationPlan): Map<String, Any?> {
|
|
1243
|
+
return mapOf(
|
|
1244
|
+
"planVendorId" to plan.planVendorId,
|
|
1245
|
+
"storeProductId" to plan.storeProductId,
|
|
1246
|
+
"basePlanId" to plan.basePlanId,
|
|
1247
|
+
"offerId" to plan.storeOfferId
|
|
1248
|
+
)
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1159
1251
|
private fun transformPlanToMap(plan: PLYPlan?): Map<String?, Any?> {
|
|
1160
1252
|
if (plan == null) return HashMap()
|
|
1161
1253
|
val map = HashMap(plan.toMap())
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
map["type"] = DistributionType.NON_RENEWING_SUBSCRIPTION.ordinal
|
|
1170
|
-
} else if (plan.type == DistributionType.UNKNOWN) {
|
|
1171
|
-
map["type"] = DistributionType.UNKNOWN.ordinal
|
|
1254
|
+
map["type"] = when (plan.type) {
|
|
1255
|
+
DistributionType.RENEWING_SUBSCRIPTION -> DistributionType.RENEWING_SUBSCRIPTION.ordinal
|
|
1256
|
+
DistributionType.NON_RENEWING_SUBSCRIPTION -> DistributionType.NON_RENEWING_SUBSCRIPTION.ordinal
|
|
1257
|
+
DistributionType.CONSUMABLE -> DistributionType.CONSUMABLE.ordinal
|
|
1258
|
+
DistributionType.NON_CONSUMABLE -> DistributionType.NON_CONSUMABLE.ordinal
|
|
1259
|
+
DistributionType.UNKNOWN -> DistributionType.UNKNOWN.ordinal
|
|
1260
|
+
else -> null
|
|
1172
1261
|
}
|
|
1173
|
-
map["isEligibleForIntroOffer"] = plan.
|
|
1262
|
+
map["isEligibleForIntroOffer"] = plan.isEligibleToOffer(null)
|
|
1174
1263
|
return map
|
|
1175
1264
|
}
|
|
1176
1265
|
}
|
|
@@ -1223,4 +1312,4 @@ class PurchaselyPlugin : CordovaPlugin() {
|
|
|
1223
1312
|
BATCH_CUSTOM_USER_ID: 20,
|
|
1224
1313
|
*/
|
|
1225
1314
|
}
|
|
1226
|
-
}
|
|
1315
|
+
}
|