@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.
Files changed (309) hide show
  1. package/BillDogReactNative.podspec +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +330 -0
  4. package/android/build.gradle +99 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/kotlin/io/billdog/reactnative/BillDogABTestModule.kt +317 -0
  7. package/android/src/main/kotlin/io/billdog/reactnative/BillDogAnalyticsModule.kt +389 -0
  8. package/android/src/main/kotlin/io/billdog/reactnative/BillDogModule.kt +817 -0
  9. package/android/src/main/kotlin/io/billdog/reactnative/BillDogPaywallViewManager.kt +383 -0
  10. package/android/src/main/kotlin/io/billdog/reactnative/BillDogSurveyModule.kt +427 -0
  11. package/android/src/main/kotlin/io/billdog/reactnative/BillDogVirtualCurrencyModule.kt +407 -0
  12. package/android/src/main/kotlin/io/billdog/virtualcurrency/BillDogVirtualCurrencyPackage.kt +44 -0
  13. package/dist/AdvancedPaywallCache.d.ts +70 -0
  14. package/dist/AdvancedPaywallCache.js +344 -0
  15. package/dist/BillDog.d.ts +255 -0
  16. package/dist/BillDog.js +918 -0
  17. package/dist/BillDogConfiguration.d.ts +145 -0
  18. package/dist/BillDogConfiguration.js +229 -0
  19. package/dist/ComponentRenderer.d.ts +17 -0
  20. package/dist/ComponentRenderer.js +886 -0
  21. package/dist/LayoutEngine.d.ts +101 -0
  22. package/dist/LayoutEngine.js +394 -0
  23. package/dist/LocalizationResolver.d.ts +99 -0
  24. package/dist/LocalizationResolver.js +266 -0
  25. package/dist/NativePaywallView.d.ts +36 -0
  26. package/dist/NativePaywallView.js +59 -0
  27. package/dist/PaywallCache.d.ts +74 -0
  28. package/dist/PaywallCache.js +293 -0
  29. package/dist/PaywallStateManager.d.ts +62 -0
  30. package/dist/PaywallStateManager.js +147 -0
  31. package/dist/PaywallView.d.ts +42 -0
  32. package/dist/PaywallView.js +275 -0
  33. package/dist/ProductVariableProvider.d.ts +60 -0
  34. package/dist/ProductVariableProvider.js +239 -0
  35. package/dist/VariableResolver.d.ts +120 -0
  36. package/dist/VariableResolver.js +379 -0
  37. package/dist/WebViewPaywall.d.ts +75 -0
  38. package/dist/WebViewPaywall.js +360 -0
  39. package/dist/animations/presets.d.ts +49 -0
  40. package/dist/animations/presets.js +367 -0
  41. package/dist/components/AccoladeCpsComponent.d.ts +13 -0
  42. package/dist/components/AccoladeCpsComponent.js +248 -0
  43. package/dist/components/AccordionComponent.d.ts +10 -0
  44. package/dist/components/AccordionComponent.js +187 -0
  45. package/dist/components/AnimatedWrapper.d.ts +35 -0
  46. package/dist/components/AnimatedWrapper.js +277 -0
  47. package/dist/components/CarouselComponent.d.ts +23 -0
  48. package/dist/components/CarouselComponent.js +484 -0
  49. package/dist/components/ChartCurveComponent.d.ts +13 -0
  50. package/dist/components/ChartCurveComponent.js +333 -0
  51. package/dist/components/CheckboxComponent.d.ts +9 -0
  52. package/dist/components/CheckboxComponent.js +172 -0
  53. package/dist/components/ConfettiComponent.d.ts +9 -0
  54. package/dist/components/ConfettiComponent.js +172 -0
  55. package/dist/components/CountdownComponent.d.ts +16 -0
  56. package/dist/components/CountdownComponent.js +414 -0
  57. package/dist/components/DelayedXComponent.d.ts +12 -0
  58. package/dist/components/DelayedXComponent.js +240 -0
  59. package/dist/components/DrawerModalComponent.d.ts +20 -0
  60. package/dist/components/DrawerModalComponent.js +286 -0
  61. package/dist/components/GradientView.d.ts +38 -0
  62. package/dist/components/GradientView.js +126 -0
  63. package/dist/components/HapticFeedbackComponent.d.ts +16 -0
  64. package/dist/components/HapticFeedbackComponent.js +64 -0
  65. package/dist/components/HorizontalProductsComponent.d.ts +17 -0
  66. package/dist/components/HorizontalProductsComponent.js +284 -0
  67. package/dist/components/MultiChoiceGridComponent.d.ts +13 -0
  68. package/dist/components/MultiChoiceGridComponent.js +234 -0
  69. package/dist/components/NPSScaleComponent.d.ts +16 -0
  70. package/dist/components/NPSScaleComponent.js +260 -0
  71. package/dist/components/NavigationComponent.d.ts +26 -0
  72. package/dist/components/NavigationComponent.js +302 -0
  73. package/dist/components/OAuthButtonComponent.d.ts +20 -0
  74. package/dist/components/OAuthButtonComponent.js +237 -0
  75. package/dist/components/ProgressLoaderComponent.d.ts +13 -0
  76. package/dist/components/ProgressLoaderComponent.js +308 -0
  77. package/dist/components/ProgressStepperComponent.d.ts +18 -0
  78. package/dist/components/ProgressStepperComponent.js +228 -0
  79. package/dist/components/SegmentedPickerComponent.d.ts +10 -0
  80. package/dist/components/SegmentedPickerComponent.js +221 -0
  81. package/dist/components/ShareButtonsComponent.d.ts +21 -0
  82. package/dist/components/ShareButtonsComponent.js +265 -0
  83. package/dist/components/ShareCodeComponent.d.ts +16 -0
  84. package/dist/components/ShareCodeComponent.js +274 -0
  85. package/dist/components/SingleChoiceGridComponent.d.ts +13 -0
  86. package/dist/components/SingleChoiceGridComponent.js +210 -0
  87. package/dist/components/SliderComponent.d.ts +9 -0
  88. package/dist/components/SliderComponent.js +263 -0
  89. package/dist/components/StarRatingInputComponent.d.ts +20 -0
  90. package/dist/components/StarRatingInputComponent.js +198 -0
  91. package/dist/components/StickerCompositeComponent.d.ts +13 -0
  92. package/dist/components/StickerCompositeComponent.js +136 -0
  93. package/dist/components/TabsComponent.d.ts +19 -0
  94. package/dist/components/TabsComponent.js +431 -0
  95. package/dist/components/TextInputComponent.d.ts +18 -0
  96. package/dist/components/TextInputComponent.js +227 -0
  97. package/dist/components/TimelineComponent.d.ts +19 -0
  98. package/dist/components/TimelineComponent.js +104 -0
  99. package/dist/components/ToggleComponent.d.ts +14 -0
  100. package/dist/components/ToggleComponent.js +132 -0
  101. package/dist/components/VerticalProductsComponent.d.ts +17 -0
  102. package/dist/components/VerticalProductsComponent.js +311 -0
  103. package/dist/dynamicValues.d.ts +36 -0
  104. package/dist/dynamicValues.js +142 -0
  105. package/dist/filters/dateFilters.d.ts +50 -0
  106. package/dist/filters/dateFilters.js +390 -0
  107. package/dist/filters/index.d.ts +18 -0
  108. package/dist/filters/index.js +253 -0
  109. package/dist/hooks/useOfferings.d.ts +76 -0
  110. package/dist/hooks/useOfferings.js +99 -0
  111. package/dist/hooks/usePaywall.d.ts +27 -0
  112. package/dist/hooks/usePaywall.js +114 -0
  113. package/dist/hooks/usePlacement.d.ts +34 -0
  114. package/dist/hooks/usePlacement.js +89 -0
  115. package/dist/hooks/useSubscription.d.ts +40 -0
  116. package/dist/hooks/useSubscription.js +97 -0
  117. package/dist/index.d.ts +98 -0
  118. package/dist/index.js +198 -0
  119. package/dist/models/BillDogError.d.ts +40 -0
  120. package/dist/models/BillDogError.js +11 -0
  121. package/dist/models/CacheFetchPolicy.d.ts +8 -0
  122. package/dist/models/CacheFetchPolicy.js +4 -0
  123. package/dist/models/CustomProduct.d.ts +32 -0
  124. package/dist/models/CustomProduct.js +15 -0
  125. package/dist/models/CustomerInfo.d.ts +17 -0
  126. package/dist/models/CustomerInfo.js +11 -0
  127. package/dist/models/Delegate.d.ts +20 -0
  128. package/dist/models/Delegate.js +11 -0
  129. package/dist/models/EntitlementInfo.d.ts +19 -0
  130. package/dist/models/EntitlementInfo.js +2 -0
  131. package/dist/models/Placement.d.ts +54 -0
  132. package/dist/models/Placement.js +15 -0
  133. package/dist/models/PurchaseParams.d.ts +39 -0
  134. package/dist/models/PurchaseParams.js +60 -0
  135. package/dist/models/PurchaseResult.d.ts +30 -0
  136. package/dist/models/PurchaseResult.js +15 -0
  137. package/dist/models/RestoreResult.d.ts +30 -0
  138. package/dist/models/RestoreResult.js +22 -0
  139. package/dist/models/RestoredItem.d.ts +26 -0
  140. package/dist/models/RestoredItem.js +13 -0
  141. package/dist/modules/ABTest.d.ts +65 -0
  142. package/dist/modules/ABTest.js +113 -0
  143. package/dist/modules/Analytics.d.ts +91 -0
  144. package/dist/modules/Analytics.js +182 -0
  145. package/dist/modules/Survey.d.ts +65 -0
  146. package/dist/modules/Survey.js +82 -0
  147. package/dist/modules/VirtualCurrency.d.ts +63 -0
  148. package/dist/modules/VirtualCurrency.js +147 -0
  149. package/dist/murmur.d.ts +23 -0
  150. package/dist/murmur.js +104 -0
  151. package/dist/notifications/LiveActivitiesManager.d.ts +47 -0
  152. package/dist/notifications/LiveActivitiesManager.js +106 -0
  153. package/dist/notifications/NotificationScheduler.d.ts +55 -0
  154. package/dist/notifications/NotificationScheduler.js +199 -0
  155. package/dist/notifications/PurchaseNotificationHandler.d.ts +24 -0
  156. package/dist/notifications/PurchaseNotificationHandler.js +55 -0
  157. package/dist/notifications/useDeviceToken.d.ts +36 -0
  158. package/dist/notifications/useDeviceToken.js +97 -0
  159. package/dist/notifications/useLiveActivity.d.ts +39 -0
  160. package/dist/notifications/useLiveActivity.js +104 -0
  161. package/dist/review/ReviewAnalytics.d.ts +34 -0
  162. package/dist/review/ReviewAnalytics.js +73 -0
  163. package/dist/review/ReviewManager.d.ts +83 -0
  164. package/dist/review/ReviewManager.js +339 -0
  165. package/dist/review/ReviewTimingManager.d.ts +82 -0
  166. package/dist/review/ReviewTimingManager.js +224 -0
  167. package/dist/survey-core/types.d.ts +215 -0
  168. package/dist/survey-core/types.js +8 -0
  169. package/dist/types.d.ts +438 -0
  170. package/dist/types.js +158 -0
  171. package/dist/utils/ApiClient.d.ts +65 -0
  172. package/dist/utils/ApiClient.js +132 -0
  173. package/dist/utils/CSSConverter.d.ts +49 -0
  174. package/dist/utils/CSSConverter.js +470 -0
  175. package/dist/utils/animations.d.ts +41 -0
  176. package/dist/utils/animations.js +194 -0
  177. package/dist/utils/calendarIntegration.d.ts +36 -0
  178. package/dist/utils/calendarIntegration.js +89 -0
  179. package/dist/utils/configurationResolver.d.ts +23 -0
  180. package/dist/utils/configurationResolver.js +78 -0
  181. package/dist/utils/deviceDetection.d.ts +38 -0
  182. package/dist/utils/deviceDetection.js +59 -0
  183. package/dist/utils/hapticFeedback.d.ts +17 -0
  184. package/dist/utils/hapticFeedback.js +53 -0
  185. package/dist/utils/iconMapper.d.ts +26 -0
  186. package/dist/utils/iconMapper.js +113 -0
  187. package/dist/utils/notificationPermissions.d.ts +13 -0
  188. package/dist/utils/notificationPermissions.js +51 -0
  189. package/dist/utils/priceFormatting.d.ts +19 -0
  190. package/dist/utils/priceFormatting.js +99 -0
  191. package/dist/utils/shareSheet.d.ts +17 -0
  192. package/dist/utils/shareSheet.js +42 -0
  193. package/dist/utils/subscriptionUpgrade.d.ts +84 -0
  194. package/dist/utils/subscriptionUpgrade.js +100 -0
  195. package/dist/valueUtils.d.ts +48 -0
  196. package/dist/valueUtils.js +129 -0
  197. package/dist/xFlowTags.d.ts +18 -0
  198. package/dist/xFlowTags.js +385 -0
  199. package/ios/BillDogABTest.m +50 -0
  200. package/ios/BillDogABTest.swift +188 -0
  201. package/ios/BillDogAnalytics.m +105 -0
  202. package/ios/BillDogAnalytics.swift +324 -0
  203. package/ios/BillDogBridge.m +222 -0
  204. package/ios/BillDogBridge.swift +715 -0
  205. package/ios/BillDogLiveActivities.m +41 -0
  206. package/ios/BillDogLiveActivities.swift +104 -0
  207. package/ios/BillDogPaywallViewManager.m +39 -0
  208. package/ios/BillDogPaywallViewManager.swift +361 -0
  209. package/ios/BillDogSurvey.m +34 -0
  210. package/ios/BillDogSurvey.swift +257 -0
  211. package/ios/BillDogVirtualCurrencyBridge.m +63 -0
  212. package/ios/BillDogVirtualCurrencyBridge.swift +511 -0
  213. package/junit.xml +25 -0
  214. package/package.json +61 -0
  215. package/react-native.config.js +13 -0
  216. package/src/AdvancedPaywallCache.ts +433 -0
  217. package/src/BillDog.ts +983 -0
  218. package/src/BillDogConfiguration.ts +318 -0
  219. package/src/ComponentRenderer.tsx +1325 -0
  220. package/src/LayoutEngine.ts +459 -0
  221. package/src/LocalizationResolver.ts +311 -0
  222. package/src/NativePaywallView.tsx +132 -0
  223. package/src/PaywallCache.ts +358 -0
  224. package/src/PaywallStateManager.ts +154 -0
  225. package/src/PaywallView.tsx +328 -0
  226. package/src/ProductVariableProvider.ts +282 -0
  227. package/src/VariableResolver.ts +516 -0
  228. package/src/WebViewPaywall.tsx +454 -0
  229. package/src/animations/presets.ts +421 -0
  230. package/src/components/AccoladeCpsComponent.tsx +259 -0
  231. package/src/components/AccordionComponent.tsx +213 -0
  232. package/src/components/AnimatedWrapper.tsx +316 -0
  233. package/src/components/CarouselComponent.tsx +615 -0
  234. package/src/components/ChartCurveComponent.tsx +452 -0
  235. package/src/components/CheckboxComponent.tsx +185 -0
  236. package/src/components/ConfettiComponent.tsx +178 -0
  237. package/src/components/CountdownComponent.tsx +454 -0
  238. package/src/components/DelayedXComponent.tsx +266 -0
  239. package/src/components/DrawerModalComponent.tsx +324 -0
  240. package/src/components/GradientView.tsx +129 -0
  241. package/src/components/HapticFeedbackComponent.tsx +39 -0
  242. package/src/components/HorizontalProductsComponent.tsx +329 -0
  243. package/src/components/MultiChoiceGridComponent.tsx +265 -0
  244. package/src/components/NPSScaleComponent.tsx +294 -0
  245. package/src/components/NavigationComponent.tsx +329 -0
  246. package/src/components/OAuthButtonComponent.tsx +266 -0
  247. package/src/components/ProgressLoaderComponent.tsx +366 -0
  248. package/src/components/ProgressStepperComponent.tsx +250 -0
  249. package/src/components/SegmentedPickerComponent.tsx +245 -0
  250. package/src/components/ShareButtonsComponent.tsx +299 -0
  251. package/src/components/ShareCodeComponent.tsx +303 -0
  252. package/src/components/SingleChoiceGridComponent.tsx +229 -0
  253. package/src/components/SliderComponent.tsx +280 -0
  254. package/src/components/StarRatingInputComponent.tsx +223 -0
  255. package/src/components/StickerCompositeComponent.tsx +168 -0
  256. package/src/components/TabsComponent.tsx +496 -0
  257. package/src/components/TextInputComponent.tsx +258 -0
  258. package/src/components/TimelineComponent.tsx +129 -0
  259. package/src/components/ToggleComponent.tsx +142 -0
  260. package/src/components/VerticalProductsComponent.tsx +357 -0
  261. package/src/dynamicValues.ts +180 -0
  262. package/src/filters/dateFilters.ts +411 -0
  263. package/src/filters/index.ts +281 -0
  264. package/src/hooks/useOfferings.ts +192 -0
  265. package/src/hooks/usePaywall.ts +154 -0
  266. package/src/hooks/usePlacement.ts +143 -0
  267. package/src/hooks/useSubscription.ts +154 -0
  268. package/src/index.ts +245 -0
  269. package/src/models/BillDogError.ts +24 -0
  270. package/src/models/CacheFetchPolicy.ts +13 -0
  271. package/src/models/CustomProduct.ts +33 -0
  272. package/src/models/CustomerInfo.ts +26 -0
  273. package/src/models/Delegate.ts +22 -0
  274. package/src/models/EntitlementInfo.ts +27 -0
  275. package/src/models/Placement.ts +50 -0
  276. package/src/models/PurchaseParams.ts +81 -0
  277. package/src/models/PurchaseResult.ts +32 -0
  278. package/src/models/RestoreResult.ts +27 -0
  279. package/src/models/RestoredItem.ts +25 -0
  280. package/src/modules/ABTest.ts +131 -0
  281. package/src/modules/Analytics.ts +227 -0
  282. package/src/modules/Survey.ts +126 -0
  283. package/src/modules/VirtualCurrency.ts +223 -0
  284. package/src/murmur.ts +102 -0
  285. package/src/notifications/LiveActivitiesManager.ts +140 -0
  286. package/src/notifications/NotificationScheduler.ts +292 -0
  287. package/src/notifications/PurchaseNotificationHandler.ts +67 -0
  288. package/src/notifications/useDeviceToken.ts +116 -0
  289. package/src/notifications/useLiveActivity.ts +125 -0
  290. package/src/review/ReviewAnalytics.ts +89 -0
  291. package/src/review/ReviewManager.ts +355 -0
  292. package/src/review/ReviewTimingManager.ts +265 -0
  293. package/src/survey-core/types.ts +244 -0
  294. package/src/types/ambient.d.ts +9 -0
  295. package/src/types.ts +680 -0
  296. package/src/utils/ApiClient.ts +163 -0
  297. package/src/utils/CSSConverter.ts +552 -0
  298. package/src/utils/animations.ts +232 -0
  299. package/src/utils/calendarIntegration.ts +113 -0
  300. package/src/utils/configurationResolver.ts +106 -0
  301. package/src/utils/deviceDetection.ts +83 -0
  302. package/src/utils/hapticFeedback.ts +55 -0
  303. package/src/utils/iconMapper.ts +102 -0
  304. package/src/utils/notificationPermissions.ts +48 -0
  305. package/src/utils/priceFormatting.ts +119 -0
  306. package/src/utils/shareSheet.ts +55 -0
  307. package/src/utils/subscriptionUpgrade.ts +183 -0
  308. package/src/valueUtils.ts +108 -0
  309. package/src/xFlowTags.ts +463 -0
