@billdog.io/react-native 1.0.0-beta.2
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/BillDogReactNative.podspec +30 -0
- package/LICENSE +21 -0
- package/README.md +330 -0
- package/android/build.gradle +99 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogABTestModule.kt +317 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogAnalyticsModule.kt +389 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogModule.kt +817 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogPaywallViewManager.kt +383 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogSurveyModule.kt +427 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogVirtualCurrencyModule.kt +407 -0
- package/android/src/main/kotlin/io/billdog/virtualcurrency/BillDogVirtualCurrencyPackage.kt +44 -0
- package/dist/AdvancedPaywallCache.d.ts +70 -0
- package/dist/AdvancedPaywallCache.js +344 -0
- package/dist/BillDog.d.ts +255 -0
- package/dist/BillDog.js +918 -0
- package/dist/BillDogConfiguration.d.ts +145 -0
- package/dist/BillDogConfiguration.js +229 -0
- package/dist/ComponentRenderer.d.ts +17 -0
- package/dist/ComponentRenderer.js +886 -0
- package/dist/LayoutEngine.d.ts +101 -0
- package/dist/LayoutEngine.js +394 -0
- package/dist/LocalizationResolver.d.ts +99 -0
- package/dist/LocalizationResolver.js +266 -0
- package/dist/NativePaywallView.d.ts +36 -0
- package/dist/NativePaywallView.js +59 -0
- package/dist/PaywallCache.d.ts +74 -0
- package/dist/PaywallCache.js +293 -0
- package/dist/PaywallStateManager.d.ts +62 -0
- package/dist/PaywallStateManager.js +147 -0
- package/dist/PaywallView.d.ts +42 -0
- package/dist/PaywallView.js +275 -0
- package/dist/ProductVariableProvider.d.ts +60 -0
- package/dist/ProductVariableProvider.js +239 -0
- package/dist/VariableResolver.d.ts +120 -0
- package/dist/VariableResolver.js +379 -0
- package/dist/WebViewPaywall.d.ts +75 -0
- package/dist/WebViewPaywall.js +360 -0
- package/dist/animations/presets.d.ts +49 -0
- package/dist/animations/presets.js +367 -0
- package/dist/components/AccoladeCpsComponent.d.ts +13 -0
- package/dist/components/AccoladeCpsComponent.js +248 -0
- package/dist/components/AccordionComponent.d.ts +10 -0
- package/dist/components/AccordionComponent.js +187 -0
- package/dist/components/AnimatedWrapper.d.ts +35 -0
- package/dist/components/AnimatedWrapper.js +277 -0
- package/dist/components/CarouselComponent.d.ts +23 -0
- package/dist/components/CarouselComponent.js +484 -0
- package/dist/components/ChartCurveComponent.d.ts +13 -0
- package/dist/components/ChartCurveComponent.js +333 -0
- package/dist/components/CheckboxComponent.d.ts +9 -0
- package/dist/components/CheckboxComponent.js +172 -0
- package/dist/components/ConfettiComponent.d.ts +9 -0
- package/dist/components/ConfettiComponent.js +172 -0
- package/dist/components/CountdownComponent.d.ts +16 -0
- package/dist/components/CountdownComponent.js +414 -0
- package/dist/components/DelayedXComponent.d.ts +12 -0
- package/dist/components/DelayedXComponent.js +240 -0
- package/dist/components/DrawerModalComponent.d.ts +20 -0
- package/dist/components/DrawerModalComponent.js +286 -0
- package/dist/components/GradientView.d.ts +38 -0
- package/dist/components/GradientView.js +126 -0
- package/dist/components/HapticFeedbackComponent.d.ts +16 -0
- package/dist/components/HapticFeedbackComponent.js +64 -0
- package/dist/components/HorizontalProductsComponent.d.ts +17 -0
- package/dist/components/HorizontalProductsComponent.js +284 -0
- package/dist/components/MultiChoiceGridComponent.d.ts +13 -0
- package/dist/components/MultiChoiceGridComponent.js +234 -0
- package/dist/components/NPSScaleComponent.d.ts +16 -0
- package/dist/components/NPSScaleComponent.js +260 -0
- package/dist/components/NavigationComponent.d.ts +26 -0
- package/dist/components/NavigationComponent.js +302 -0
- package/dist/components/OAuthButtonComponent.d.ts +20 -0
- package/dist/components/OAuthButtonComponent.js +237 -0
- package/dist/components/ProgressLoaderComponent.d.ts +13 -0
- package/dist/components/ProgressLoaderComponent.js +308 -0
- package/dist/components/ProgressStepperComponent.d.ts +18 -0
- package/dist/components/ProgressStepperComponent.js +228 -0
- package/dist/components/SegmentedPickerComponent.d.ts +10 -0
- package/dist/components/SegmentedPickerComponent.js +221 -0
- package/dist/components/ShareButtonsComponent.d.ts +21 -0
- package/dist/components/ShareButtonsComponent.js +265 -0
- package/dist/components/ShareCodeComponent.d.ts +16 -0
- package/dist/components/ShareCodeComponent.js +274 -0
- package/dist/components/SingleChoiceGridComponent.d.ts +13 -0
- package/dist/components/SingleChoiceGridComponent.js +210 -0
- package/dist/components/SliderComponent.d.ts +9 -0
- package/dist/components/SliderComponent.js +263 -0
- package/dist/components/StarRatingInputComponent.d.ts +20 -0
- package/dist/components/StarRatingInputComponent.js +198 -0
- package/dist/components/StickerCompositeComponent.d.ts +13 -0
- package/dist/components/StickerCompositeComponent.js +136 -0
- package/dist/components/TabsComponent.d.ts +19 -0
- package/dist/components/TabsComponent.js +431 -0
- package/dist/components/TextInputComponent.d.ts +18 -0
- package/dist/components/TextInputComponent.js +227 -0
- package/dist/components/TimelineComponent.d.ts +19 -0
- package/dist/components/TimelineComponent.js +104 -0
- package/dist/components/ToggleComponent.d.ts +14 -0
- package/dist/components/ToggleComponent.js +132 -0
- package/dist/components/VerticalProductsComponent.d.ts +17 -0
- package/dist/components/VerticalProductsComponent.js +311 -0
- package/dist/dynamicValues.d.ts +36 -0
- package/dist/dynamicValues.js +142 -0
- package/dist/filters/dateFilters.d.ts +50 -0
- package/dist/filters/dateFilters.js +390 -0
- package/dist/filters/index.d.ts +18 -0
- package/dist/filters/index.js +253 -0
- package/dist/hooks/useOfferings.d.ts +76 -0
- package/dist/hooks/useOfferings.js +99 -0
- package/dist/hooks/usePaywall.d.ts +27 -0
- package/dist/hooks/usePaywall.js +114 -0
- package/dist/hooks/usePlacement.d.ts +34 -0
- package/dist/hooks/usePlacement.js +89 -0
- package/dist/hooks/useSubscription.d.ts +40 -0
- package/dist/hooks/useSubscription.js +97 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +198 -0
- package/dist/models/BillDogError.d.ts +40 -0
- package/dist/models/BillDogError.js +11 -0
- package/dist/models/CacheFetchPolicy.d.ts +8 -0
- package/dist/models/CacheFetchPolicy.js +4 -0
- package/dist/models/CustomProduct.d.ts +32 -0
- package/dist/models/CustomProduct.js +15 -0
- package/dist/models/CustomerInfo.d.ts +17 -0
- package/dist/models/CustomerInfo.js +11 -0
- package/dist/models/Delegate.d.ts +20 -0
- package/dist/models/Delegate.js +11 -0
- package/dist/models/EntitlementInfo.d.ts +19 -0
- package/dist/models/EntitlementInfo.js +2 -0
- package/dist/models/Placement.d.ts +54 -0
- package/dist/models/Placement.js +15 -0
- package/dist/models/PurchaseParams.d.ts +39 -0
- package/dist/models/PurchaseParams.js +60 -0
- package/dist/models/PurchaseResult.d.ts +30 -0
- package/dist/models/PurchaseResult.js +15 -0
- package/dist/models/RestoreResult.d.ts +30 -0
- package/dist/models/RestoreResult.js +22 -0
- package/dist/models/RestoredItem.d.ts +26 -0
- package/dist/models/RestoredItem.js +13 -0
- package/dist/modules/ABTest.d.ts +65 -0
- package/dist/modules/ABTest.js +113 -0
- package/dist/modules/Analytics.d.ts +91 -0
- package/dist/modules/Analytics.js +182 -0
- package/dist/modules/Survey.d.ts +65 -0
- package/dist/modules/Survey.js +82 -0
- package/dist/modules/VirtualCurrency.d.ts +63 -0
- package/dist/modules/VirtualCurrency.js +147 -0
- package/dist/murmur.d.ts +23 -0
- package/dist/murmur.js +104 -0
- package/dist/notifications/LiveActivitiesManager.d.ts +47 -0
- package/dist/notifications/LiveActivitiesManager.js +106 -0
- package/dist/notifications/NotificationScheduler.d.ts +55 -0
- package/dist/notifications/NotificationScheduler.js +199 -0
- package/dist/notifications/PurchaseNotificationHandler.d.ts +24 -0
- package/dist/notifications/PurchaseNotificationHandler.js +55 -0
- package/dist/notifications/useDeviceToken.d.ts +36 -0
- package/dist/notifications/useDeviceToken.js +97 -0
- package/dist/notifications/useLiveActivity.d.ts +39 -0
- package/dist/notifications/useLiveActivity.js +104 -0
- package/dist/review/ReviewAnalytics.d.ts +34 -0
- package/dist/review/ReviewAnalytics.js +73 -0
- package/dist/review/ReviewManager.d.ts +83 -0
- package/dist/review/ReviewManager.js +339 -0
- package/dist/review/ReviewTimingManager.d.ts +82 -0
- package/dist/review/ReviewTimingManager.js +224 -0
- package/dist/survey-core/types.d.ts +215 -0
- package/dist/survey-core/types.js +8 -0
- package/dist/types.d.ts +438 -0
- package/dist/types.js +158 -0
- package/dist/utils/ApiClient.d.ts +65 -0
- package/dist/utils/ApiClient.js +132 -0
- package/dist/utils/CSSConverter.d.ts +49 -0
- package/dist/utils/CSSConverter.js +470 -0
- package/dist/utils/animations.d.ts +41 -0
- package/dist/utils/animations.js +194 -0
- package/dist/utils/calendarIntegration.d.ts +36 -0
- package/dist/utils/calendarIntegration.js +89 -0
- package/dist/utils/configurationResolver.d.ts +23 -0
- package/dist/utils/configurationResolver.js +78 -0
- package/dist/utils/deviceDetection.d.ts +38 -0
- package/dist/utils/deviceDetection.js +59 -0
- package/dist/utils/hapticFeedback.d.ts +17 -0
- package/dist/utils/hapticFeedback.js +53 -0
- package/dist/utils/iconMapper.d.ts +26 -0
- package/dist/utils/iconMapper.js +113 -0
- package/dist/utils/notificationPermissions.d.ts +13 -0
- package/dist/utils/notificationPermissions.js +51 -0
- package/dist/utils/priceFormatting.d.ts +19 -0
- package/dist/utils/priceFormatting.js +99 -0
- package/dist/utils/shareSheet.d.ts +17 -0
- package/dist/utils/shareSheet.js +42 -0
- package/dist/utils/subscriptionUpgrade.d.ts +84 -0
- package/dist/utils/subscriptionUpgrade.js +100 -0
- package/dist/valueUtils.d.ts +48 -0
- package/dist/valueUtils.js +129 -0
- package/dist/xFlowTags.d.ts +18 -0
- package/dist/xFlowTags.js +385 -0
- package/ios/BillDogABTest.m +50 -0
- package/ios/BillDogABTest.swift +188 -0
- package/ios/BillDogAnalytics.m +105 -0
- package/ios/BillDogAnalytics.swift +324 -0
- package/ios/BillDogBridge.m +222 -0
- package/ios/BillDogBridge.swift +715 -0
- package/ios/BillDogLiveActivities.m +41 -0
- package/ios/BillDogLiveActivities.swift +104 -0
- package/ios/BillDogPaywallViewManager.m +39 -0
- package/ios/BillDogPaywallViewManager.swift +361 -0
- package/ios/BillDogSurvey.m +34 -0
- package/ios/BillDogSurvey.swift +257 -0
- package/ios/BillDogVirtualCurrencyBridge.m +63 -0
- package/ios/BillDogVirtualCurrencyBridge.swift +511 -0
- package/junit.xml +25 -0
- package/package.json +61 -0
- package/react-native.config.js +13 -0
- package/src/AdvancedPaywallCache.ts +433 -0
- package/src/BillDog.ts +983 -0
- package/src/BillDogConfiguration.ts +318 -0
- package/src/ComponentRenderer.tsx +1325 -0
- package/src/LayoutEngine.ts +459 -0
- package/src/LocalizationResolver.ts +311 -0
- package/src/NativePaywallView.tsx +132 -0
- package/src/PaywallCache.ts +358 -0
- package/src/PaywallStateManager.ts +154 -0
- package/src/PaywallView.tsx +328 -0
- package/src/ProductVariableProvider.ts +282 -0
- package/src/VariableResolver.ts +516 -0
- package/src/WebViewPaywall.tsx +454 -0
- package/src/animations/presets.ts +421 -0
- package/src/components/AccoladeCpsComponent.tsx +259 -0
- package/src/components/AccordionComponent.tsx +213 -0
- package/src/components/AnimatedWrapper.tsx +316 -0
- package/src/components/CarouselComponent.tsx +615 -0
- package/src/components/ChartCurveComponent.tsx +452 -0
- package/src/components/CheckboxComponent.tsx +185 -0
- package/src/components/ConfettiComponent.tsx +178 -0
- package/src/components/CountdownComponent.tsx +454 -0
- package/src/components/DelayedXComponent.tsx +266 -0
- package/src/components/DrawerModalComponent.tsx +324 -0
- package/src/components/GradientView.tsx +129 -0
- package/src/components/HapticFeedbackComponent.tsx +39 -0
- package/src/components/HorizontalProductsComponent.tsx +329 -0
- package/src/components/MultiChoiceGridComponent.tsx +265 -0
- package/src/components/NPSScaleComponent.tsx +294 -0
- package/src/components/NavigationComponent.tsx +329 -0
- package/src/components/OAuthButtonComponent.tsx +266 -0
- package/src/components/ProgressLoaderComponent.tsx +366 -0
- package/src/components/ProgressStepperComponent.tsx +250 -0
- package/src/components/SegmentedPickerComponent.tsx +245 -0
- package/src/components/ShareButtonsComponent.tsx +299 -0
- package/src/components/ShareCodeComponent.tsx +303 -0
- package/src/components/SingleChoiceGridComponent.tsx +229 -0
- package/src/components/SliderComponent.tsx +280 -0
- package/src/components/StarRatingInputComponent.tsx +223 -0
- package/src/components/StickerCompositeComponent.tsx +168 -0
- package/src/components/TabsComponent.tsx +496 -0
- package/src/components/TextInputComponent.tsx +258 -0
- package/src/components/TimelineComponent.tsx +129 -0
- package/src/components/ToggleComponent.tsx +142 -0
- package/src/components/VerticalProductsComponent.tsx +357 -0
- package/src/dynamicValues.ts +180 -0
- package/src/filters/dateFilters.ts +411 -0
- package/src/filters/index.ts +281 -0
- package/src/hooks/useOfferings.ts +192 -0
- package/src/hooks/usePaywall.ts +154 -0
- package/src/hooks/usePlacement.ts +143 -0
- package/src/hooks/useSubscription.ts +154 -0
- package/src/index.ts +245 -0
- package/src/models/BillDogError.ts +24 -0
- package/src/models/CacheFetchPolicy.ts +13 -0
- package/src/models/CustomProduct.ts +33 -0
- package/src/models/CustomerInfo.ts +26 -0
- package/src/models/Delegate.ts +22 -0
- package/src/models/EntitlementInfo.ts +27 -0
- package/src/models/Placement.ts +50 -0
- package/src/models/PurchaseParams.ts +81 -0
- package/src/models/PurchaseResult.ts +32 -0
- package/src/models/RestoreResult.ts +27 -0
- package/src/models/RestoredItem.ts +25 -0
- package/src/modules/ABTest.ts +131 -0
- package/src/modules/Analytics.ts +227 -0
- package/src/modules/Survey.ts +126 -0
- package/src/modules/VirtualCurrency.ts +223 -0
- package/src/murmur.ts +102 -0
- package/src/notifications/LiveActivitiesManager.ts +140 -0
- package/src/notifications/NotificationScheduler.ts +292 -0
- package/src/notifications/PurchaseNotificationHandler.ts +67 -0
- package/src/notifications/useDeviceToken.ts +116 -0
- package/src/notifications/useLiveActivity.ts +125 -0
- package/src/review/ReviewAnalytics.ts +89 -0
- package/src/review/ReviewManager.ts +355 -0
- package/src/review/ReviewTimingManager.ts +265 -0
- package/src/survey-core/types.ts +244 -0
- package/src/types/ambient.d.ts +9 -0
- package/src/types.ts +680 -0
- package/src/utils/ApiClient.ts +163 -0
- package/src/utils/CSSConverter.ts +552 -0
- package/src/utils/animations.ts +232 -0
- package/src/utils/calendarIntegration.ts +113 -0
- package/src/utils/configurationResolver.ts +106 -0
- package/src/utils/deviceDetection.ts +83 -0
- package/src/utils/hapticFeedback.ts +55 -0
- package/src/utils/iconMapper.ts +102 -0
- package/src/utils/notificationPermissions.ts +48 -0
- package/src/utils/priceFormatting.ts +119 -0
- package/src/utils/shareSheet.ts +55 -0
- package/src/utils/subscriptionUpgrade.ts +183 -0
- package/src/valueUtils.ts +108 -0
- package/src/xFlowTags.ts +463 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
package io.billdog.reactnative
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.net.Uri
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import com.facebook.react.bridge.Arguments
|
|
7
|
+
import com.facebook.react.bridge.Promise
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
10
|
+
import com.facebook.react.bridge.ReactMethod
|
|
11
|
+
import com.facebook.react.bridge.ReadableArray
|
|
12
|
+
import com.facebook.react.bridge.ReadableMap
|
|
13
|
+
import com.facebook.react.bridge.WritableArray
|
|
14
|
+
import com.facebook.react.bridge.WritableMap
|
|
15
|
+
import io.billdog.core.BillDog
|
|
16
|
+
import io.billdog.core.BillDogError
|
|
17
|
+
import io.billdog.core.InterfaceStyle
|
|
18
|
+
import io.billdog.core.PresentationResult
|
|
19
|
+
import io.billdog.core.PurchaseResult
|
|
20
|
+
import io.billdog.core.RestoreResult
|
|
21
|
+
import io.billdog.core.models.CustomerInfo
|
|
22
|
+
import io.billdog.core.models.EntitlementInfo
|
|
23
|
+
import kotlinx.coroutines.CoroutineScope
|
|
24
|
+
import kotlinx.coroutines.Dispatchers
|
|
25
|
+
import kotlinx.coroutines.SupervisorJob
|
|
26
|
+
import kotlinx.coroutines.cancel
|
|
27
|
+
import kotlinx.coroutines.launch
|
|
28
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* BillDog — core React Native native module for Android (`NativeModules.BillDog`).
|
|
32
|
+
*
|
|
33
|
+
* Thin bridge: every `@ReactMethod` here delegates to the native BillDog Android
|
|
34
|
+
* SDK (`io.billdog.core.BillDog`, a Kotlin `object`) rather than reimplementing
|
|
35
|
+
* logic in JS. The authoritative JS contract is `src/BillDog.ts`; each
|
|
36
|
+
* `native.<method>(...)` call there maps to the identically-named method below.
|
|
37
|
+
*
|
|
38
|
+
* ⚠️ VERIFICATION: this file cannot be compiled in this environment — there is
|
|
39
|
+
* no Android/Gradle toolchain present. It needs a Gradle/Android build
|
|
40
|
+
* (`./gradlew :billdog_react-native:compileDebugKotlin` or the example app
|
|
41
|
+
* build) to type-check against the resolved `io.billdog:billdog-core` +
|
|
42
|
+
* kotlinx-coroutines artifacts declared in android/build.gradle. Every native
|
|
43
|
+
* symbol below is cited to its source file:line in billdog-core.
|
|
44
|
+
*
|
|
45
|
+
* Activity acquisition: unlike the Flutter plugin (ActivityAware), the current
|
|
46
|
+
* Activity is read from `currentActivity` (ReactContextBaseJavaModule). For the
|
|
47
|
+
* Activity-required calls (registerPlacement, showManageSubscriptions,
|
|
48
|
+
* showInAppMessages) a null currentActivity rejects with "NO_ACTIVITY".
|
|
49
|
+
*/
|
|
50
|
+
class BillDogModule(reactContext: ReactApplicationContext) :
|
|
51
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
52
|
+
|
|
53
|
+
private val TAG = "BillDog"
|
|
54
|
+
|
|
55
|
+
/** Suspend calls are launched here; cancelled in invalidate()/onCatalystInstanceDestroy(). */
|
|
56
|
+
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
|
57
|
+
|
|
58
|
+
/** Guards registerOptionalModules() — `BillDog.use()` does not de-dupe. */
|
|
59
|
+
private val modulesRegistered = AtomicBoolean(false)
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* PUR-364: JS-side PurchaseController active flag. Full JS-PurchaseController
|
|
63
|
+
* bridging (routing native purchase() through a JS-driven EXTERNAL flow) is
|
|
64
|
+
* DEFERRED — for now we only remember whether the JS facade has a host
|
|
65
|
+
* controller registered. When true, the JS facade drives the transaction
|
|
66
|
+
* itself and never calls native.purchase(), so this is purely informational.
|
|
67
|
+
*/
|
|
68
|
+
@Volatile
|
|
69
|
+
private var purchaseControllerActive: Boolean = false
|
|
70
|
+
|
|
71
|
+
override fun getName(): String = "BillDog"
|
|
72
|
+
|
|
73
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
override fun invalidate() {
|
|
76
|
+
scope.cancel()
|
|
77
|
+
super.invalidate()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@Deprecated("Deprecated in RN; kept for older RN versions that call it instead of invalidate()")
|
|
81
|
+
override fun onCatalystInstanceDestroy() {
|
|
82
|
+
scope.cancel()
|
|
83
|
+
@Suppress("DEPRECATION")
|
|
84
|
+
super.onCatalystInstanceDestroy()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Coroutine helpers ──────────────────────────────────────────────────
|
|
88
|
+
// Launched on Dispatchers.Main so the suspend body resumes on the main
|
|
89
|
+
// thread and promise.resolve/reject are invoked there.
|
|
90
|
+
|
|
91
|
+
/** Run a suspend [block] that produces a resolve value; map any throwable → promise.reject. */
|
|
92
|
+
private fun launch(promise: Promise, block: suspend () -> Any?) {
|
|
93
|
+
scope.launch {
|
|
94
|
+
try {
|
|
95
|
+
promise.resolve(block())
|
|
96
|
+
} catch (e: Throwable) {
|
|
97
|
+
promise.reject("BILLDOG_ERROR", e.message, e)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Configuration ──────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* native.configure(apiKey, options) → BillDog.configure(context, apiKey, config)
|
|
106
|
+
* (BillDog.kt:263). This is what makes the native SDK usable — without it the
|
|
107
|
+
* native singleton is unconfigured and every delegating call fails. The RN
|
|
108
|
+
* `Context` comes from `reactContext.applicationContext`.
|
|
109
|
+
*/
|
|
110
|
+
@ReactMethod
|
|
111
|
+
fun configure(apiKey: String, options: ReadableMap?, promise: Promise) {
|
|
112
|
+
try {
|
|
113
|
+
val enableLogging =
|
|
114
|
+
options?.let { if (it.hasKey("enableLogging")) it.getBoolean("enableLogging") else true } ?: true
|
|
115
|
+
val config = io.billdog.core.BillDogConfig(apiKey = apiKey, enableLogging = enableLogging)
|
|
116
|
+
BillDog.configure(reactApplicationContext.applicationContext, apiKey, config)
|
|
117
|
+
registerOptionalModules()
|
|
118
|
+
promise.resolve(true)
|
|
119
|
+
} catch (e: Throwable) {
|
|
120
|
+
promise.reject("CONFIGURE_ERROR", e.message, e)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Register the optional native submodules with the core SDK.
|
|
126
|
+
*
|
|
127
|
+
* WHY THIS EXISTS: billdog-core keeps an OPT-IN module registry —
|
|
128
|
+
* `BillDog.use(module)` (BillDog.kt:415) is the only way to populate it, and
|
|
129
|
+
* `BillDog.configure()` does NOT auto-register anything. Every bridge module
|
|
130
|
+
* here resolves its manager per-call out of `BillDog.getModules()`
|
|
131
|
+
* (BillDog.kt:427), so with an empty registry `BillDogAnalyticsModule` /
|
|
132
|
+
* `BillDogABTestModule` resolve `null` and silently NO-OP: `track()` reports
|
|
133
|
+
* success while `BillDog.trackEvent` drops the event at
|
|
134
|
+
* `findAnalyticsModule()?.track(event)` (BillDog.kt:948), and `getDistinctId()`
|
|
135
|
+
* returns JS `null` even though the Kotlin signature is non-nullable `String`.
|
|
136
|
+
* Verified 2026-07-21 on-device: the RN harness emitted events that NEVER
|
|
137
|
+
* reached ClickHouse.
|
|
138
|
+
*
|
|
139
|
+
* A native Kotlin host does this itself (see the Android Inspector's
|
|
140
|
+
* BillDogBootstrap.kt:145-165) — but an RN app has no Kotlin bootstrap, so the
|
|
141
|
+
* bridge IS the host and must do it here. Mirrors that reference sequence.
|
|
142
|
+
*
|
|
143
|
+
* Idempotent: `use()` is a bare `registeredModules.add()` with NO de-dupe, so
|
|
144
|
+
* a second configure() would register a second analytics module and every
|
|
145
|
+
* event would be sent twice.
|
|
146
|
+
*/
|
|
147
|
+
private fun registerOptionalModules() {
|
|
148
|
+
if (!modulesRegistered.compareAndSet(false, true)) return
|
|
149
|
+
val app = reactApplicationContext.applicationContext as? android.app.Application
|
|
150
|
+
try {
|
|
151
|
+
// Analytics. `use()` injects the network client + config, but the event
|
|
152
|
+
// QUEUE is installed separately: track() ends in `val db = database ?:
|
|
153
|
+
// return@launch` (BillDogAnalytics.kt:690) and `database` is only set by
|
|
154
|
+
// initializeAutoTracking(). Registering without this call still drops
|
|
155
|
+
// every event, so both steps are REQUIRED.
|
|
156
|
+
val analytics = io.billdog.analytics.BillDogAnalytics()
|
|
157
|
+
BillDog.use(analytics)
|
|
158
|
+
if (app != null) {
|
|
159
|
+
analytics.initializeAutoTracking(app)
|
|
160
|
+
} else {
|
|
161
|
+
Log.w(TAG, "No Application context — analytics persistence disabled; events will not queue")
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// A/B test manager — BillDogABTestModule resolves this out of the registry.
|
|
165
|
+
BillDog.use(io.billdog.abtest.BillDogABTest(reactApplicationContext.applicationContext))
|
|
166
|
+
} catch (e: Throwable) {
|
|
167
|
+
// Never fail configure() because an optional module is absent from the
|
|
168
|
+
// host's classpath — the corresponding bridge just keeps no-oping.
|
|
169
|
+
modulesRegistered.set(false)
|
|
170
|
+
Log.w(TAG, "Optional module registration failed: ${e.message}", e)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── PUR-350 / PUR-364 — Unified purchase API ───────────────────────────
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* native.purchase({ ...params, source }) — JS passes a PurchaseParams object.
|
|
178
|
+
* We route on `productId`. Delegates to BillDog.purchase(activity, productId)
|
|
179
|
+
* — BillDog.kt:700 (suspend). Custom-product / package overloads and full
|
|
180
|
+
* PurchaseController EXTERNAL bridging are DEFERRED (see purchaseControllerActive).
|
|
181
|
+
*/
|
|
182
|
+
@ReactMethod
|
|
183
|
+
fun purchase(params: ReadableMap, promise: Promise) {
|
|
184
|
+
val productId = if (params.hasKey("productId")) params.getString("productId") else null
|
|
185
|
+
if (productId.isNullOrBlank()) {
|
|
186
|
+
promise.reject("INVALID_ARGS", "Missing productId", null)
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
val activity: Activity? = currentActivity
|
|
190
|
+
launch(promise) {
|
|
191
|
+
// Activity-less overload (BillDog.kt:680) is available but only the
|
|
192
|
+
// Activity-aware entry point (BillDog.kt:700) can launch the Play
|
|
193
|
+
// billing sheet; fall back to it when no Activity is attached.
|
|
194
|
+
val result = if (activity != null) {
|
|
195
|
+
BillDog.purchase(activity, productId)
|
|
196
|
+
} else {
|
|
197
|
+
BillDog.purchase(productId)
|
|
198
|
+
}
|
|
199
|
+
serializePurchaseResult(result)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* native.setPurchaseControllerActive(active) — no native equivalent.
|
|
205
|
+
* Stores the flag; full JS-PurchaseController bridging is DEFERRED.
|
|
206
|
+
*/
|
|
207
|
+
@ReactMethod
|
|
208
|
+
fun setPurchaseControllerActive(active: Boolean, promise: Promise) {
|
|
209
|
+
purchaseControllerActive = active
|
|
210
|
+
Log.d(TAG, "setPurchaseControllerActive($active) — flag stored; JS-controller bridging deferred")
|
|
211
|
+
promise.resolve(null)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** native.restore() → BillDog.restorePurchases() — BillDog.kt:822 (suspend). */
|
|
215
|
+
@ReactMethod
|
|
216
|
+
fun restore(promise: Promise) {
|
|
217
|
+
launch(promise) { serializeRestoreResult(BillDog.restorePurchases()) }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── PUR-141/224 — CustomerInfo ─────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* native.getCustomerInfo(policy) → BillDog.getCustomerInfo(forceRefresh)
|
|
224
|
+
* — BillDog.kt:902 (suspend, Result<CustomerInfo>). Only "FETCH_CURRENT"
|
|
225
|
+
* maps to forceRefresh=true (mirrors the Flutter plugin).
|
|
226
|
+
*/
|
|
227
|
+
@ReactMethod
|
|
228
|
+
fun getCustomerInfo(policy: String?, promise: Promise) {
|
|
229
|
+
val forceRefresh = policy == "FETCH_CURRENT"
|
|
230
|
+
launch(promise) {
|
|
231
|
+
val info = BillDog.getCustomerInfo(forceRefresh).getOrElse { throw it }
|
|
232
|
+
serializeCustomerInfo(info)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** native.invalidateCustomerInfoCache() → BillDog.invalidateCustomerInfoCache() — BillDog.kt:2516 (suspend). */
|
|
237
|
+
@ReactMethod
|
|
238
|
+
fun invalidateCustomerInfoCache(promise: Promise) {
|
|
239
|
+
launch(promise) { BillDog.invalidateCustomerInfoCache(); null }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** native.clearCache() → BillDog.clearCache() — BillDog.kt:1490. */
|
|
243
|
+
@ReactMethod
|
|
244
|
+
fun clearCache(promise: Promise) {
|
|
245
|
+
try {
|
|
246
|
+
BillDog.clearCache()
|
|
247
|
+
promise.resolve(null)
|
|
248
|
+
} catch (e: Throwable) {
|
|
249
|
+
promise.reject("BILLDOG_ERROR", e.message, e)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ── Store capability surface ────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
/** native.canMakePayments() → BillDog.canMakePayments() — BillDog.kt:1296 (Boolean). */
|
|
256
|
+
@ReactMethod
|
|
257
|
+
fun canMakePayments(promise: Promise) {
|
|
258
|
+
try {
|
|
259
|
+
promise.resolve(BillDog.canMakePayments())
|
|
260
|
+
} catch (e: Throwable) {
|
|
261
|
+
promise.reject("BILLDOG_ERROR", e.message, e)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* native.presentCodeRedemptionSheet() — NO Android equivalent (iOS-only App
|
|
267
|
+
* Store promo-code sheet). Resolve null (documented no-op).
|
|
268
|
+
*/
|
|
269
|
+
@ReactMethod
|
|
270
|
+
fun presentCodeRedemptionSheet(promise: Promise) {
|
|
271
|
+
Log.d(TAG, "presentCodeRedemptionSheet() — iOS-only; no Android equivalent; no-op")
|
|
272
|
+
promise.resolve(null)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* native.checkTrialOrIntroDiscountEligibility(productIds) — NO native
|
|
277
|
+
* equivalent in billdog-core (no matching method). Resolve a map of
|
|
278
|
+
* productId → "unknown" (mirrors the JS fallback shape).
|
|
279
|
+
*/
|
|
280
|
+
@ReactMethod
|
|
281
|
+
fun checkTrialOrIntroDiscountEligibility(productIds: ReadableArray, promise: Promise) {
|
|
282
|
+
val out = Arguments.createMap()
|
|
283
|
+
for (i in 0 until productIds.size()) {
|
|
284
|
+
productIds.getString(i)?.let { out.putString(it, "unknown") }
|
|
285
|
+
}
|
|
286
|
+
Log.d(TAG, "checkTrialOrIntroDiscountEligibility — no native equivalent; returning 'unknown'")
|
|
287
|
+
promise.resolve(out)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* native.subscribeStorefront() — NO native equivalent (iOS-only storefront
|
|
292
|
+
* country observer; Play exposes no country API). Documented no-op.
|
|
293
|
+
*/
|
|
294
|
+
@ReactMethod
|
|
295
|
+
fun subscribeStorefront(promise: Promise) {
|
|
296
|
+
Log.d(TAG, "subscribeStorefront() — iOS-only; no Android equivalent; no-op")
|
|
297
|
+
promise.resolve(null)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ── PUR-362 — Self-hosted proxy ─────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
/** native.setProxyURL(url) → BillDog.setProxyURL(url) — BillDog.kt:1349. */
|
|
303
|
+
@ReactMethod
|
|
304
|
+
fun setProxyURL(url: String?, promise: Promise) {
|
|
305
|
+
BillDog.setProxyURL(url)
|
|
306
|
+
promise.resolve(null)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* native.getProxyURL() → BillDog.getProxyURL() — BillDog.kt:1355 (String?).
|
|
311
|
+
* BillDog.ts reads this synchronously (`native.getProxyURL() ?? null`), so it must be a
|
|
312
|
+
* blocking-synchronous method (a Promise would return a truthy object, never the value).
|
|
313
|
+
*/
|
|
314
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
315
|
+
fun getProxyURL(): String? = BillDog.getProxyURL()
|
|
316
|
+
|
|
317
|
+
// ── Consent (cross-SDK contract) ────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
/** native.setConsentRequired(required) → BillDog.setConsentRequired(required) — BillDog.kt:1485. */
|
|
320
|
+
@ReactMethod
|
|
321
|
+
fun setConsentRequired(required: Boolean, promise: Promise) {
|
|
322
|
+
BillDog.setConsentRequired(required)
|
|
323
|
+
promise.resolve(null)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* native.setConsent(purpose, granted) — NO per-purpose native equivalent;
|
|
328
|
+
* billdog-core only exposes the global setConsentGiven (BillDog.kt:1486).
|
|
329
|
+
* Forward as the global toggle when granted for any purpose is set, else
|
|
330
|
+
* log. Documented mapping limitation; resolve null.
|
|
331
|
+
*/
|
|
332
|
+
@ReactMethod
|
|
333
|
+
fun setConsent(purpose: String?, granted: Boolean, promise: Promise) {
|
|
334
|
+
// billdog-core has no per-purpose consent API; the closest primitive is
|
|
335
|
+
// the global consent flag. We do NOT collapse per-purpose into global to
|
|
336
|
+
// avoid over-granting — this is a documented no-op pending a native
|
|
337
|
+
// per-purpose ConsentManager API.
|
|
338
|
+
Log.d(TAG, "setConsent(purpose=$purpose, granted=$granted) — no per-purpose native API; no-op")
|
|
339
|
+
promise.resolve(null)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** native.setConsentGiven(given) → BillDog.setConsentGiven(given) — BillDog.kt:1486. */
|
|
343
|
+
@ReactMethod
|
|
344
|
+
fun setConsentGiven(given: Boolean, promise: Promise) {
|
|
345
|
+
BillDog.setConsentGiven(given)
|
|
346
|
+
promise.resolve(null)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* native.consentHeaderValue() — NO native accessor in billdog-core (consent
|
|
351
|
+
* state is applied to requests internally, not exposed as a header string).
|
|
352
|
+
* Resolve null (documented no-op).
|
|
353
|
+
*/
|
|
354
|
+
@ReactMethod
|
|
355
|
+
fun consentHeaderValue(promise: Promise) {
|
|
356
|
+
Log.d(TAG, "consentHeaderValue() — no native accessor; returning null")
|
|
357
|
+
promise.resolve(null)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ── PUR-360 — attribution / device identifiers ──────────────────────────
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* native.collectDeviceIdentifiers() → BillDog.collectDeviceIdentifiers()
|
|
364
|
+
* — BillDog.kt:1367 (suspend, returns DeviceIdentifiers). JS treats it as
|
|
365
|
+
* void; we resolve null on completion.
|
|
366
|
+
*/
|
|
367
|
+
@ReactMethod
|
|
368
|
+
fun collectDeviceIdentifiers(promise: Promise) {
|
|
369
|
+
launch(promise) { BillDog.collectDeviceIdentifiers(); null }
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* native.setAttribution({ key: id, … }) → the matching native provider setter
|
|
374
|
+
* (BillDog.kt:2646+). Keys are provider attribution keys; each maps 1:1 to a
|
|
375
|
+
* consent-gated native setter. Unknown keys are ignored. The JS facade's
|
|
376
|
+
* named setters (setMixpanelDistinctID etc.) all funnel through here.
|
|
377
|
+
*/
|
|
378
|
+
@ReactMethod
|
|
379
|
+
fun setAttribution(data: ReadableMap, promise: Promise) {
|
|
380
|
+
launch(promise) {
|
|
381
|
+
val it = data.keySetIterator()
|
|
382
|
+
while (it.hasNextKey()) {
|
|
383
|
+
val key = it.nextKey()
|
|
384
|
+
val value = data.getString(key) ?: continue
|
|
385
|
+
when (key) {
|
|
386
|
+
"cleverTapId" -> BillDog.setCleverTapID(value)
|
|
387
|
+
"mixpanelDistinctId" -> BillDog.setMixpanelDistinctID(value)
|
|
388
|
+
"onesignalId" -> BillDog.setOnesignalID(value)
|
|
389
|
+
"fbAnonymousId" -> BillDog.setFBAnonymousID(value)
|
|
390
|
+
"airshipChannelId" -> BillDog.setAirshipChannelID(value)
|
|
391
|
+
"moEngageAnonymousId" -> BillDog.setMoEngageAnonymousID(value)
|
|
392
|
+
"mparticleId" -> BillDog.setMparticleID(value)
|
|
393
|
+
"kochavaId" -> BillDog.setKochavaDeviceID(value)
|
|
394
|
+
"tenjinId" -> BillDog.setTenjinAnalyticsInstallationID(value)
|
|
395
|
+
"iterableId" -> BillDog.setIterableUserID(value)
|
|
396
|
+
"segmentId" -> BillDog.setSegmentAnonymousID(value)
|
|
397
|
+
// MMP providers — dedicated native setters.
|
|
398
|
+
"adjustId" -> BillDog.setAdjustId(value)
|
|
399
|
+
"appsflyerId" -> BillDog.setAppsflyerId(value)
|
|
400
|
+
"firebaseAppInstanceId" -> BillDog.setFirebaseAppInstanceId(value)
|
|
401
|
+
else -> { /* unknown provider key — ignore */ }
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
null
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ── Identity API ────────────────────────────────────────────────────────
|
|
409
|
+
|
|
410
|
+
/** native.logIn(appUserId) → BillDog.logIn(appUserId) — BillDog.kt:1169 (suspend, Result<LogInResult>). */
|
|
411
|
+
@ReactMethod
|
|
412
|
+
fun logIn(appUserId: String, promise: Promise) {
|
|
413
|
+
launch(promise) {
|
|
414
|
+
BillDog.logIn(appUserId).exceptionOrNull()?.let { throw it }
|
|
415
|
+
null
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** native.logOut() → BillDog.logOut() — BillDog.kt:1209. */
|
|
420
|
+
@ReactMethod
|
|
421
|
+
fun logOut(promise: Promise) {
|
|
422
|
+
try {
|
|
423
|
+
BillDog.logOut()
|
|
424
|
+
promise.resolve(null)
|
|
425
|
+
} catch (e: Throwable) {
|
|
426
|
+
promise.reject("BILLDOG_ERROR", e.message, e)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* native.setSandboxMode(enabled) — NO native setter. Sandbox is derived from
|
|
432
|
+
* the API-key prefix (bd_public_test_* / bd_secret_test_*); documented no-op.
|
|
433
|
+
*/
|
|
434
|
+
@ReactMethod
|
|
435
|
+
fun setSandboxMode(enabled: Boolean, promise: Promise) {
|
|
436
|
+
Log.d(TAG, "setSandboxMode($enabled) — no native setter (sandbox tracked by API-key prefix); no-op")
|
|
437
|
+
promise.resolve(null)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ── Placement registration ──────────────────────────────────────────────
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* native.registerPlacement({ placementId, params }) → BillDog.register(
|
|
444
|
+
* placementId, activity, params, handler) — BillDog.kt:1377 (suspend).
|
|
445
|
+
* Requires an Activity; rejects "NO_ACTIVITY" when currentActivity is null.
|
|
446
|
+
*/
|
|
447
|
+
@ReactMethod
|
|
448
|
+
fun registerPlacement(args: ReadableMap, promise: Promise) {
|
|
449
|
+
val placementId = if (args.hasKey("placementId")) args.getString("placementId") else null
|
|
450
|
+
if (placementId.isNullOrBlank()) {
|
|
451
|
+
promise.reject("INVALID_ARGS", "Missing placementId", null)
|
|
452
|
+
return
|
|
453
|
+
}
|
|
454
|
+
val activity = currentActivity
|
|
455
|
+
?: return promise.reject("NO_ACTIVITY", "No Activity available for registerPlacement", null)
|
|
456
|
+
|
|
457
|
+
@Suppress("UNCHECKED_CAST")
|
|
458
|
+
val params: Map<String, Any>? =
|
|
459
|
+
if (args.hasKey("params")) args.getMap("params")?.toHashMap() as? Map<String, Any> else null
|
|
460
|
+
|
|
461
|
+
val replied = AtomicBoolean(false)
|
|
462
|
+
scope.launch {
|
|
463
|
+
try {
|
|
464
|
+
BillDog.register(placementId, activity, params) { placementResult ->
|
|
465
|
+
if (replied.compareAndSet(false, true)) {
|
|
466
|
+
promise.resolve(serializePlacementResult(placementResult))
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
} catch (e: Throwable) {
|
|
470
|
+
if (replied.compareAndSet(false, true)) {
|
|
471
|
+
promise.reject("BILLDOG_ERROR", e.message, e)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* native.hasAccessForPlacement(placementId) → BillDog.hasAccessForPlacement — BillDog.kt:1440 (Boolean).
|
|
479
|
+
* BillDog.ts reads this synchronously (`!!native.hasAccessForPlacement(id)`), so it must be
|
|
480
|
+
* blocking-synchronous. On error, default false (JS treats a thrown/false as "no access").
|
|
481
|
+
*/
|
|
482
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
483
|
+
fun hasAccessForPlacement(placementId: String): Boolean =
|
|
484
|
+
try { BillDog.hasAccessForPlacement(placementId) } catch (e: Throwable) { false }
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* native.getPresentationResult(placementId) → BillDog.getPresentationResult(
|
|
488
|
+
* placementId) — BillDog.kt:2842 (suspend).
|
|
489
|
+
*/
|
|
490
|
+
@ReactMethod
|
|
491
|
+
fun getPresentationResult(placementId: String, promise: Promise) {
|
|
492
|
+
launch(promise) { serializePresentationResult(BillDog.getPresentationResult(placementId)) }
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Experiments & feature flags: the paywall-facade shim was removed. Use the
|
|
496
|
+
// standalone BillDogABTest module (bridged separately) instead.
|
|
497
|
+
|
|
498
|
+
// ── In-app messages ─────────────────────────────────────────────────────
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* native.showInAppMessages(placementId?) → BillDog.showInAppMessages(activity)
|
|
502
|
+
* — BillDog.kt:2873. Native has no placement param; placementId is ignored.
|
|
503
|
+
* Requires an Activity → reject "NO_ACTIVITY" when null.
|
|
504
|
+
*/
|
|
505
|
+
@ReactMethod
|
|
506
|
+
fun showInAppMessages(placementId: String?, promise: Promise) {
|
|
507
|
+
val activity = currentActivity
|
|
508
|
+
?: return promise.reject("NO_ACTIVITY", "No Activity available for showInAppMessages", null)
|
|
509
|
+
try {
|
|
510
|
+
BillDog.showInAppMessages(activity)
|
|
511
|
+
promise.resolve(null)
|
|
512
|
+
} catch (e: Throwable) {
|
|
513
|
+
promise.reject("BILLDOG_ERROR", e.message, e)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// ── Deep linking ────────────────────────────────────────────────────────
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* native.handleDeepLink(uri) → BillDog.handleDeepLink(Uri.parse(uri))
|
|
521
|
+
* — BillDog.kt:883 (returns DeepLinkResult?). Resolve true when handled.
|
|
522
|
+
*/
|
|
523
|
+
@ReactMethod
|
|
524
|
+
fun handleDeepLink(uri: String, promise: Promise) {
|
|
525
|
+
val handled = try {
|
|
526
|
+
BillDog.handleDeepLink(Uri.parse(uri)) != null
|
|
527
|
+
} catch (e: Exception) {
|
|
528
|
+
Log.w(TAG, "handleDeepLink failed: ${e.message}")
|
|
529
|
+
false
|
|
530
|
+
}
|
|
531
|
+
promise.resolve(handled)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ── Component selection state ───────────────────────────────────────────
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* native.getSelectedProductIndex(componentId) → BillDog.getSelectedProductIndex — BillDog.kt:1448 (Int?).
|
|
538
|
+
* BillDog.ts reads this synchronously, so it must be blocking-synchronous.
|
|
539
|
+
*/
|
|
540
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
541
|
+
fun getSelectedProductIndex(componentId: String): Int? =
|
|
542
|
+
BillDog.getSelectedProductIndex(componentId)
|
|
543
|
+
|
|
544
|
+
/** native.setSelectedProductIndex(componentId, index) → BillDog.setSelectedProductIndex(componentId, index) — BillDog.kt:1454. */
|
|
545
|
+
@ReactMethod
|
|
546
|
+
fun setSelectedProductIndex(componentId: String, index: Int, promise: Promise) {
|
|
547
|
+
BillDog.setSelectedProductIndex(componentId, index)
|
|
548
|
+
promise.resolve(null)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ── Theme / interface style ─────────────────────────────────────────────
|
|
552
|
+
|
|
553
|
+
/** native.updateTheme(overrides) → BillDog.updateTheme(overrides) — BillDog.kt:212. */
|
|
554
|
+
@ReactMethod
|
|
555
|
+
fun updateTheme(overrides: ReadableMap, promise: Promise) {
|
|
556
|
+
val map = overrides.toHashMap().entries
|
|
557
|
+
.filter { it.value != null }
|
|
558
|
+
.associate { it.key to it.value.toString() }
|
|
559
|
+
BillDog.updateTheme(map)
|
|
560
|
+
promise.resolve(null)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** native.setInterfaceStyle(style) → BillDog.setInterfaceStyle(InterfaceStyle?) — BillDog.kt:2604. null = follow system. */
|
|
564
|
+
@ReactMethod
|
|
565
|
+
fun setInterfaceStyle(style: String?, promise: Promise) {
|
|
566
|
+
BillDog.setInterfaceStyle(parseInterfaceStyle(style))
|
|
567
|
+
promise.resolve(null)
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ── Subscription management ─────────────────────────────────────────────
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* native.showManageSubscriptions(subscriptionId?) → BillDog.showManageSubscriptions(
|
|
574
|
+
* activity, subscriptionId) — BillDog.kt:2035. Requires an Activity → reject
|
|
575
|
+
* "NO_ACTIVITY" when null.
|
|
576
|
+
*/
|
|
577
|
+
@ReactMethod
|
|
578
|
+
fun showManageSubscriptions(subscriptionId: String?, promise: Promise) {
|
|
579
|
+
val activity = currentActivity
|
|
580
|
+
?: return promise.reject("NO_ACTIVITY", "No Activity available for showManageSubscriptions", null)
|
|
581
|
+
try {
|
|
582
|
+
BillDog.showManageSubscriptions(activity, subscriptionId)
|
|
583
|
+
promise.resolve(null)
|
|
584
|
+
} catch (e: Throwable) {
|
|
585
|
+
promise.reject("BILLDOG_ERROR", e.message, e)
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// ── User attributes ─────────────────────────────────────────────────────
|
|
590
|
+
|
|
591
|
+
/** native.setUserAttributes(attributes) → BillDog.setUserAttributes(attributes) — BillDog.kt:2618 (suspend, Result<Unit>). */
|
|
592
|
+
@ReactMethod
|
|
593
|
+
fun setUserAttributes(attributes: ReadableMap, promise: Promise) {
|
|
594
|
+
val map: Map<String, Any?> = attributes.toHashMap()
|
|
595
|
+
launch(promise) {
|
|
596
|
+
BillDog.setUserAttributes(map).exceptionOrNull()?.let { throw it }
|
|
597
|
+
null
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** native.getUserAttributes() → BillDog.getUserAttributes() — BillDog.kt:2633 (Map<String, Any>). */
|
|
602
|
+
@ReactMethod
|
|
603
|
+
fun getUserAttributes(promise: Promise) {
|
|
604
|
+
try {
|
|
605
|
+
promise.resolve(toWritableMap(BillDog.getUserAttributes()))
|
|
606
|
+
} catch (e: Throwable) {
|
|
607
|
+
promise.reject("BILLDOG_ERROR", e.message, e)
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// ── External purchase observation ───────────────────────────────────────
|
|
612
|
+
|
|
613
|
+
/** native.observePurchaseStart(productId) → BillDog.observePurchaseStart(productId) — BillDog.kt:2768. */
|
|
614
|
+
@ReactMethod
|
|
615
|
+
fun observePurchaseStart(productId: String, promise: Promise) {
|
|
616
|
+
BillDog.observePurchaseStart(productId)
|
|
617
|
+
promise.resolve(null)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/** native.observePurchaseResult(productId, orderId?) → BillDog.observePurchaseResult(productId, orderId) — BillDog.kt:2774. */
|
|
621
|
+
@ReactMethod
|
|
622
|
+
fun observePurchaseResult(productId: String, orderId: String?, promise: Promise) {
|
|
623
|
+
BillDog.observePurchaseResult(productId, orderId)
|
|
624
|
+
promise.resolve(null)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** native.observePurchaseError(productId, error) → BillDog.observePurchaseError(productId, error) — BillDog.kt:2782. */
|
|
628
|
+
@ReactMethod
|
|
629
|
+
fun observePurchaseError(productId: String, error: String, promise: Promise) {
|
|
630
|
+
BillDog.observePurchaseError(productId, error)
|
|
631
|
+
promise.resolve(null)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// ── Server-side tap actions ─────────────────────────────────────────────
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* native.executeAction(paywallId, componentId, actionType, idempotencyKey,
|
|
638
|
+
* metadata) — NO native equivalent in billdog-core (Route B tap-action
|
|
639
|
+
* execution has no exposed SDK method). The JS facade already has a direct
|
|
640
|
+
* fetch fallback to the `execute-paywall-action` Edge Function when native is
|
|
641
|
+
* absent, so we resolve null here to signal "no native handler" and let the
|
|
642
|
+
* JS fallback path run. Documented no-op (does NOT reject).
|
|
643
|
+
*/
|
|
644
|
+
@ReactMethod
|
|
645
|
+
fun executeAction(
|
|
646
|
+
paywallId: String?,
|
|
647
|
+
componentId: String,
|
|
648
|
+
actionType: String,
|
|
649
|
+
idempotencyKey: String?,
|
|
650
|
+
metadata: ReadableMap?,
|
|
651
|
+
promise: Promise
|
|
652
|
+
) {
|
|
653
|
+
Log.d(TAG, "executeAction($actionType) — no native SDK method; resolving null (JS fetch fallback handles it)")
|
|
654
|
+
promise.resolve(null)
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// ── Serializers (shapes match the JS *.ts result unions) ────────────────
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* PurchaseResult (PurchaseResult.kt:28) → JS PurchaseResult union.
|
|
661
|
+
* type ∈ purchased | cancelled | pending | failed | restored.
|
|
662
|
+
*/
|
|
663
|
+
private fun serializePurchaseResult(r: PurchaseResult): WritableMap = when (r) {
|
|
664
|
+
is PurchaseResult.Purchased -> toWritableMap(mapOf(
|
|
665
|
+
"type" to "purchased",
|
|
666
|
+
"productId" to r.productId,
|
|
667
|
+
"transactionId" to r.transactionId,
|
|
668
|
+
"originalTransactionId" to r.originalTransactionId
|
|
669
|
+
))
|
|
670
|
+
is PurchaseResult.Cancelled -> toWritableMap(mapOf("type" to "cancelled"))
|
|
671
|
+
is PurchaseResult.Pending -> toWritableMap(mapOf("type" to "pending"))
|
|
672
|
+
is PurchaseResult.Failed -> toWritableMap(mapOf(
|
|
673
|
+
"type" to "failed",
|
|
674
|
+
"error" to mapOf(
|
|
675
|
+
"category" to r.error.category,
|
|
676
|
+
"message" to r.error.message
|
|
677
|
+
)
|
|
678
|
+
))
|
|
679
|
+
is PurchaseResult.Restored -> toWritableMap(mapOf(
|
|
680
|
+
"type" to "restored",
|
|
681
|
+
"productId" to r.productId,
|
|
682
|
+
"transactionId" to r.transactionId,
|
|
683
|
+
"originalTransactionId" to r.originalTransactionId
|
|
684
|
+
))
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/** RestoreResult (PurchaseResult.kt:60) → JS RestoreResult union. */
|
|
688
|
+
private fun serializeRestoreResult(r: RestoreResult): WritableMap = when (r) {
|
|
689
|
+
is RestoreResult.Restored -> toWritableMap(mapOf(
|
|
690
|
+
"type" to "restored",
|
|
691
|
+
"items" to r.items.map {
|
|
692
|
+
mapOf(
|
|
693
|
+
"productId" to it.productId,
|
|
694
|
+
"transactionId" to it.transactionId,
|
|
695
|
+
"originalTransactionId" to it.originalTransactionId
|
|
696
|
+
)
|
|
697
|
+
}
|
|
698
|
+
))
|
|
699
|
+
is RestoreResult.Failed -> toWritableMap(mapOf(
|
|
700
|
+
"type" to "failed",
|
|
701
|
+
"error" to mapOf(
|
|
702
|
+
"category" to r.error.category,
|
|
703
|
+
"message" to r.error.message
|
|
704
|
+
)
|
|
705
|
+
))
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** PlacementResult (PlacementManager.kt) → JS PlacementResult union. */
|
|
709
|
+
private fun serializePlacementResult(r: io.billdog.managers.PlacementResult): WritableMap = when (r) {
|
|
710
|
+
is io.billdog.managers.PlacementResult.Entitled -> toWritableMap(mapOf("type" to "entitled"))
|
|
711
|
+
is io.billdog.managers.PlacementResult.Purchased ->
|
|
712
|
+
toWritableMap(mapOf("type" to "purchased", "productId" to r.productId))
|
|
713
|
+
is io.billdog.managers.PlacementResult.Dismissed -> toWritableMap(mapOf("type" to "dismissed"))
|
|
714
|
+
is io.billdog.managers.PlacementResult.Restored -> toWritableMap(mapOf("type" to "restored"))
|
|
715
|
+
is io.billdog.managers.PlacementResult.Error ->
|
|
716
|
+
toWritableMap(mapOf("type" to "error", "message" to (r.exception.message ?: "Placement error")))
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** PresentationResult (BillDog.kt) → JS PresentationResult union. */
|
|
720
|
+
private fun serializePresentationResult(r: PresentationResult): WritableMap = when (r) {
|
|
721
|
+
is PresentationResult.Paywall -> toWritableMap(mapOf("type" to "paywall"))
|
|
722
|
+
is PresentationResult.UserSubscribed -> toWritableMap(mapOf("type" to "userSubscribed"))
|
|
723
|
+
is PresentationResult.NoPaywall -> toWritableMap(mapOf("type" to "noPaywall", "reason" to r.reason))
|
|
724
|
+
is PresentationResult.Holdout -> toWritableMap(mapOf("type" to "holdout"))
|
|
725
|
+
is PresentationResult.Error -> toWritableMap(mapOf("type" to "error", "message" to r.message))
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/** EntitlementInfo (CustomerModels.kt) → JS EntitlementInfo. */
|
|
729
|
+
private fun serializeEntitlement(e: EntitlementInfo): Map<String, Any?> = mapOf(
|
|
730
|
+
"identifier" to e.identifier,
|
|
731
|
+
"isActive" to e.isActive,
|
|
732
|
+
"expiresAt" to e.expiresAt,
|
|
733
|
+
"store" to e.store,
|
|
734
|
+
"grantedAt" to e.grantedAt,
|
|
735
|
+
"willRenew" to e.willRenew,
|
|
736
|
+
"isLifetime" to e.isLifetime,
|
|
737
|
+
"state" to e.state,
|
|
738
|
+
"productIds" to e.productIds,
|
|
739
|
+
"startsAt" to e.startsAt,
|
|
740
|
+
"renewedAt" to e.renewedAt,
|
|
741
|
+
"latestProductId" to e.latestProductId
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* CustomerInfo (CustomerModels.kt) → JS CustomerInfo. `subscriptions` is
|
|
746
|
+
* emitted EMPTY — native activeSubscriptions is only a List<String> of ids
|
|
747
|
+
* with no per-subscription dates (mirrors the Flutter plugin limitation);
|
|
748
|
+
* active state is carried by `entitlements`.
|
|
749
|
+
*/
|
|
750
|
+
private fun serializeCustomerInfo(info: CustomerInfo): WritableMap = toWritableMap(mapOf(
|
|
751
|
+
"userId" to info.userId,
|
|
752
|
+
"originalAppUserId" to info.originalAppUserId,
|
|
753
|
+
"entitlements" to info.entitlements.map { serializeEntitlement(it) },
|
|
754
|
+
"subscriptions" to emptyList<Any?>(),
|
|
755
|
+
"productEntitlementMapping" to info.productEntitlementMapping
|
|
756
|
+
))
|
|
757
|
+
|
|
758
|
+
// ── Parsers ──────────────────────────────────────────────────────────────
|
|
759
|
+
|
|
760
|
+
private fun parseInterfaceStyle(value: String?): InterfaceStyle? =
|
|
761
|
+
when (value?.lowercase()) {
|
|
762
|
+
"light" -> InterfaceStyle.LIGHT
|
|
763
|
+
"dark" -> InterfaceStyle.DARK
|
|
764
|
+
"system" -> InterfaceStyle.SYSTEM
|
|
765
|
+
else -> null
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// ── Map<String, Any?> → WritableMap converter ────────────────────────────
|
|
769
|
+
// RN's Writable* types are the only values the bridge can send to JS. This
|
|
770
|
+
// recursively converts plain Kotlin maps/lists/scalars produced by the
|
|
771
|
+
// serializers above. Longs/Floats are widened to Double (JS number).
|
|
772
|
+
|
|
773
|
+
private fun toWritableMap(map: Map<String, Any?>): WritableMap {
|
|
774
|
+
val out = Arguments.createMap()
|
|
775
|
+
for ((key, value) in map) {
|
|
776
|
+
when (value) {
|
|
777
|
+
null -> out.putNull(key)
|
|
778
|
+
is Boolean -> out.putBoolean(key, value)
|
|
779
|
+
is Int -> out.putInt(key, value)
|
|
780
|
+
is Long -> out.putDouble(key, value.toDouble())
|
|
781
|
+
is Float -> out.putDouble(key, value.toDouble())
|
|
782
|
+
is Double -> out.putDouble(key, value)
|
|
783
|
+
is String -> out.putString(key, value)
|
|
784
|
+
is WritableMap -> out.putMap(key, value)
|
|
785
|
+
is WritableArray -> out.putArray(key, value)
|
|
786
|
+
is Map<*, *> -> out.putMap(key, toWritableMap(coerceStringKeys(value)))
|
|
787
|
+
is List<*> -> out.putArray(key, toWritableArray(value))
|
|
788
|
+
else -> out.putString(key, value.toString())
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return out
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
private fun toWritableArray(list: List<*>): WritableArray {
|
|
795
|
+
val out = Arguments.createArray()
|
|
796
|
+
for (value in list) {
|
|
797
|
+
when (value) {
|
|
798
|
+
null -> out.pushNull()
|
|
799
|
+
is Boolean -> out.pushBoolean(value)
|
|
800
|
+
is Int -> out.pushInt(value)
|
|
801
|
+
is Long -> out.pushDouble(value.toDouble())
|
|
802
|
+
is Float -> out.pushDouble(value.toDouble())
|
|
803
|
+
is Double -> out.pushDouble(value)
|
|
804
|
+
is String -> out.pushString(value)
|
|
805
|
+
is WritableMap -> out.pushMap(value)
|
|
806
|
+
is WritableArray -> out.pushArray(value)
|
|
807
|
+
is Map<*, *> -> out.pushMap(toWritableMap(coerceStringKeys(value)))
|
|
808
|
+
is List<*> -> out.pushArray(toWritableArray(value))
|
|
809
|
+
else -> out.pushString(value.toString())
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return out
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
private fun coerceStringKeys(map: Map<*, *>): Map<String, Any?> =
|
|
816
|
+
map.entries.associate { (k, v) -> k.toString() to v }
|
|
817
|
+
}
|