@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,192 @@
1
+ /**
2
+ * useOfferings Hook - Fetches offerings list with filtering and pagination
3
+ */
4
+
5
+ import { useState, useEffect, useCallback } from 'react';
6
+ import { ApiClient } from '../utils/ApiClient';
7
+
8
+ interface Package {
9
+ id: string;
10
+ package_identifier: string;
11
+ position: number;
12
+ metadata?: Record<string, any>;
13
+ product_id?: string;
14
+ product?: {
15
+ id: string;
16
+ product_identifier: string;
17
+ display_name: string;
18
+ description?: string;
19
+ type: string;
20
+ platform: string;
21
+ price_usd?: number;
22
+ currency?: string;
23
+ };
24
+ }
25
+
26
+ interface PaywallOffering {
27
+ is_primary: boolean;
28
+ paywall: {
29
+ id: string;
30
+ identifier: string;
31
+ name: string;
32
+ configuration: any;
33
+ version: number;
34
+ };
35
+ }
36
+
37
+ interface Offering {
38
+ id: string;
39
+ identifier: string;
40
+ display_name: string;
41
+ description?: string;
42
+ notes?: string;
43
+ is_current: boolean;
44
+ is_default: boolean;
45
+ metadata?: Record<string, any>;
46
+ created_at: string;
47
+ updated_at: string;
48
+ offering_packages?: Package[];
49
+ paywall_offerings?: PaywallOffering[];
50
+ }
51
+
52
+ export interface UseOfferingsOptions {
53
+ /** @deprecated Use BillDogConfiguration.configure({ apiKey }) instead */
54
+ apiKey?: string;
55
+ skipFetch?: boolean;
56
+ limit?: number;
57
+ offset?: number;
58
+ is_current?: boolean;
59
+ is_default?: boolean;
60
+ identifier?: string;
61
+ expand?: 'packages' | 'products' | 'paywall' | string;
62
+ sort_by?: 'created_at' | 'display_name' | 'identifier';
63
+ sort_order?: 'asc' | 'desc';
64
+ }
65
+
66
+ interface Pagination {
67
+ total: number;
68
+ limit: number;
69
+ offset: number;
70
+ has_more: boolean;
71
+ }
72
+
73
+ export interface UseOfferingsResult {
74
+ offerings: Offering[];
75
+ currentOffering: Offering | null;
76
+ allOfferings: Record<string, Offering> | null;
77
+ pagination: Pagination | null;
78
+ loading: boolean;
79
+ error: Error | null;
80
+ refetch: () => Promise<void>;
81
+ fetchCurrentOffering: () => Promise<Offering | null>;
82
+ fetchOfferingForPlacement: (placementIdentifier: string, userContext?: Record<string, any>) => Promise<any>;
83
+ }
84
+
85
+ export const useOfferings = ({
86
+ apiKey: apiKeyOverride,
87
+ skipFetch = false,
88
+ limit = 20,
89
+ offset = 0,
90
+ is_current,
91
+ is_default,
92
+ identifier,
93
+ expand,
94
+ sort_by = 'created_at',
95
+ sort_order = 'desc',
96
+ }: UseOfferingsOptions): UseOfferingsResult => {
97
+ const [offerings, setOfferings] = useState<Offering[]>([]);
98
+ const [currentOffering, setCurrentOffering] = useState<Offering | null>(null);
99
+ const [allOfferings, setAllOfferings] = useState<Record<string, Offering> | null>(null);
100
+ const [pagination, setPagination] = useState<Pagination | null>(null);
101
+ const [loading, setLoading] = useState(false);
102
+ const [error, setError] = useState<Error | null>(null);
103
+
104
+ const resolvedApiKey = ApiClient.resolveApiKey(apiKeyOverride);
105
+ const deprecatedOptions = { apiKey: apiKeyOverride };
106
+
107
+ const fetchOfferings = async () => {
108
+ if (skipFetch || !resolvedApiKey) {
109
+ return;
110
+ }
111
+
112
+ setLoading(true);
113
+ setError(null);
114
+
115
+ try {
116
+ const body: any = {
117
+ limit,
118
+ offset,
119
+ sort_by,
120
+ sort_order,
121
+ };
122
+
123
+ if (is_current !== undefined) body.is_current = is_current;
124
+ if (is_default !== undefined) body.is_default = is_default;
125
+ if (identifier) body.identifier = identifier;
126
+ if (expand) body.expand = expand;
127
+
128
+ const json = await ApiClient.post('list-offerings', body, deprecatedOptions);
129
+
130
+ // Phase M4: Extract from wrapped envelope { success, data, meta }
131
+ const data = json.data;
132
+
133
+ setOfferings(data.offerings || []);
134
+ setCurrentOffering(data.current_offering || null);
135
+ setAllOfferings(data.all_offerings || null);
136
+ setPagination(data.pagination || null);
137
+ } catch (err) {
138
+ setError(err instanceof Error ? err : new Error('Unknown error'));
139
+ } finally {
140
+ setLoading(false);
141
+ }
142
+ };
143
+
144
+ useEffect(() => {
145
+ fetchOfferings();
146
+ }, [resolvedApiKey, skipFetch, limit, offset, is_current, is_default, identifier, expand, sort_by, sort_order]);
147
+
148
+ const fetchCurrentOffering = useCallback(async (): Promise<Offering | null> => {
149
+ if (!resolvedApiKey) return null;
150
+ try {
151
+ const json = await ApiClient.post('fetch-offering', {
152
+ is_current: true,
153
+ }, deprecatedOptions);
154
+
155
+ const data = json.data;
156
+ return data.current_offering || null;
157
+ } catch (err) {
158
+ console.error('Error fetching current offering:', err);
159
+ return null;
160
+ }
161
+ }, [resolvedApiKey, apiKeyOverride]);
162
+
163
+ const fetchOfferingForPlacement = useCallback(async (
164
+ placementIdentifier: string,
165
+ userContext?: Record<string, any>
166
+ ): Promise<any> => {
167
+ if (!resolvedApiKey) return null;
168
+ try {
169
+ const json = await ApiClient.post('fetch-offerings-for-placement', {
170
+ placement_identifier: placementIdentifier,
171
+ ...userContext,
172
+ }, deprecatedOptions);
173
+
174
+ return json.data;
175
+ } catch (err) {
176
+ console.error('Error fetching offering for placement:', err);
177
+ return null;
178
+ }
179
+ }, [resolvedApiKey, apiKeyOverride]);
180
+
181
+ return {
182
+ offerings,
183
+ currentOffering,
184
+ allOfferings,
185
+ pagination,
186
+ loading,
187
+ error,
188
+ refetch: fetchOfferings,
189
+ fetchCurrentOffering,
190
+ fetchOfferingForPlacement,
191
+ };
192
+ };
@@ -0,0 +1,154 @@
1
+ /**
2
+ * usePaywall Hook - Fetches paywall configuration from API
3
+ */
4
+
5
+ import { useState, useEffect } from 'react';
6
+ import { PaywallConfiguration } from '../types';
7
+ import { AdvancedPaywallCache, AdvancedCacheConfig } from '../AdvancedPaywallCache';
8
+ import { detectDeviceCategory } from '../utils/deviceDetection';
9
+ import { resolveConfiguration, MultiPaywallConfig } from '../utils/configurationResolver';
10
+ import { ApiClient } from '../utils/ApiClient';
11
+
12
+ export interface UsePaywallOptions {
13
+ paywallIdentifier?: string;
14
+ /** @deprecated Use BillDogConfiguration.configure({ apiKey }) instead */
15
+ apiKey?: string;
16
+ skipFetch?: boolean;
17
+ cacheConfig?: Partial<AdvancedCacheConfig>;
18
+ userContext?: {
19
+ app_user_id?: string;
20
+ country?: string;
21
+ app_version?: string;
22
+ platform?: string;
23
+ sdk_version?: string;
24
+ custom_attributes?: Record<string, string>;
25
+ };
26
+ }
27
+
28
+ export interface UsePaywallResult {
29
+ configuration: PaywallConfiguration | null;
30
+ loading: boolean;
31
+ error: Error | null;
32
+ refetch: () => Promise<void>;
33
+ }
34
+
35
+ export const usePaywall = ({
36
+ paywallIdentifier,
37
+ apiKey: apiKeyOverride,
38
+ skipFetch = false,
39
+ cacheConfig,
40
+ userContext,
41
+ }: UsePaywallOptions): UsePaywallResult => {
42
+ const [configuration, setConfiguration] = useState<PaywallConfiguration | null>(null);
43
+ const [loading, setLoading] = useState(false);
44
+ const [error, setError] = useState<Error | null>(null);
45
+ const [cache] = useState(() => cacheConfig?.enabled !== false ? new AdvancedPaywallCache(cacheConfig || {}) : null);
46
+
47
+ const resolvedApiKey = ApiClient.resolveApiKey(apiKeyOverride);
48
+
49
+ const fetchPaywall = async () => {
50
+ if (skipFetch || !paywallIdentifier || !resolvedApiKey) {
51
+ return;
52
+ }
53
+
54
+ // Try cache first if enabled
55
+ if (cache) {
56
+ const cached = await cache.get(paywallIdentifier);
57
+ if (cached && Date.now() < cached.expiresAt) {
58
+ console.log(`[usePaywall] Cache HIT for ${paywallIdentifier}`);
59
+ setConfiguration(cached.paywall.configuration);
60
+ setLoading(false);
61
+
62
+ // Check for updates in background
63
+ checkAndUpdateInBackground(paywallIdentifier, cached.version);
64
+ return;
65
+ }
66
+ }
67
+
68
+ setLoading(true);
69
+ setError(null);
70
+
71
+ try {
72
+ const json = await ApiClient.post('fetch-paywall', {
73
+ paywall_identifier: paywallIdentifier,
74
+ ...userContext,
75
+ }, {
76
+ apiKey: apiKeyOverride,
77
+ });
78
+
79
+ // Phase M4: Extract from wrapped envelope { success, data, meta }
80
+ const data = json.data;
81
+
82
+ if (data.paywall) {
83
+ // Detect device category
84
+ const deviceCategory = detectDeviceCategory();
85
+
86
+ // Check if we have multi-config or single config
87
+ let resolvedConfig: PaywallConfiguration | null = null;
88
+
89
+ if (data.paywall.configurations) {
90
+ // Multi-config mode
91
+ const multiConfig: MultiPaywallConfig = {
92
+ mode: data.paywall.configuration_mode || 'single',
93
+ ...data.paywall.configurations,
94
+ };
95
+ resolvedConfig = resolveConfiguration(multiConfig, deviceCategory);
96
+ } else if (data.paywall.configuration) {
97
+ // Single config mode (legacy)
98
+ resolvedConfig = data.paywall.configuration;
99
+ }
100
+
101
+ if (!resolvedConfig) {
102
+ throw new Error('No configuration available for current device');
103
+ }
104
+
105
+ setConfiguration(resolvedConfig);
106
+
107
+ // Cache the result
108
+ if (cache) {
109
+ await cache.set(paywallIdentifier, {
110
+ paywall: data.paywall,
111
+ version: data.paywall.version || 1,
112
+ cachedAt: Date.now(),
113
+ expiresAt: Date.now() + (cacheConfig?.maxCacheAge || 604800000),
114
+ });
115
+ }
116
+ } else {
117
+ throw new Error('Invalid paywall data');
118
+ }
119
+ } catch (err) {
120
+ setError(err instanceof Error ? err : new Error('Unknown error'));
121
+ } finally {
122
+ setLoading(false);
123
+ }
124
+ };
125
+
126
+ const checkAndUpdateInBackground = async (identifier: string, currentVersion: number) => {
127
+ try {
128
+ const versionInfo = await ApiClient.post('check-paywall-version', {
129
+ paywall_identifier: identifier,
130
+ current_version: currentVersion,
131
+ }, {
132
+ apiKey: apiKeyOverride,
133
+ });
134
+
135
+ if (versionInfo.has_update) {
136
+ console.log(`[usePaywall] New version available for ${identifier}`);
137
+ await fetchPaywall();
138
+ }
139
+ } catch (err) {
140
+ console.error('[usePaywall] Background update failed:', err);
141
+ }
142
+ };
143
+
144
+ useEffect(() => {
145
+ fetchPaywall();
146
+ }, [paywallIdentifier, resolvedApiKey, skipFetch]);
147
+
148
+ return {
149
+ configuration,
150
+ loading,
151
+ error,
152
+ refetch: fetchPaywall,
153
+ };
154
+ };
@@ -0,0 +1,143 @@
1
+ /**
2
+ * usePlacement Hook — placement registration with a feature-callback model
3
+ * (API_Calls_Migration P4, Track D).
4
+ *
5
+ * Mirrors the iOS `register` / Android `registerFeature` feature gate:
6
+ * - **non-gated** placement → `onFeatureUnlocked` runs immediately;
7
+ * - **gated** placement → the paywall is presented (via the native bridge)
8
+ * and `onFeatureUnlocked` runs only on an entitlement-granting outcome
9
+ * (already-entitled / purchased / restored). A dismissed/errored outcome
10
+ * does NOT run it.
11
+ *
12
+ * Gating is resolved in JS through the unified `get-wall-for-placement`
13
+ * endpoint (an unknown placement is auto-created server-side — P4 hybrid
14
+ * auto-create). `usePaywall` + `useOfferings` remain the escape-hatch hooks.
15
+ */
16
+
17
+ import { useCallback, useState } from 'react';
18
+ import { BillDog } from '../BillDog';
19
+ import { ApiClient } from '../utils/ApiClient';
20
+ import type { PlacementResult, PaywallPresentationHandler } from '../models/Placement';
21
+
22
+ interface ResolvedPlacement {
23
+ paywallId: string | null;
24
+ gated: boolean;
25
+ holdout: boolean;
26
+ }
27
+
28
+ export interface UsePlacementResult {
29
+ /**
30
+ * Result-handler variant — register a placement and get the
31
+ * `PlacementResult`. The paywall (if any) is presented via the native bridge.
32
+ */
33
+ register: (
34
+ placementId: string,
35
+ params?: Record<string, unknown>,
36
+ handler?: PaywallPresentationHandler,
37
+ ) => Promise<PlacementResult>;
38
+ /**
39
+ * Feature-closure variant — Superwall's `register("feature") { unlock() }`.
40
+ * Non-gated → `onFeatureUnlocked` runs immediately; gated → it runs only
41
+ * when the placement yields entitlement (entitled / purchased / restored).
42
+ */
43
+ registerFeature: (
44
+ placementId: string,
45
+ onFeatureUnlocked: () => void,
46
+ params?: Record<string, unknown>,
47
+ ) => Promise<PlacementResult>;
48
+ /** True while a registration is in flight. */
49
+ loading: boolean;
50
+ /** The most recent `PlacementResult`, or null. */
51
+ lastResult: PlacementResult | null;
52
+ }
53
+
54
+ /** Resolve a placement in JS via `get-wall-for-placement` to learn its gating. */
55
+ async function resolvePlacement(
56
+ placementId: string,
57
+ params?: Record<string, unknown>,
58
+ ): Promise<ResolvedPlacement | null> {
59
+ try {
60
+ const json: any = await ApiClient.post('get-wall-for-placement', {
61
+ placement_identifier: placementId,
62
+ ...(params ? { custom_attributes: params } : {}),
63
+ });
64
+ const data = json?.data ?? {};
65
+ if (data.placement?.source === 'sdk_autocreated') {
66
+ console.warn(
67
+ `[usePlacement] Placement '${placementId}' is unconfigured (auto-created on ` +
68
+ 'first sighting). Configure it in the BillDog dashboard — or fix the ' +
69
+ 'identifier if this is a typo.',
70
+ );
71
+ }
72
+ return {
73
+ paywallId: data.paywall?.id ?? null,
74
+ gated: data.presentation?.gated === true,
75
+ holdout: data.presentation?.holdout === true,
76
+ };
77
+ } catch {
78
+ // Resolution failed — caller falls back to presenting the paywall.
79
+ return null;
80
+ }
81
+ }
82
+
83
+ export const usePlacement = (): UsePlacementResult => {
84
+ const [loading, setLoading] = useState(false);
85
+ const [lastResult, setLastResult] = useState<PlacementResult | null>(null);
86
+
87
+ const register = useCallback(
88
+ async (
89
+ placementId: string,
90
+ params?: Record<string, unknown>,
91
+ handler?: PaywallPresentationHandler,
92
+ ): Promise<PlacementResult> => {
93
+ setLoading(true);
94
+ try {
95
+ const result = await BillDog.register(placementId, params, handler);
96
+ setLastResult(result);
97
+ return result;
98
+ } finally {
99
+ setLoading(false);
100
+ }
101
+ },
102
+ [],
103
+ );
104
+
105
+ const registerFeature = useCallback(
106
+ async (
107
+ placementId: string,
108
+ onFeatureUnlocked: () => void,
109
+ params?: Record<string, unknown>,
110
+ ): Promise<PlacementResult> => {
111
+ setLoading(true);
112
+ try {
113
+ const resolved = await resolvePlacement(placementId, params);
114
+
115
+ // Non-gated (or nothing to gate with) → unlock immediately.
116
+ if (resolved && (!resolved.gated || !resolved.paywallId)) {
117
+ onFeatureUnlocked();
118
+ const immediate: PlacementResult = { type: 'entitled' };
119
+ setLastResult(immediate);
120
+ return immediate;
121
+ }
122
+
123
+ // Gated — or resolution failed (fail safe by presenting). Present the
124
+ // paywall via the native bridge; unlock only on an entitlement outcome.
125
+ const result = await BillDog.register(placementId, params);
126
+ setLastResult(result);
127
+ if (
128
+ result.type === 'entitled' ||
129
+ result.type === 'purchased' ||
130
+ result.type === 'restored'
131
+ ) {
132
+ onFeatureUnlocked();
133
+ }
134
+ return result;
135
+ } finally {
136
+ setLoading(false);
137
+ }
138
+ },
139
+ [],
140
+ );
141
+
142
+ return { register, registerFeature, loading, lastResult };
143
+ };
@@ -0,0 +1,154 @@
1
+ import { useState, useCallback } from 'react';
2
+ import {
3
+ previewSubscriptionUpgrade,
4
+ upgradeSubscription,
5
+ downgradeSubscription,
6
+ type SubscriptionUpgradeOptions,
7
+ type ProrationPreview,
8
+ type SubscriptionUpgradeResult,
9
+ } from '../utils/subscriptionUpgrade';
10
+
11
+
12
+ export interface UseSubscriptionOptions {
13
+ /** @deprecated Use BillDogConfiguration.configure({ apiKey }) instead */
14
+ apiKey?: string;
15
+ }
16
+
17
+ export interface UseSubscriptionReturn {
18
+ /** Preview upgrade/downgrade costs before executing */
19
+ previewUpgrade: (newPriceId: string, options?: SubscriptionUpgradeOptions) => Promise<ProrationPreview>;
20
+ /** Upgrade to a higher-tier subscription */
21
+ upgradeSubscription: (newPriceId: string, options?: SubscriptionUpgradeOptions) => Promise<SubscriptionUpgradeResult>;
22
+ /** Downgrade to a lower-tier subscription */
23
+ downgradeSubscription: (newPriceId: string, options?: SubscriptionUpgradeOptions) => Promise<SubscriptionUpgradeResult>;
24
+ /** Whether an operation is in progress */
25
+ isLoading: boolean;
26
+ /** Last error that occurred */
27
+ error: Error | null;
28
+ /** Last proration preview */
29
+ preview: ProrationPreview | null;
30
+ /** Last upgrade/downgrade result */
31
+ result: SubscriptionUpgradeResult | null;
32
+ /** Clear error state */
33
+ clearError: () => void;
34
+ }
35
+
36
+ /**
37
+ * Hook for managing subscription upgrades and downgrades.
38
+ *
39
+ * Credentials are resolved from `BillDogConfiguration` by default.
40
+ * Pass an `apiKey` override only for backward compatibility.
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * // After calling BillDogConfiguration.configure({ apiKey: 'bd_...' })
45
+ * const { previewUpgrade, upgradeSubscription } = useSubscription();
46
+ *
47
+ * const preview = await previewUpgrade('price_new_plan', {
48
+ * customerId: 'customer_123',
49
+ * });
50
+ * ```
51
+ */
52
+ export function useSubscription(options: UseSubscriptionOptions = {}): UseSubscriptionReturn {
53
+ const [isLoading, setIsLoading] = useState(false);
54
+ const [error, setError] = useState<Error | null>(null);
55
+ const [preview, setPreview] = useState<ProrationPreview | null>(null);
56
+ const [result, setResult] = useState<SubscriptionUpgradeResult | null>(null);
57
+
58
+ const clearError = useCallback(() => {
59
+ setError(null);
60
+ }, []);
61
+
62
+ const handlePreviewUpgrade = useCallback(async (
63
+ newPriceId: string,
64
+ upgradeOptions?: SubscriptionUpgradeOptions
65
+ ): Promise<ProrationPreview> => {
66
+ setIsLoading(true);
67
+ setError(null);
68
+
69
+ try {
70
+ const previewResult = await previewSubscriptionUpgrade(
71
+ options.apiKey,
72
+ newPriceId,
73
+ upgradeOptions
74
+ );
75
+ setPreview(previewResult);
76
+ return previewResult;
77
+ } catch (err) {
78
+ const error = err instanceof Error ? err : new Error(String(err));
79
+ setError(error);
80
+ throw error;
81
+ } finally {
82
+ setIsLoading(false);
83
+ }
84
+ }, [options.apiKey]);
85
+
86
+ const handleUpgradeSubscription = useCallback(async (
87
+ newPriceId: string,
88
+ upgradeOptions?: SubscriptionUpgradeOptions
89
+ ): Promise<SubscriptionUpgradeResult> => {
90
+ setIsLoading(true);
91
+ setError(null);
92
+
93
+ try {
94
+ const upgradeResult = await upgradeSubscription(
95
+ options.apiKey,
96
+ newPriceId,
97
+ upgradeOptions
98
+ );
99
+ setResult(upgradeResult);
100
+
101
+ if (!upgradeResult.success) {
102
+ throw new Error(upgradeResult.errorMessage || 'Upgrade failed');
103
+ }
104
+
105
+ return upgradeResult;
106
+ } catch (err) {
107
+ const error = err instanceof Error ? err : new Error(String(err));
108
+ setError(error);
109
+ throw error;
110
+ } finally {
111
+ setIsLoading(false);
112
+ }
113
+ }, [options.apiKey]);
114
+
115
+ const handleDowngradeSubscription = useCallback(async (
116
+ newPriceId: string,
117
+ upgradeOptions?: SubscriptionUpgradeOptions
118
+ ): Promise<SubscriptionUpgradeResult> => {
119
+ setIsLoading(true);
120
+ setError(null);
121
+
122
+ try {
123
+ const downgradeResult = await downgradeSubscription(
124
+ options.apiKey,
125
+ newPriceId,
126
+ upgradeOptions
127
+ );
128
+ setResult(downgradeResult);
129
+
130
+ if (!downgradeResult.success) {
131
+ throw new Error(downgradeResult.errorMessage || 'Downgrade failed');
132
+ }
133
+
134
+ return downgradeResult;
135
+ } catch (err) {
136
+ const error = err instanceof Error ? err : new Error(String(err));
137
+ setError(error);
138
+ throw error;
139
+ } finally {
140
+ setIsLoading(false);
141
+ }
142
+ }, [options.apiKey]);
143
+
144
+ return {
145
+ previewUpgrade: handlePreviewUpgrade,
146
+ upgradeSubscription: handleUpgradeSubscription,
147
+ downgradeSubscription: handleDowngradeSubscription,
148
+ isLoading,
149
+ error,
150
+ preview,
151
+ result,
152
+ clearError,
153
+ };
154
+ }