@@ -0,0 +1,715 @@
1
+ //
2
+ // BillDogBridge.swift
3
+ // @billdog.io/react-native
4
+ //
5
+ // Core BillDog React Native module — REAL native delegation to the BillDog iOS
6
+ // SDK (JS accesses this as `NativeModules.BillDog`; the objc name is pinned via
7
+ // `@objc(BillDog)`).
8
+ //
9
+ // This bridges the ~40 `native.<method>(...)` calls in `../src/BillDog.ts` to
10
+ // `BillDog.shared` (native BillDog iOS SDK, vended by the `BillDog` pod — see the
11
+ // podspec `s.dependency 'BillDog'`, Phase 0). CocoaPods compiles every BillDog
12
+ // subspec (Core, Paywall, …) into a SINGLE Clang module named `BillDog`, so one
13
+ // `import BillDog` exposes every public symbol used below (BillDog, BillDogConfig,
14
+ // ConsentManager, CustomerInfo, EntitlementInfo, PlacementResult, PresentationResult,
15
+ // RestoreResult, PurchaseResult, PurchaseParams, CacheFetchPolicy, InterfaceStyle).
16
+ // Because this Swift class is named `BillDogBridge` (only its objc export is
17
+ // `BillDog`), the bare `BillDog` identifier below unambiguously resolves to the
18
+ // SDK class, never to this bridge.
19
+ //
20
+ // ⚠️ VERIFICATION: there is no iOS/CocoaPods/Xcode toolchain in the authoring
21
+ // environment. This file is written to be internally consistent and compile-ready
22
+ // against the verified native signatures (cited inline as `BillDog.swift:<line>`
23
+ // etc.), but it REQUIRES a `pod install` + Xcode build in a host RN app to
24
+ // actually compile and run.
25
+ //
26
+ // Method names + argument shapes are the AUTHORITATIVE contract from
27
+ // `../src/BillDog.ts`. The result maps mirror the same model shapes the JS side
28
+ // parses (PurchaseResult / RestoreResult / CustomerInfo / PlacementResult /
29
+ // PresentationResult), matching `BillDogFlutterPlugin.swift`'s serialization.
30
+ //
31
+
32
+ import Foundation
33
+ import React
34
+ import BillDogFull
35
+
36
+ @objc(BillDog)
37
+ class BillDogBridge: NSObject {
38
+
39
+ override static func requiresMainQueueSetup() -> Bool {
40
+ return false
41
+ }
42
+
43
+ /// native.configure(apiKey, options) → BillDog.shared.configure(config:)
44
+ /// (BillDog.swift:338). Without this the native SDK is unconfigured and every
45
+ /// delegating call fails; this is what makes the thin bridge usable.
46
+ @objc
47
+ func configure(_ apiKey: String,
48
+ options: NSDictionary?,
49
+ resolver resolve: @escaping RCTPromiseResolveBlock,
50
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
51
+ let enableLogging = (options?["enableLogging"] as? Bool) ?? true
52
+ let localeOverride = options?["localeOverride"] as? String
53
+ let config = BillDogConfig(
54
+ apiKey: apiKey,
55
+ enableLogging: enableLogging,
56
+ environment: .production,
57
+ localeOverride: localeOverride
58
+ )
59
+ BillDog.shared.configure(config: config)
60
+ BillDogBridge.registerOptionalModules()
61
+ resolve(true)
62
+ }
63
+
64
+ /// Guards `registerOptionalModules()` — `use(module:)` does not de-dupe.
65
+ private static var modulesRegistered = false
66
+
67
+ /// Register the optional native submodules with the core SDK.
68
+ ///
69
+ /// WHY THIS EXISTS: the iOS SDK keeps an OPT-IN module registry —
70
+ /// `BillDog.shared.use(module:)` is the only way to populate it, and
71
+ /// `configure(config:)` registers nothing. Every bridge here resolves its
72
+ /// manager per-call out of `BillDog.shared.getModules()` (BillDog.swift:579),
73
+ /// so with an empty registry `BillDogAnalytics` / `BillDogABTest` resolve nil
74
+ /// and silently NO-OP — `track()` reports success and delivers nothing.
75
+ /// Confirmed on the Android twin of this bug 2026-07-21: events never reached
76
+ /// ClickHouse.
77
+ ///
78
+ /// A native Swift host does this itself (see the iOS Inspector's
79
+ /// BillDogBootstrap.swift:42-56) — but an RN app has no Swift bootstrap, so the
80
+ /// bridge IS the host. Mirrors that reference sequence. Unlike Android, the
81
+ /// iOS analytics manager needs no Application context and no separate
82
+ /// persistence-init call (`init(directoryURL:)` defaults).
83
+ static func registerOptionalModules() {
84
+ guard !modulesRegistered else { return }
85
+ modulesRegistered = true
86
+ BillDog.shared.use(module: BillDogAnalyticsManager())
87
+ BillDog.shared.use(module: BillDogABTestManager())
88
+ }
89
+
90
+ /// PUR-364 — Whether a JS-side host PurchaseController is registered.
91
+ ///
92
+ /// NOTE: full JS-PurchaseController callback bridging (invoking a JS
93
+ /// `purchase(productId)` from native and awaiting its typed result) is
94
+ /// DEFERRED. For now we only store the flag; `purchase(_:)` always routes
95
+ /// through the SDK's own native controller via `BillDog.shared.purchase(params:)`
96
+ /// (the JS facade already short-circuits to the JS controller before ever
97
+ /// reaching this bridge — see BillDog.ts:209 — so this flag is currently
98
+ /// informational only).
99
+ private var purchaseControllerActive = false
100
+
101
+ // MARK: - Async helpers
102
+
103
+ /// Run an async throwing block; resolve its value or reject on throw.
104
+ private func runThrowing(_ resolve: @escaping RCTPromiseResolveBlock,
105
+ _ reject: @escaping RCTPromiseRejectBlock,
106
+ _ work: @escaping () async throws -> Any?) {
107
+ Task {
108
+ do {
109
+ let value = try await work()
110
+ resolve(value)
111
+ } catch {
112
+ reject("BILLDOG_ERROR", error.localizedDescription, error)
113
+ }
114
+ }
115
+ }
116
+
117
+ /// Run an async non-throwing block and resolve its value.
118
+ private func runValue(_ resolve: @escaping RCTPromiseResolveBlock,
119
+ _ work: @escaping () async -> Any?) {
120
+ Task {
121
+ let value = await work()
122
+ resolve(value)
123
+ }
124
+ }
125
+
126
+ // MARK: - Purchases
127
+
128
+ /// BillDog.ts:221 — params is `{ ...PurchaseParams, source }`. We map every
129
+ /// PurchaseParams field onto the native `PurchaseParams.Builder`
130
+ /// (PurchaseParams.swift:67) and call `purchase(params:)` (BillDog.swift:1697),
131
+ /// which returns the canonical 5-case PurchaseResult.
132
+ @objc(purchase:resolver:rejecter:)
133
+ func purchase(_ params: NSDictionary,
134
+ resolver resolve: @escaping RCTPromiseResolveBlock,
135
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
136
+ guard let productId = params["productId"] as? String else {
137
+ reject("BILLDOG_ERROR", "purchase: missing productId", nil)
138
+ return
139
+ }
140
+ // PurchaseParams.Builder — PurchaseParams.swift:67.
141
+ let builder = PurchaseParams.Builder(productId: productId)
142
+ if let promo = params["promoOfferId"] as? String { builder.with(promoOfferId: promo) }
143
+ if let winBack = params["winBackOfferId"] as? String { builder.with(winBackOfferId: winBack) }
144
+ if let token = params["appAccountToken"] as? String, let uuid = UUID(uuidString: token) {
145
+ builder.with(appAccountToken: uuid)
146
+ }
147
+ if let qty = params["quantity"] as? Int { builder.with(quantity: qty) }
148
+ if let personalized = params["isPersonalizedOffer"] as? Bool { builder.with(isPersonalizedOffer: personalized) }
149
+ if let mode = params["replacementMode"] as? String { builder.with(replacementMode: mode) }
150
+ let built = builder.build()
151
+
152
+ runThrowing(resolve, reject) {
153
+ // BillDog.swift:1697 — purchase(params:) async throws -> PurchaseResult.
154
+ let result = try await BillDog.shared.purchase(params: built)
155
+ return Self.serialize(result)
156
+ }
157
+ }
158
+
159
+ /// BillDog.ts:266 — store the flag only (JS-controller bridging deferred; see
160
+ /// `purchaseControllerActive` doc above). No native equivalent needed here.
161
+ @objc(setPurchaseControllerActive:)
162
+ func setPurchaseControllerActive(_ active: Bool) {
163
+ purchaseControllerActive = active
164
+ NSLog("[BillDog] setPurchaseControllerActive(\(active)) — flag stored; JS PurchaseController bridging is deferred")
165
+ }
166
+
167
+ /// BillDog.ts:296 — restorePurchases() async throws -> RestoreResult. BillDog.swift:1507.
168
+ @objc(restore:rejecter:)
169
+ func restore(_ resolve: @escaping RCTPromiseResolveBlock,
170
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
171
+ runValue(resolve) {
172
+ do {
173
+ let result = try await BillDog.shared.restorePurchases()
174
+ return Self.serialize(result)
175
+ } catch {
176
+ return ["type": "failed",
177
+ "error": ["category": "unknown", "message": error.localizedDescription]]
178
+ }
179
+ }
180
+ }
181
+
182
+ // MARK: - CustomerInfo
183
+
184
+ /// BillDog.ts:320 — getCustomerInfo(fetchPolicy:) async throws -> CustomerInfo. BillDog.swift:2640.
185
+ @objc(getCustomerInfo:resolver:rejecter:)
186
+ func getCustomerInfo(_ policy: String,
187
+ resolver resolve: @escaping RCTPromiseResolveBlock,
188
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
189
+ let fetchPolicy = Self.mapCacheFetchPolicy(policy)
190
+ runThrowing(resolve, reject) {
191
+ let info = try await BillDog.shared.getCustomerInfo(fetchPolicy: fetchPolicy)
192
+ return Self.serialize(info)
193
+ }
194
+ }
195
+
196
+ /// BillDog.ts:792 — invalidateCustomerInfoCache() async throws. BillDog.swift:1813.
197
+ @objc(invalidateCustomerInfoCache:rejecter:)
198
+ func invalidateCustomerInfoCache(_ resolve: @escaping RCTPromiseResolveBlock,
199
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
200
+ runThrowing(resolve, reject) {
201
+ try await BillDog.shared.invalidateCustomerInfoCache()
202
+ return nil
203
+ }
204
+ }
205
+
206
+ // MARK: - Store capability surface
207
+
208
+ /// BillDog.ts:350 — static canMakePayments() async -> Bool. BillDog.swift:2616.
209
+ @objc(canMakePayments:rejecter:)
210
+ func canMakePayments(_ resolve: @escaping RCTPromiseResolveBlock,
211
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
212
+ runValue(resolve) {
213
+ await BillDog.canMakePayments()
214
+ }
215
+ }
216
+
217
+ /// BillDog.ts:359 — presentCodeRedemptionSheet() async throws. BillDog.swift:1656.
218
+ @objc(presentCodeRedemptionSheet:rejecter:)
219
+ func presentCodeRedemptionSheet(_ resolve: @escaping RCTPromiseResolveBlock,
220
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
221
+ runThrowing(resolve, reject) {
222
+ try await BillDog.shared.presentCodeRedemptionSheet()
223
+ return nil
224
+ }
225
+ }
226
+
227
+ /// BillDog.ts:375 — NO native equivalent on BillDog.shared (searched the whole
228
+ /// paywall SDK: no `checkTrialOrIntroDiscountEligibility` / intro-eligibility
229
+ /// query API). Resolve all ids `unknown` (the JS fallback shape) + NSLog.
230
+ @objc(checkTrialOrIntroDiscountEligibility:resolver:rejecter:)
231
+ func checkTrialOrIntroDiscountEligibility(_ productIds: NSArray,
232
+ resolver resolve: @escaping RCTPromiseResolveBlock,
233
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
234
+ NSLog("[BillDog] checkTrialOrIntroDiscountEligibility — no native SDK query; returning 'unknown' for all ids")
235
+ var out: [String: String] = [:]
236
+ for case let id as String in productIds { out[id] = "unknown" }
237
+ resolve(out)
238
+ }
239
+
240
+ /// BillDog.ts:388 — the SDK exposes storefront changes only as an AsyncSequence
241
+ /// (StorefrontManager.swift:30) and this bridge is a plain NSObject with no
242
+ /// RCTEventEmitter channel, so there is no way to push updates back to JS yet.
243
+ /// Documented no-op (the JS facade keeps its listeners locally).
244
+ @objc(subscribeStorefront)
245
+ func subscribeStorefront() {
246
+ NSLog("[BillDog] subscribeStorefront — no event channel on this NSObject module; no-op (storefront push deferred)")
247
+ }
248
+
249
+ // MARK: - Self-hosted proxy
250
+
251
+ /// BillDog.ts:404 — setProxyURL(_:). BillDog.swift:1185.
252
+ @objc(setProxyURL:)
253
+ func setProxyURL(_ url: String?) {
254
+ BillDog.shared.setProxyURL(url)
255
+ }
256
+
257
+ /// BillDog.ts:453 — SYNCHRONOUS read. BillDogConfig.getProxyURL() static. BillDogConfig.swift:76.
258
+ @objc(getProxyURL)
259
+ func getProxyURL() -> Any? {
260
+ return BillDogConfig.getProxyURL()
261
+ }
262
+
263
+ // MARK: - Consent
264
+
265
+ /// BillDog.ts:419 — ConsentManager.setConsentRequired(_:). ConsentManager.swift:42
266
+ /// (also re-exposed on BillDog.shared at BillDog.swift:460).
267
+ @objc(setConsentRequired:)
268
+ func setConsentRequired(_ required: Bool) {
269
+ ConsentManager.shared.setConsentRequired(required)
270
+ }
271
+
272
+ /// BillDog.ts:428 — granular per-purpose consent. ConsentManager.setConsent(for:granted:).
273
+ /// ConsentManager.swift:53. Purpose enum: analytics|replay|messaging|survey (ConsentManager.swift:16).
274
+ @objc(setConsent:granted:)
275
+ func setConsent(_ purpose: String, granted: Bool) {
276
+ guard let p = ConsentManager.Purpose(rawValue: purpose) else {
277
+ NSLog("[BillDog] setConsent — unknown purpose '\(purpose)'; ignored")
278
+ return
279
+ }
280
+ ConsentManager.shared.setConsent(for: p, granted: granted)
281
+ }
282
+
283
+ /// BillDog.ts:437 — ConsentManager.setConsentGiven(_:). ConsentManager.swift:46
284
+ /// (also BillDog.swift:464).
285
+ @objc(setConsentGiven:)
286
+ func setConsentGiven(_ given: Bool) {
287
+ ConsentManager.shared.setConsentGiven(given)
288
+ }
289
+
290
+ /// BillDog.ts:446 — ConsentManager.consentHeaderValue() -> String?. ConsentManager.swift:75.
291
+ /// Sync accessor; the JS facade awaits it, so we surface it as a Promise.
292
+ @objc(consentHeaderValue:rejecter:)
293
+ func consentHeaderValue(_ resolve: @escaping RCTPromiseResolveBlock,
294
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
295
+ resolve(ConsentManager.shared.consentHeaderValue())
296
+ }
297
+
298
+ // MARK: - Attribution
299
+
300
+ /// BillDog.ts:484 — collectDeviceIdentifiers() async -> DeviceIdentifiers. BillDog.swift:2480.
301
+ /// JS calls this fire-and-forget; collect on a Task and log.
302
+ @objc(collectDeviceIdentifiers)
303
+ func collectDeviceIdentifiers() {
304
+ Task {
305
+ _ = await BillDog.shared.collectDeviceIdentifiers()
306
+ NSLog("[BillDog] collectDeviceIdentifiers — device identifiers collected")
307
+ }
308
+ }
309
+
310
+ /// native.setAttribution({ key: id, … }) → the matching native provider setter
311
+ /// (BillDog.swift:3388+). Keys are provider attribution keys; each maps 1:1 to
312
+ /// a consent-gated native setter. Unknown keys are ignored. The JS facade's
313
+ /// named setters (setMixpanelDistinctID etc.) all funnel through here.
314
+ @objc
315
+ func setAttribution(_ data: NSDictionary,
316
+ resolver resolve: @escaping RCTPromiseResolveBlock,
317
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
318
+ let entries = (data as? [String: String]) ?? [:]
319
+ Task {
320
+ do {
321
+ for (key, value) in entries {
322
+ switch key {
323
+ case "cleverTapId": try await BillDog.shared.setCleverTapID(value)
324
+ case "mixpanelDistinctId": try await BillDog.shared.setMixpanelDistinctID(value)
325
+ case "onesignalId": try await BillDog.shared.setOnesignalID(value)
326
+ case "fbAnonymousId": try await BillDog.shared.setFBAnonymousID(value)
327
+ case "airshipChannelId": try await BillDog.shared.setAirshipChannelID(value)
328
+ case "moEngageAnonymousId": try await BillDog.shared.setMoEngageAnonymousID(value)
329
+ case "mparticleId": try await BillDog.shared.setMparticleID(value)
330
+ case "kochavaId": try await BillDog.shared.setKochavaDeviceID(value)
331
+ case "tenjinId": try await BillDog.shared.setTenjinAnalyticsInstallationID(value)
332
+ case "iterableId": try await BillDog.shared.setIterableUserID(value)
333
+ case "segmentId": try await BillDog.shared.setSegmentAnonymousID(value)
334
+ // MMP providers — dedicated native setters.
335
+ case "adjustId": try await BillDog.shared.setAdjustId(value)
336
+ case "appsflyerId": try await BillDog.shared.setAppsflyerId(value)
337
+ case "firebaseAppInstanceId": try await BillDog.shared.setFirebaseAppInstanceId(value)
338
+ default: break // unknown provider key — ignore
339
+ }
340
+ }
341
+ resolve(nil)
342
+ } catch {
343
+ reject("ATTRIBUTION_ERROR", error.localizedDescription, error)
344
+ }
345
+ }
346
+ }
347
+
348
+ // MARK: - Identity
349
+
350
+ /// BillDog.ts:513 — logIn(_:) async throws -> LogInResult. BillDog.swift:2529.
351
+ /// JS ignores the return value; resolve nil on success.
352
+ @objc(logIn:resolver:rejecter:)
353
+ func logIn(_ appUserId: String,
354
+ resolver resolve: @escaping RCTPromiseResolveBlock,
355
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
356
+ runThrowing(resolve, reject) {
357
+ _ = try await BillDog.shared.logIn(appUserId)
358
+ return nil
359
+ }
360
+ }
361
+
362
+ /// BillDog.ts:536 — logOut() (synchronous on the SDK). BillDog.swift:2547.
363
+ @objc(logOut:rejecter:)
364
+ func logOut(_ resolve: @escaping RCTPromiseResolveBlock,
365
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
366
+ BillDog.shared.logOut()
367
+ resolve(nil)
368
+ }
369
+
370
+ /// BillDog.ts:589 — NO native runtime toggle (sandbox derives from API-key
371
+ /// prefix at configure time — same finding as BillDogFlutterPlugin.swift:381).
372
+ @objc(setSandboxMode:)
373
+ func setSandboxMode(_ enabled: Bool) {
374
+ NSLog("[BillDog] setSandboxMode(\(enabled)) — no native runtime toggle (sandbox derives from API-key prefix); no-op")
375
+ }
376
+
377
+ // MARK: - Placements
378
+
379
+ /// BillDog.ts:634 — args is `{ placementId, params }`. FULL-SCREEN PRESENT via
380
+ /// register(placement:params:) async throws -> PlacementResult. BillDog.swift:2899.
381
+ @objc(registerPlacement:resolver:rejecter:)
382
+ func registerPlacement(_ args: NSDictionary,
383
+ resolver resolve: @escaping RCTPromiseResolveBlock,
384
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
385
+ guard let placementId = args["placementId"] as? String else {
386
+ reject("BILLDOG_ERROR", "registerPlacement: missing placementId", nil)
387
+ return
388
+ }
389
+ let params = args["params"] as? [String: Any]
390
+ runValue(resolve) {
391
+ do {
392
+ let result = try await BillDog.shared.register(placement: placementId, params: params)
393
+ return Self.serialize(result)
394
+ } catch {
395
+ return ["type": "error", "message": error.localizedDescription]
396
+ }
397
+ }
398
+ }
399
+
400
+ /// BillDog.ts:679 — SYNCHRONOUS entitlement probe. hasAccessForPlacement(_:) -> Bool.
401
+ /// BillDog.swift:2213.
402
+ @objc(hasAccessForPlacement:)
403
+ func hasAccessForPlacement(_ placementId: String) -> NSNumber {
404
+ return NSNumber(value: BillDog.shared.hasAccessForPlacement(placementId))
405
+ }
406
+
407
+ /// BillDog.ts:878 — getPresentationResult(forPlacement:) async -> PresentationResult.
408
+ /// BillDog.swift:3319.
409
+ @objc(getPresentationResult:resolver:rejecter:)
410
+ func getPresentationResult(_ placementId: String,
411
+ resolver resolve: @escaping RCTPromiseResolveBlock,
412
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
413
+ runValue(resolve) {
414
+ let result = await BillDog.shared.getPresentationResult(forPlacement: placementId)
415
+ return Self.serialize(result)
416
+ }
417
+ }
418
+
419
+ // Experiments & feature flags: the paywall-facade shim was removed. Use the
420
+ // standalone BillDogABTestManager (bridged via BillDogABTestModule) instead.
421
+
422
+ // MARK: - In-app messages
423
+
424
+ /// BillDog.ts:722 — NO native equivalent on BillDog.shared (same finding as
425
+ /// BillDogFlutterPlugin.swift:389 — in-app messages dispatch internally / via
426
+ /// journeys, not through a paywall-channel call). Resolve nil + NSLog.
427
+ @objc(showInAppMessages:resolver:rejecter:)
428
+ func showInAppMessages(_ placementId: String?,
429
+ resolver resolve: @escaping RCTPromiseResolveBlock,
430
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
431
+ NSLog("[BillDog] showInAppMessages(\(placementId ?? "nil")) — no native SDK method on BillDog.shared; no-op")
432
+ resolve(nil)
433
+ }
434
+
435
+ // MARK: - Deep linking
436
+
437
+ /// BillDog.ts:731 — handleDeepLink(_: URL) -> String? (non-nil route id when
438
+ /// handled). BillDog.swift:2876. JS wants a bool.
439
+ @objc(handleDeepLink:resolver:rejecter:)
440
+ func handleDeepLink(_ uri: String,
441
+ resolver resolve: @escaping RCTPromiseResolveBlock,
442
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
443
+ guard let url = URL(string: uri) else {
444
+ resolve(false)
445
+ return
446
+ }
447
+ resolve(BillDog.shared.handleDeepLink(url) != nil)
448
+ }
449
+
450
+ // MARK: - Component selection state
451
+
452
+ /// BillDog.ts:740 — SYNCHRONOUS. getSelectedProductIndex(componentId:) -> Int?
453
+ /// (nil is a valid response → JS null). BillDog.swift:1889.
454
+ @objc(getSelectedProductIndex:)
455
+ func getSelectedProductIndex(_ componentId: String) -> Any? {
456
+ if let index = BillDog.shared.getSelectedProductIndex(componentId: componentId) {
457
+ return NSNumber(value: index)
458
+ }
459
+ return nil
460
+ }
461
+
462
+ /// BillDog.ts:747 — setSelectedProductIndex(componentId:index:). BillDog.swift:1895.
463
+ @objc(setSelectedProductIndex:index:)
464
+ func setSelectedProductIndex(_ componentId: String, index: NSNumber) {
465
+ BillDog.shared.setSelectedProductIndex(componentId: componentId, index: index.intValue)
466
+ }
467
+
468
+ // MARK: - Theming
469
+
470
+ /// BillDog.ts:755 — updateTheme(_: [String: String]?). BillDog.swift:1174.
471
+ @objc(updateTheme:)
472
+ func updateTheme(_ overrides: NSDictionary) {
473
+ BillDog.shared.updateTheme(overrides as? [String: String])
474
+ }
475
+
476
+ /// BillDog.ts:762 — setInterfaceStyle(_: InterfaceStyle?). BillDog.swift:3333.
477
+ /// InterfaceStyle: light/dark/system; nil == follow system.
478
+ @objc(setInterfaceStyle:)
479
+ func setInterfaceStyle(_ style: String?) {
480
+ BillDog.shared.setInterfaceStyle(Self.mapInterfaceStyle(style))
481
+ }
482
+
483
+ // MARK: - Subscription management
484
+
485
+ /// BillDog.ts:776 — showManageSubscriptions() async (takes no argument; Apple
486
+ /// resolves the active subscription — the JS `subscriptionId` is ignored, same
487
+ /// as BillDogFlutterPlugin.swift:344). BillDog.swift:3182.
488
+ @objc(showManageSubscriptions:resolver:rejecter:)
489
+ func showManageSubscriptions(_ subscriptionId: String?,
490
+ resolver resolve: @escaping RCTPromiseResolveBlock,
491
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
492
+ runValue(resolve) {
493
+ await BillDog.shared.showManageSubscriptions()
494
+ return nil
495
+ }
496
+ }
497
+
498
+ // MARK: - Cache control
499
+
500
+ /// BillDog.ts:785 — clearCache() (synchronous on the SDK). BillDog.swift:1914.
501
+ @objc(clearCache:rejecter:)
502
+ func clearCache(_ resolve: @escaping RCTPromiseResolveBlock,
503
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
504
+ BillDog.shared.clearCache()
505
+ resolve(nil)
506
+ }
507
+
508
+ // MARK: - User attributes
509
+
510
+ /// BillDog.ts:801 — setUserAttributes(_: [String: Any?]) async throws. BillDog.swift:3344.
511
+ @objc(setUserAttributes:resolver:rejecter:)
512
+ func setUserAttributes(_ attributes: NSDictionary,
513
+ resolver resolve: @escaping RCTPromiseResolveBlock,
514
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
515
+ let dict = (attributes as? [String: Any]) ?? [:]
516
+ runThrowing(resolve, reject) {
517
+ try await BillDog.shared.setUserAttributes(dict)
518
+ return nil
519
+ }
520
+ }
521
+
522
+ /// BillDog.ts:812 — getUserAttributes() -> [String: Any] (synchronous on the
523
+ /// SDK; the JS facade awaits it). BillDog.swift:3358.
524
+ @objc(getUserAttributes:rejecter:)
525
+ func getUserAttributes(_ resolve: @escaping RCTPromiseResolveBlock,
526
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
527
+ resolve(BillDog.shared.getUserAttributes())
528
+ }
529
+
530
+ // MARK: - External-purchase observation
531
+
532
+ /// BillDog.ts:822 — observePurchaseStart(productId:). BillDog.swift:3568.
533
+ @objc(observePurchaseStart:)
534
+ func observePurchaseStart(_ productId: String) {
535
+ BillDog.shared.observePurchaseStart(productId: productId)
536
+ }
537
+
538
+ /// BillDog.ts:829 — observePurchaseResult(productId:orderId:...). BillDog.swift:3583.
539
+ @objc(observePurchaseResult:orderId:)
540
+ func observePurchaseResult(_ productId: String, orderId: String?) {
541
+ BillDog.shared.observePurchaseResult(productId: productId, orderId: orderId)
542
+ }
543
+
544
+ /// BillDog.ts:835 — observePurchaseError(productId:error:). BillDog.swift:3610.
545
+ /// JS passes an error string; wrap it in an NSError (same as the Flutter plugin).
546
+ @objc(observePurchaseError:error:)
547
+ func observePurchaseError(_ productId: String, error: String) {
548
+ let nsError = NSError(domain: "io.billdog.reactnative", code: -1,
549
+ userInfo: [NSLocalizedDescriptionKey: error])
550
+ BillDog.shared.observePurchaseError(productId: productId, error: nsError)
551
+ }
552
+
553
+ // MARK: - Server-side actions
554
+
555
+ /// BillDog.ts:900 — executeAction(paywallId:componentId:actionType:idempotencyKey:metadata:)
556
+ /// async throws -> [String: Any]. BillDog.swift:231.
557
+ @objc(executeAction:componentId:actionType:idempotencyKey:metadata:resolver:rejecter:)
558
+ func executeAction(_ paywallId: String?,
559
+ componentId: String,
560
+ actionType: String,
561
+ idempotencyKey: String?,
562
+ metadata: NSDictionary?,
563
+ resolver resolve: @escaping RCTPromiseResolveBlock,
564
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
565
+ let meta = metadata as? [String: Any]
566
+ runThrowing(resolve, reject) {
567
+ try await BillDog.shared.executeAction(paywallId: paywallId,
568
+ componentId: componentId,
569
+ actionType: actionType,
570
+ idempotencyKey: idempotencyKey,
571
+ metadata: meta)
572
+ }
573
+ }
574
+
575
+ // MARK: - Enum mapping
576
+
577
+ /// CacheFetchPolicy (CustomerManager.swift:4); JS wire names from CacheFetchPolicy.ts.
578
+ private static func mapCacheFetchPolicy(_ raw: String?) -> CacheFetchPolicy {
579
+ switch raw {
580
+ case "FROM_CACHE_ONLY": return .fromCacheOnly
581
+ case "FETCH_CURRENT": return .fetchCurrent
582
+ case "NOT_STALE_CACHED_OR_CURRENT": return .notStaleCachedOrCurrent
583
+ default: return .cachedOrFetched // PUR-141 default (CACHED_OR_FETCHED)
584
+ }
585
+ }
586
+
587
+ /// InterfaceStyle (InterfaceStyle.swift); JS passes 'light' | 'dark' | null.
588
+ private static func mapInterfaceStyle(_ raw: String?) -> InterfaceStyle? {
589
+ switch raw?.lowercased() {
590
+ case "light": return .light
591
+ case "dark": return .dark
592
+ case "system": return .system
593
+ default: return nil // nil == follow system
594
+ }
595
+ }
596
+
597
+ // MARK: - Serialization (mirrors BillDogFlutterPlugin.swift; matches BillDog.ts model shapes)
598
+
599
+ /// PurchaseResult (PurchaseResult.swift:24) → PurchaseResult.ts 5-case union.
600
+ private static func serialize(_ r: PurchaseResult) -> [String: Any] {
601
+ switch r {
602
+ case .purchased(let transactionId, let productId, let originalTransactionId):
603
+ var m: [String: Any] = ["type": "purchased",
604
+ "transactionId": transactionId,
605
+ "productId": productId]
606
+ if let orig = originalTransactionId { m["originalTransactionId"] = orig }
607
+ return m
608
+ case .cancelled:
609
+ return ["type": "cancelled"]
610
+ case .pending:
611
+ return ["type": "pending"]
612
+ case .failed(let error):
613
+ return ["type": "failed",
614
+ "error": ["category": "unknown", "message": error.localizedDescription]]
615
+ case .restored(let transactionId, let productId, let originalTransactionId):
616
+ var m: [String: Any] = ["type": "restored",
617
+ "transactionId": transactionId,
618
+ "productId": productId]
619
+ if let orig = originalTransactionId { m["originalTransactionId"] = orig }
620
+ return m
621
+ }
622
+ }
623
+
624
+ /// RestoreResult (PurchaseResult.swift:52) → RestoreResult.ts.
625
+ /// Each item carries `status` per RestoredItem.ts (per-transaction success).
626
+ private static func serialize(_ r: RestoreResult) -> [String: Any] {
627
+ switch r {
628
+ case .restored(let items):
629
+ let mapped: [[String: Any]] = items.map { p in
630
+ var m: [String: Any] = [
631
+ "productId": p.productId,
632
+ "transactionId": p.transactionId,
633
+ "status": ["type": "restored"]
634
+ ]
635
+ if let orig = p.originalTransactionId { m["originalTransactionId"] = orig }
636
+ return m
637
+ }
638
+ return ["type": "restored", "items": mapped]
639
+ case .failed(let error):
640
+ return ["type": "failed",
641
+ "error": ["category": "unknown", "message": error.localizedDescription]]
642
+ }
643
+ }
644
+
645
+ /// PlacementResult (PlacementManager) → Placement.ts PlacementResult.
646
+ private static func serialize(_ r: PlacementResult) -> [String: Any] {
647
+ switch r {
648
+ case .entitled:
649
+ return ["type": "entitled"]
650
+ case .purchased(let productId):
651
+ return ["type": "purchased", "productId": productId]
652
+ case .dismissed:
653
+ return ["type": "dismissed"]
654
+ case .restored:
655
+ return ["type": "restored"]
656
+ case .oauthSuccess:
657
+ // No JS PlacementResult case for OAuth sign-in — map to neutral `dismissed`
658
+ // so the caller resumes without a spurious error (parity with Flutter).
659
+ return ["type": "dismissed"]
660
+ case .error(let err):
661
+ return ["type": "error", "message": err.localizedDescription]
662
+ }
663
+ }
664
+
665
+ /// PresentationResult (BillDog.swift:3844) → Placement.ts PresentationResult.
666
+ private static func serialize(_ r: PresentationResult) -> [String: Any] {
667
+ switch r {
668
+ case .paywall:
669
+ return ["type": "paywall"]
670
+ case .userSubscribed:
671
+ return ["type": "userSubscribed"]
672
+ case .holdout:
673
+ return ["type": "holdout"]
674
+ case .noPaywall(let reason):
675
+ return ["type": "noPaywall", "reason": reason]
676
+ case .error(let err):
677
+ return ["type": "error", "message": err.localizedDescription]
678
+ }
679
+ }
680
+
681
+ /// CustomerInfo (CustomerInfo.swift:169) → CustomerInfo.ts shape.
682
+ private static func serialize(_ info: CustomerInfo) -> [String: Any] {
683
+ var m: [String: Any] = [
684
+ "userId": info.userId,
685
+ "entitlements": info.entitlements.map { serialize($0) },
686
+ "activeSubscriptions": info.activeSubscriptions,
687
+ "productEntitlementMapping": info.productEntitlementMapping,
688
+ "isInTrial": info.isInTrial,
689
+ "isInGracePeriod": info.isInGracePeriod,
690
+ "hasExpiredSubscription": info.hasExpiredSubscription
691
+ ]
692
+ if let orig = info.originalAppUserId { m["originalAppUserId"] = orig }
693
+ return m
694
+ }
695
+
696
+ /// EntitlementInfo (CustomerInfo.swift:77) → EntitlementInfo.ts shape.
697
+ private static func serialize(_ e: EntitlementInfo) -> [String: Any] {
698
+ var m: [String: Any] = [
699
+ "identifier": e.identifier,
700
+ "isActive": e.isActive,
701
+ "willRenew": e.willRenew,
702
+ "isLifetime": e.isLifetime,
703
+ "productIds": e.productIds
704
+ ]
705
+ if let v = e.expiresAt { m["expiresAt"] = v }
706
+ if let v = e.store { m["store"] = v }
707
+ if let v = e.grantedAt { m["grantedAt"] = v }
708
+ if let v = e.displayName { m["displayName"] = v }
709
+ if let v = e.state { m["state"] = v }
710
+ if let v = e.startsAt { m["startsAt"] = v }
711
+ if let v = e.renewedAt { m["renewedAt"] = v }
712
+ if let v = e.latestProductId { m["latestProductId"] = v }
713
+ return m
714
+ }
715
+ }