@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,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
|
+
}
|