@umituz/react-native-subscription 2.20.7 → 2.22.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.20.7",
3
+ "version": "2.22.0",
4
4
  "description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -13,7 +13,7 @@ import { usePaywallActions } from "../hooks/usePaywallActions";
13
13
  import type { PaywallContainerProps } from "./PaywallContainer.types";
14
14
 
15
15
  export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
16
- const { userId, isAnonymous: _isAnonymous = false, translations, mode = "subscription", legalUrls, features, heroImage, bestValueIdentifier, creditsLabel, creditAmounts, packageFilterConfig, source, onPurchaseSuccess, onPurchaseError, onAuthRequired, visible, onClose } = props;
16
+ const { userId, translations, mode = "subscription", legalUrls, features, heroImage, bestValueIdentifier, creditsLabel, creditAmounts, packageFilterConfig, source, onPurchaseSuccess, onPurchaseError, onAuthRequired, visible, onClose } = props;
17
17
 
18
18
  const { showPaywall, closePaywall, currentSource } = usePaywallVisibility();
19
19
  const isVisible = visible ?? showPaywall;
@@ -12,6 +12,9 @@ import type { SubscriptionFeature, PaywallTranslations, PaywallLegalUrls } from
12
12
  import { paywallModalStyles as styles } from "./PaywallModal.styles";
13
13
  import { PaywallFeatures } from "./PaywallFeatures";
14
14
  import { PaywallFooter } from "./PaywallFooter";
15
+ import { usePurchaseLoadingStore, selectIsPurchasing } from "../../../presentation/stores";
16
+
17
+ declare const __DEV__: boolean;
15
18
 
16
19
  export interface PaywallModalProps {
17
20
  visible: boolean;
@@ -33,21 +36,61 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
33
36
  const { visible, onClose, translations, packages = [], features = [], isLoading = false, legalUrls = {}, bestValueIdentifier, creditAmounts, creditsLabel, heroImage, onPurchase, onRestore } = props;
34
37
  const tokens = useAppDesignTokens();
35
38
  const [selectedPlanId, setSelectedPlanId] = useState<string | null>(null);
36
- const [isProcessing, setIsProcessing] = useState(false);
39
+ const [isLocalProcessing, setIsLocalProcessing] = useState(false);
40
+
41
+ // Global purchase loading state (for auto-execution after auth)
42
+ const isGlobalPurchasing = usePurchaseLoadingStore(selectIsPurchasing);
43
+ const { startPurchase, endPurchase } = usePurchaseLoadingStore();
44
+
45
+ // Combined processing state
46
+ const isProcessing = isLocalProcessing || isGlobalPurchasing;
37
47
 
38
48
  const handlePurchase = useCallback(async () => {
39
49
  if (!selectedPlanId || !onPurchase) return;
40
- setIsProcessing(true);
50
+
51
+ if (__DEV__) {
52
+ console.log("[PaywallModal] handlePurchase starting:", { selectedPlanId });
53
+ }
54
+
55
+ setIsLocalProcessing(true);
56
+ startPurchase(selectedPlanId, "manual");
57
+
41
58
  try {
42
59
  const pkg = packages.find((p) => p.product.identifier === selectedPlanId);
43
- if (pkg) await onPurchase(pkg);
44
- } finally { setIsProcessing(false); }
45
- }, [selectedPlanId, packages, onPurchase]);
60
+ if (pkg) {
61
+ if (__DEV__) {
62
+ console.log("[PaywallModal] Calling onPurchase:", { productId: pkg.product.identifier });
63
+ }
64
+ await onPurchase(pkg);
65
+ if (__DEV__) {
66
+ console.log("[PaywallModal] onPurchase completed");
67
+ }
68
+ }
69
+ } finally {
70
+ setIsLocalProcessing(false);
71
+ endPurchase();
72
+ if (__DEV__) {
73
+ console.log("[PaywallModal] handlePurchase finished");
74
+ }
75
+ }
76
+ }, [selectedPlanId, packages, onPurchase, startPurchase, endPurchase]);
46
77
 
47
78
  const handleRestore = useCallback(async () => {
48
79
  if (!onRestore || isProcessing) return;
49
- setIsProcessing(true);
50
- try { await onRestore(); } finally { setIsProcessing(false); }
80
+
81
+ if (__DEV__) {
82
+ console.log("[PaywallModal] handleRestore starting");
83
+ }
84
+
85
+ setIsLocalProcessing(true);
86
+ try {
87
+ await onRestore();
88
+ if (__DEV__) {
89
+ console.log("[PaywallModal] handleRestore completed");
90
+ }
91
+ } finally {
92
+ setIsLocalProcessing(false);
93
+ }
51
94
  }, [onRestore, isProcessing]);
52
95
 
53
96
  const handleLegalUrl = useCallback(async (url: string | undefined) => {
package/src/index.ts CHANGED
@@ -14,6 +14,13 @@ export type { ISubscriptionRepository } from "./application/ports/ISubscriptionR
14
14
 
15
15
  // Infrastructure Layer
16
16
  export { SubscriptionService, initializeSubscriptionService } from "./infrastructure/services/SubscriptionService";
17
+ export {
18
+ submitFeedback,
19
+ submitPaywallFeedback,
20
+ submitSettingsFeedback,
21
+ type FeedbackData,
22
+ type FeedbackSubmitResult,
23
+ } from "./infrastructure/services/FeedbackService";
17
24
  export { initializeSubscription, type SubscriptionInitConfig, type CreditPackageConfig } from "./infrastructure/services/SubscriptionInitializer";
18
25
  export { CreditsRepository, createCreditsRepository } from "./infrastructure/repositories/CreditsRepository";
19
26
  export { configureCreditsRepository, getCreditsRepository, getCreditsConfig, resetCreditsRepository, isCreditsRepositoryConfigured } from "./infrastructure/repositories/CreditsRepositoryProvider";
@@ -32,9 +39,13 @@ export * from "./presentation/components/details/PremiumDetailsCard";
32
39
  export * from "./presentation/components/details/PremiumStatusBadge";
33
40
  export * from "./presentation/components/sections/SubscriptionSection";
34
41
  export * from "./presentation/components/feedback/PaywallFeedbackModal";
42
+ export * from "./presentation/components/overlay";
35
43
  export * from "./presentation/screens/SubscriptionDetailScreen";
36
44
  export * from "./presentation/types/SubscriptionDetailTypes";
37
45
 
46
+ // Presentation Layer - Stores
47
+ export * from "./presentation/stores";
48
+
38
49
  // Credits Domain
39
50
  export type {
40
51
  CreditType,
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Feedback Service
3
+ * Handles feedback submission to Firestore
4
+ */
5
+
6
+ import { getFirestore } from "@umituz/react-native-firebase";
7
+ import { collection, addDoc } from "firebase/firestore";
8
+
9
+ export interface FeedbackData {
10
+ userId: string | null;
11
+ userEmail: string | null;
12
+ type: string;
13
+ title: string;
14
+ description: string;
15
+ rating?: number;
16
+ status?: string;
17
+ }
18
+
19
+ export interface FeedbackSubmitResult {
20
+ success: boolean;
21
+ error?: Error;
22
+ }
23
+
24
+ const FEEDBACK_COLLECTION = "feedback";
25
+
26
+ /**
27
+ * Submit feedback to Firestore
28
+ */
29
+ export async function submitFeedback(
30
+ data: FeedbackData
31
+ ): Promise<FeedbackSubmitResult> {
32
+ const db = getFirestore();
33
+
34
+ if (!db) {
35
+ if (__DEV__) {
36
+ console.warn("[FeedbackService] Firestore not available");
37
+ }
38
+ return { success: false, error: new Error("Firestore not available") };
39
+ }
40
+
41
+ try {
42
+ if (__DEV__) {
43
+ console.log("[FeedbackService] Submitting feedback:", {
44
+ type: data.type,
45
+ userId: data.userId?.slice(0, 8),
46
+ });
47
+ }
48
+
49
+ const now = new Date().toISOString();
50
+
51
+ await addDoc(collection(db, FEEDBACK_COLLECTION), {
52
+ userId: data.userId,
53
+ userEmail: data.userEmail,
54
+ type: data.type,
55
+ title: data.title,
56
+ description: data.description,
57
+ rating: data.rating ?? null,
58
+ status: data.status ?? "pending",
59
+ createdAt: now,
60
+ updatedAt: now,
61
+ });
62
+
63
+ if (__DEV__) {
64
+ console.log("[FeedbackService] Feedback submitted successfully");
65
+ }
66
+
67
+ return { success: true };
68
+ } catch (error) {
69
+ if (__DEV__) {
70
+ console.error("[FeedbackService] Submit error:", error);
71
+ }
72
+ return {
73
+ success: false,
74
+ error: error instanceof Error ? error : new Error("Unknown error"),
75
+ };
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Submit paywall decline feedback
81
+ */
82
+ export async function submitPaywallFeedback(
83
+ userId: string | null,
84
+ userEmail: string | null,
85
+ reason: string
86
+ ): Promise<FeedbackSubmitResult> {
87
+ return submitFeedback({
88
+ userId,
89
+ userEmail,
90
+ type: "paywall_declined",
91
+ title: "Paywall Declined",
92
+ description: reason,
93
+ });
94
+ }
95
+
96
+ /**
97
+ * Submit general settings feedback
98
+ */
99
+ export async function submitSettingsFeedback(
100
+ userId: string | null,
101
+ userEmail: string | null,
102
+ data: {
103
+ type?: string;
104
+ title?: string;
105
+ description: string;
106
+ rating?: number;
107
+ }
108
+ ): Promise<FeedbackSubmitResult> {
109
+ return submitFeedback({
110
+ userId,
111
+ userEmail,
112
+ type: data.type ?? "general",
113
+ title: data.title ?? "Settings Feedback",
114
+ description: data.description,
115
+ rating: data.rating,
116
+ });
117
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Purchase Loading Overlay
3
+ * Full-screen overlay shown during purchase operations
4
+ * Locks the UI and shows a spinner with optional message
5
+ */
6
+
7
+ import React from "react";
8
+ import { View, Modal, StyleSheet } from "react-native";
9
+ import { AtomicSpinner, AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
10
+ import { usePurchaseLoadingStore, selectIsPurchasing } from "../../stores";
11
+
12
+ export interface PurchaseLoadingOverlayProps {
13
+ /** Loading message to display */
14
+ loadingText?: string;
15
+ }
16
+
17
+ export const PurchaseLoadingOverlay: React.FC<PurchaseLoadingOverlayProps> = React.memo(
18
+ ({ loadingText }) => {
19
+ const tokens = useAppDesignTokens();
20
+ const isPurchasing = usePurchaseLoadingStore(selectIsPurchasing);
21
+
22
+ if (!isPurchasing) return null;
23
+
24
+ return (
25
+ <Modal visible transparent animationType="fade" statusBarTranslucent>
26
+ <View style={[styles.container, { backgroundColor: "rgba(0, 0, 0, 0.7)" }]}>
27
+ <View style={[styles.content, { backgroundColor: tokens.colors.surface }]}>
28
+ <AtomicSpinner size="lg" color="primary" />
29
+ {loadingText && (
30
+ <AtomicText
31
+ type="bodyLarge"
32
+ style={[styles.text, { color: tokens.colors.textPrimary }]}
33
+ >
34
+ {loadingText}
35
+ </AtomicText>
36
+ )}
37
+ </View>
38
+ </View>
39
+ </Modal>
40
+ );
41
+ }
42
+ );
43
+
44
+ PurchaseLoadingOverlay.displayName = "PurchaseLoadingOverlay";
45
+
46
+ const styles = StyleSheet.create({
47
+ container: {
48
+ flex: 1,
49
+ justifyContent: "center",
50
+ alignItems: "center",
51
+ },
52
+ content: {
53
+ paddingHorizontal: 32,
54
+ paddingVertical: 24,
55
+ borderRadius: 16,
56
+ alignItems: "center",
57
+ minWidth: 200,
58
+ },
59
+ text: {
60
+ marginTop: 16,
61
+ textAlign: "center",
62
+ },
63
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Overlay Components
3
+ */
4
+
5
+ export { PurchaseLoadingOverlay, type PurchaseLoadingOverlayProps } from "./PurchaseLoadingOverlay";
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Feedback Submit Hooks
3
+ * React hooks for submitting feedback to Firestore
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import { useAuth } from "@umituz/react-native-auth";
8
+ import {
9
+ submitPaywallFeedback,
10
+ submitSettingsFeedback,
11
+ } from "../../../infrastructure/services/FeedbackService";
12
+
13
+ export interface UsePaywallFeedbackSubmitOptions {
14
+ onSuccess?: () => void;
15
+ onError?: (error: Error) => void;
16
+ onComplete?: () => void;
17
+ }
18
+
19
+ /**
20
+ * Hook for submitting paywall decline feedback
21
+ */
22
+ export function usePaywallFeedbackSubmit(
23
+ options: UsePaywallFeedbackSubmitOptions = {}
24
+ ) {
25
+ const { user } = useAuth();
26
+ const { onSuccess, onError, onComplete } = options;
27
+
28
+ const submit = useCallback(
29
+ async (reason: string) => {
30
+ if (__DEV__) {
31
+ console.log("[usePaywallFeedbackSubmit] Submitting:", {
32
+ userId: user?.uid?.slice(0, 8),
33
+ reason: reason.slice(0, 20),
34
+ });
35
+ }
36
+
37
+ const result = await submitPaywallFeedback(
38
+ user?.uid ?? null,
39
+ user?.email ?? null,
40
+ reason
41
+ );
42
+
43
+ if (result.success) {
44
+ onSuccess?.();
45
+ } else if (result.error) {
46
+ onError?.(result.error);
47
+ }
48
+
49
+ onComplete?.();
50
+ },
51
+ [user, onSuccess, onError, onComplete]
52
+ );
53
+
54
+ return { submit };
55
+ }
56
+
57
+ export interface SettingsFeedbackData {
58
+ type?: string;
59
+ title?: string;
60
+ description: string;
61
+ rating?: number;
62
+ }
63
+
64
+ export interface UseSettingsFeedbackSubmitOptions {
65
+ onSuccess?: () => void;
66
+ onError?: (error: Error) => void;
67
+ }
68
+
69
+ /**
70
+ * Hook for submitting general settings feedback
71
+ */
72
+ export function useSettingsFeedbackSubmit(
73
+ options: UseSettingsFeedbackSubmitOptions = {}
74
+ ) {
75
+ const { user } = useAuth();
76
+ const { onSuccess, onError } = options;
77
+
78
+ const submit = useCallback(
79
+ async (data: SettingsFeedbackData) => {
80
+ if (__DEV__) {
81
+ console.log("[useSettingsFeedbackSubmit] Submitting:", {
82
+ userId: user?.uid?.slice(0, 8),
83
+ type: data.type,
84
+ });
85
+ }
86
+
87
+ const result = await submitSettingsFeedback(
88
+ user?.uid ?? null,
89
+ user?.email ?? null,
90
+ data
91
+ );
92
+
93
+ if (result.success) {
94
+ onSuccess?.();
95
+ } else if (result.error) {
96
+ onError?.(result.error);
97
+ }
98
+
99
+ return result;
100
+ },
101
+ [user, onSuccess, onError]
102
+ );
103
+
104
+ return { submit };
105
+ }
@@ -21,3 +21,4 @@ export * from "./useSubscriptionStatus";
21
21
  export * from "./useUserTier";
22
22
  export * from "./useUserTierWithRepository";
23
23
  export * from "./feedback/usePaywallFeedback";
24
+ export * from "./feedback/useFeedbackSubmit";
@@ -7,6 +7,7 @@ import { useEffect, useRef, useCallback } from "react";
7
7
  import { getSavedPurchase, clearSavedPurchase } from "./useAuthAwarePurchase";
8
8
  import { usePremium } from "./usePremium";
9
9
  import { SubscriptionManager } from "../../revenuecat";
10
+ import { usePurchaseLoadingStore } from "../stores";
10
11
 
11
12
  declare const __DEV__: boolean;
12
13
 
@@ -26,6 +27,7 @@ export const useSavedPurchaseAutoExecution = (
26
27
  ): UseSavedPurchaseAutoExecutionResult => {
27
28
  const { userId, isAnonymous, onSuccess, onError } = params;
28
29
  const { purchasePackage } = usePremium(userId);
30
+ const { startPurchase, endPurchase } = usePurchaseLoadingStore();
29
31
 
30
32
  const prevIsAnonymousRef = useRef<boolean | undefined>(undefined);
31
33
  const isExecutingRef = useRef(false);
@@ -36,7 +38,8 @@ export const useSavedPurchaseAutoExecution = (
36
38
 
37
39
  if (__DEV__) {
38
40
  console.log(
39
- "[SavedPurchaseAutoExecution] Waiting for RevenueCat initialization..."
41
+ "[SavedPurchaseAutoExecution] Waiting for RevenueCat initialization...",
42
+ { userId: userId.slice(0, 8), productId: savedPurchase.pkg.product.identifier }
40
43
  );
41
44
  }
42
45
 
@@ -60,16 +63,26 @@ export const useSavedPurchaseAutoExecution = (
60
63
 
61
64
  if (__DEV__) {
62
65
  console.log(
63
- "[SavedPurchaseAutoExecution] RevenueCat ready, executing purchase:",
64
- pkg.product.identifier
66
+ "[SavedPurchaseAutoExecution] RevenueCat ready, starting purchase...",
67
+ { productId: pkg.product.identifier, userId: userId.slice(0, 8) }
65
68
  );
66
69
  }
67
70
 
71
+ // Start global loading state
72
+ startPurchase(pkg.product.identifier, "auto-execution");
73
+
68
74
  try {
75
+ if (__DEV__) {
76
+ console.log("[SavedPurchaseAutoExecution] Calling purchasePackage...");
77
+ }
78
+
69
79
  const success = await purchasePackage(pkg);
70
80
 
71
81
  if (__DEV__) {
72
- console.log("[SavedPurchaseAutoExecution] Purchase result:", success);
82
+ console.log("[SavedPurchaseAutoExecution] Purchase completed:", {
83
+ success,
84
+ productId: pkg.product.identifier
85
+ });
73
86
  }
74
87
 
75
88
  if (success && onSuccess) {
@@ -77,13 +90,22 @@ export const useSavedPurchaseAutoExecution = (
77
90
  }
78
91
  } catch (error) {
79
92
  if (__DEV__) {
80
- console.error("[SavedPurchaseAutoExecution] Purchase failed:", error);
93
+ console.error("[SavedPurchaseAutoExecution] Purchase error:", {
94
+ error,
95
+ productId: pkg.product.identifier
96
+ });
81
97
  }
82
98
  if (onError && error instanceof Error) {
83
99
  onError(error);
84
100
  }
85
101
  } finally {
102
+ // End global loading state
103
+ endPurchase();
86
104
  isExecutingRef.current = false;
105
+
106
+ if (__DEV__) {
107
+ console.log("[SavedPurchaseAutoExecution] Purchase flow finished");
108
+ }
87
109
  }
88
110
 
89
111
  return;
@@ -99,7 +121,7 @@ export const useSavedPurchaseAutoExecution = (
99
121
  }
100
122
  clearSavedPurchase();
101
123
  isExecutingRef.current = false;
102
- }, [userId, purchasePackage, onSuccess, onError]);
124
+ }, [userId, purchasePackage, onSuccess, onError, startPurchase, endPurchase]);
103
125
 
104
126
  useEffect(() => {
105
127
  const isAuthenticated = !!userId && !isAnonymous;
@@ -110,7 +132,7 @@ export const useSavedPurchaseAutoExecution = (
110
132
  const becameAuthenticated = wasAnonymous && isAuthenticated;
111
133
 
112
134
  if (__DEV__) {
113
- console.log("[SavedPurchaseAutoExecution] Check:", {
135
+ console.log("[SavedPurchaseAutoExecution] Auth state check:", {
114
136
  userId: userId?.slice(0, 8),
115
137
  prevIsAnonymous,
116
138
  isAnonymous,
@@ -118,11 +140,15 @@ export const useSavedPurchaseAutoExecution = (
118
140
  wasAnonymous,
119
141
  becameAuthenticated,
120
142
  hasSavedPurchase: !!savedPurchase,
143
+ savedProductId: savedPurchase?.pkg.product.identifier,
121
144
  willExecute: becameAuthenticated && !!savedPurchase && !isExecutingRef.current,
122
145
  });
123
146
  }
124
147
 
125
148
  if (becameAuthenticated && savedPurchase && !isExecutingRef.current) {
149
+ if (__DEV__) {
150
+ console.log("[SavedPurchaseAutoExecution] Triggering auto-execution...");
151
+ }
126
152
  executeWithWait();
127
153
  }
128
154
 
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Presentation Layer - Stores
3
+ */
4
+
5
+ export {
6
+ usePurchaseLoadingStore,
7
+ selectIsPurchasing,
8
+ selectPurchasingProductId,
9
+ selectPurchaseSource,
10
+ type PurchaseLoadingState,
11
+ type PurchaseLoadingActions,
12
+ type PurchaseLoadingStore,
13
+ } from "./purchaseLoadingStore";
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Purchase Loading Store
3
+ * Global state for tracking purchase loading across the app
4
+ * Used by both PaywallModal and useSavedPurchaseAutoExecution
5
+ */
6
+
7
+ import { create } from "zustand";
8
+
9
+ declare const __DEV__: boolean;
10
+
11
+ export interface PurchaseLoadingState {
12
+ /** Whether a purchase is in progress */
13
+ isPurchasing: boolean;
14
+ /** Product identifier being purchased */
15
+ purchasingProductId: string | null;
16
+ /** Source of the purchase (manual, auto-execution, etc.) */
17
+ purchaseSource: "manual" | "auto-execution" | null;
18
+ }
19
+
20
+ export interface PurchaseLoadingActions {
21
+ /** Start purchase loading state */
22
+ startPurchase: (productId: string, source: "manual" | "auto-execution") => void;
23
+ /** End purchase loading state */
24
+ endPurchase: () => void;
25
+ /** Reset all state */
26
+ reset: () => void;
27
+ }
28
+
29
+ export type PurchaseLoadingStore = PurchaseLoadingState & PurchaseLoadingActions;
30
+
31
+ const initialState: PurchaseLoadingState = {
32
+ isPurchasing: false,
33
+ purchasingProductId: null,
34
+ purchaseSource: null,
35
+ };
36
+
37
+ export const usePurchaseLoadingStore = create<PurchaseLoadingStore>((set) => ({
38
+ ...initialState,
39
+
40
+ startPurchase: (productId, source) => {
41
+ if (__DEV__) {
42
+ console.log("[PurchaseLoadingStore] startPurchase:", { productId, source });
43
+ }
44
+ set({
45
+ isPurchasing: true,
46
+ purchasingProductId: productId,
47
+ purchaseSource: source,
48
+ });
49
+ },
50
+
51
+ endPurchase: () => {
52
+ if (__DEV__) {
53
+ console.log("[PurchaseLoadingStore] endPurchase");
54
+ }
55
+ set({
56
+ isPurchasing: false,
57
+ purchasingProductId: null,
58
+ purchaseSource: null,
59
+ });
60
+ },
61
+
62
+ reset: () => {
63
+ if (__DEV__) {
64
+ console.log("[PurchaseLoadingStore] reset");
65
+ }
66
+ set(initialState);
67
+ },
68
+ }));
69
+
70
+ // Selectors for optimized re-renders
71
+ export const selectIsPurchasing = (state: PurchaseLoadingStore) => state.isPurchasing;
72
+ export const selectPurchasingProductId = (state: PurchaseLoadingStore) => state.purchasingProductId;
73
+ export const selectPurchaseSource = (state: PurchaseLoadingStore) => state.purchaseSource;
@@ -65,11 +65,26 @@ export async function handlePurchase(
65
65
 
66
66
  try {
67
67
  if (__DEV__) {
68
- console.log('[DEBUG PurchaseHandler] Calling Purchases.purchasePackage...');
68
+ console.log('[DEBUG PurchaseHandler] Calling Purchases.purchasePackage...', {
69
+ productId: pkg.product.identifier,
70
+ packageIdentifier: pkg.identifier,
71
+ offeringIdentifier: pkg.offeringIdentifier,
72
+ timestamp: new Date().toISOString()
73
+ });
69
74
  }
75
+
76
+ const startTime = Date.now();
70
77
  const purchaseResult = await Purchases.purchasePackage(pkg);
78
+ const duration = Date.now() - startTime;
71
79
  const customerInfo = purchaseResult.customerInfo;
72
80
 
81
+ if (__DEV__) {
82
+ console.log('[DEBUG PurchaseHandler] Purchases.purchasePackage returned', {
83
+ duration: `${duration}ms`,
84
+ productId: pkg.product.identifier
85
+ });
86
+ }
87
+
73
88
  if (__DEV__) {
74
89
  console.log('[DEBUG PurchaseHandler] Purchase completed', {
75
90
  productId: pkg.product.identifier,