@umituz/react-native-subscription 2.20.1 → 2.20.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.20.1",
3
+ "version": "2.20.2",
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",
@@ -2,7 +2,6 @@ import { useCallback } from "react";
2
2
  import type { PurchasesPackage } from "react-native-purchases";
3
3
  import { useRestorePurchase } from "../../../revenuecat/presentation/hooks/useRestorePurchase";
4
4
  import { useAuthAwarePurchase } from "../../../presentation/hooks/useAuthAwarePurchase";
5
- import { usePendingPurchaseStore } from "../../../infrastructure/stores/PendingPurchaseStore";
6
5
  import type { PurchaseSource } from "../../../domain/entities/Credits";
7
6
 
8
7
  declare const __DEV__: boolean;
@@ -26,15 +25,8 @@ export const usePaywallActions = ({
26
25
  }: UsePaywallActionsProps) => {
27
26
  const { handlePurchase: authAwarePurchase } = useAuthAwarePurchase({ source, userId });
28
27
  const { mutateAsync: restorePurchases } = useRestorePurchase(userId);
29
- const { isExecuting } = usePendingPurchaseStore();
30
28
 
31
29
  const handlePurchase = useCallback(async (pkg: PurchasesPackage) => {
32
- // Block if a pending purchase is already being executed
33
- if (isExecuting) {
34
- if (__DEV__) console.log("[PaywallActions] Purchase blocked - pending purchase in progress");
35
- return;
36
- }
37
-
38
30
  try {
39
31
  if (__DEV__) console.log("[PaywallActions] Purchase started:", pkg.product.identifier);
40
32
  const res = await authAwarePurchase(pkg, source);
@@ -46,7 +38,7 @@ export const usePaywallActions = ({
46
38
  const message = err instanceof Error ? err.message : String(err);
47
39
  onPurchaseError?.(message);
48
40
  }
49
- }, [authAwarePurchase, source, onClose, onPurchaseSuccess, onPurchaseError, isExecuting]);
41
+ }, [authAwarePurchase, source, onClose, onPurchaseSuccess, onPurchaseError]);
50
42
 
51
43
  const handleRestore = useCallback(async () => {
52
44
  try {
package/src/index.ts CHANGED
@@ -18,13 +18,11 @@ export { initializeSubscription, type SubscriptionInitConfig, type CreditPackage
18
18
  export { CreditsRepository, createCreditsRepository } from "./infrastructure/repositories/CreditsRepository";
19
19
  export { configureCreditsRepository, getCreditsRepository, getCreditsConfig, resetCreditsRepository, isCreditsRepositoryConfigured } from "./infrastructure/repositories/CreditsRepositoryProvider";
20
20
  export {
21
- usePendingPurchaseStore,
22
- type PendingPurchaseData,
23
- } from "./infrastructure/stores/PendingPurchaseStore";
24
- export {
25
- usePendingPurchaseHandler,
26
- type UsePendingPurchaseHandlerParams,
27
- } from "./presentation/hooks/usePendingPurchaseHandler";
21
+ getSavedPurchase,
22
+ clearSavedPurchase,
23
+ configureAuthProvider,
24
+ type PurchaseAuthProvider,
25
+ } from "./presentation/hooks/useAuthAwarePurchase";
28
26
 
29
27
  // Presentation Layer - Hooks
30
28
  export * from "./presentation/hooks";
@@ -1,31 +1,38 @@
1
1
  /**
2
2
  * Auth-Aware Purchase Hook
3
- * Uses globally configured auth provider
4
- * Configure once at app start with configureAuthProvider()
3
+ * Handles purchase flow with authentication requirement
5
4
  */
6
5
 
7
6
  import { useCallback } from "react";
8
7
  import type { PurchasesPackage } from "react-native-purchases";
9
8
  import { usePremium } from "./usePremium";
10
- import { usePendingPurchaseStore } from "../../infrastructure/stores/PendingPurchaseStore";
11
9
  import type { PurchaseSource } from "../../domain/entities/Credits";
12
10
 
13
11
  declare const __DEV__: boolean;
14
12
 
15
13
  export interface PurchaseAuthProvider {
16
- isAuthenticated: () => boolean;
17
- showAuthModal: () => void;
14
+ isAuthenticated: () => boolean;
15
+ showAuthModal: () => void;
18
16
  }
19
17
 
20
- // Global auth provider - configured once at app start
21
18
  let globalAuthProvider: PurchaseAuthProvider | null = null;
19
+ let savedPackage: PurchasesPackage | null = null;
20
+ let savedSource: PurchaseSource | null = null;
22
21
 
23
- /**
24
- * Configure auth provider for purchases
25
- * Call this once at app initialization
26
- */
27
22
  export const configureAuthProvider = (provider: PurchaseAuthProvider): void => {
28
- globalAuthProvider = provider;
23
+ globalAuthProvider = provider;
24
+ };
25
+
26
+ export const getSavedPurchase = (): { pkg: PurchasesPackage; source: PurchaseSource } | null => {
27
+ if (savedPackage && savedSource) {
28
+ return { pkg: savedPackage, source: savedSource };
29
+ }
30
+ return null;
31
+ };
32
+
33
+ export const clearSavedPurchase = (): void => {
34
+ savedPackage = null;
35
+ savedSource = null;
29
36
  };
30
37
 
31
38
  export interface UseAuthAwarePurchaseParams {
@@ -34,84 +41,81 @@ export interface UseAuthAwarePurchaseParams {
34
41
  }
35
42
 
36
43
  export interface UseAuthAwarePurchaseResult {
37
- handlePurchase: (pkg: PurchasesPackage, source?: PurchaseSource) => Promise<boolean>;
38
- handleRestore: () => Promise<boolean>;
44
+ handlePurchase: (pkg: PurchasesPackage, source?: PurchaseSource) => Promise<boolean>;
45
+ handleRestore: () => Promise<boolean>;
46
+ executeSavedPurchase: () => Promise<boolean>;
39
47
  }
40
48
 
41
49
  export const useAuthAwarePurchase = (
42
50
  params?: UseAuthAwarePurchaseParams
43
51
  ): UseAuthAwarePurchaseResult => {
44
- const { purchasePackage, restorePurchase, closePaywall } = usePremium(params?.userId);
45
- const { setPendingPurchase } = usePendingPurchaseStore();
46
-
47
- const handlePurchase = useCallback(
48
- async (pkg: PurchasesPackage, source?: PurchaseSource): Promise<boolean> => {
49
- // SECURITY: Block purchase if auth provider not configured
50
- if (!globalAuthProvider) {
51
- if (__DEV__) {
52
- console.error(
53
- "[useAuthAwarePurchase] CRITICAL: Auth provider not configured. " +
54
- "Call configureAuthProvider() at app start. Purchase blocked for security.",
55
- );
56
- }
57
- return false;
58
- }
59
-
60
- // Block purchase if user not authenticated (anonymous users cannot purchase)
61
- if (!globalAuthProvider.isAuthenticated()) {
62
- if (__DEV__) {
63
- console.log(
64
- "[useAuthAwarePurchase] User not authenticated, saving pending purchase and opening auth modal",
65
- );
66
- }
67
-
68
- // Save pending purchase
69
- setPendingPurchase({
70
- package: pkg,
71
- source: source || params?.source || "settings",
72
- selectedAt: Date.now(),
73
- });
74
-
75
- closePaywall();
76
- globalAuthProvider.showAuthModal();
77
- return false;
78
- }
79
-
80
- return purchasePackage(pkg);
81
- },
82
- [purchasePackage, closePaywall, setPendingPurchase, params?.source],
83
- );
84
-
85
- const handleRestore = useCallback(async (): Promise<boolean> => {
86
- // SECURITY: Block restore if auth provider not configured
87
- if (!globalAuthProvider) {
88
- if (__DEV__) {
89
- console.error(
90
- "[useAuthAwarePurchase] CRITICAL: Auth provider not configured. " +
91
- "Call configureAuthProvider() at app start. Restore blocked for security.",
92
- );
93
- }
94
- // Block restore - never allow without auth provider
95
- return false;
96
- }
52
+ const { purchasePackage, restorePurchase, closePaywall } = usePremium(params?.userId);
97
53
 
98
- // Block restore if user not authenticated
99
- if (!globalAuthProvider.isAuthenticated()) {
100
- if (__DEV__) {
101
- console.log(
102
- "[useAuthAwarePurchase] User not authenticated, opening auth modal",
103
- );
104
- }
105
- closePaywall();
106
- globalAuthProvider.showAuthModal();
107
- return false;
54
+ const handlePurchase = useCallback(
55
+ async (pkg: PurchasesPackage, source?: PurchaseSource): Promise<boolean> => {
56
+ if (!globalAuthProvider) {
57
+ if (__DEV__) {
58
+ console.error("[useAuthAwarePurchase] Auth provider not configured");
108
59
  }
60
+ return false;
61
+ }
109
62
 
110
- return restorePurchase();
111
- }, [restorePurchase, closePaywall]);
112
-
113
- return {
114
- handlePurchase,
115
- handleRestore,
116
- };
63
+ if (!globalAuthProvider.isAuthenticated()) {
64
+ if (__DEV__) {
65
+ console.log("[useAuthAwarePurchase] Not authenticated, saving and showing auth");
66
+ }
67
+ savedPackage = pkg;
68
+ savedSource = source || params?.source || "settings";
69
+ closePaywall();
70
+ globalAuthProvider.showAuthModal();
71
+ return false;
72
+ }
73
+
74
+ return purchasePackage(pkg);
75
+ },
76
+ [purchasePackage, closePaywall, params?.source]
77
+ );
78
+
79
+ const handleRestore = useCallback(async (): Promise<boolean> => {
80
+ if (!globalAuthProvider) {
81
+ if (__DEV__) {
82
+ console.error("[useAuthAwarePurchase] Auth provider not configured");
83
+ }
84
+ return false;
85
+ }
86
+
87
+ if (!globalAuthProvider.isAuthenticated()) {
88
+ if (__DEV__) {
89
+ console.log("[useAuthAwarePurchase] Not authenticated for restore");
90
+ }
91
+ closePaywall();
92
+ globalAuthProvider.showAuthModal();
93
+ return false;
94
+ }
95
+
96
+ return restorePurchase();
97
+ }, [restorePurchase, closePaywall]);
98
+
99
+ const executeSavedPurchase = useCallback(async (): Promise<boolean> => {
100
+ const saved = getSavedPurchase();
101
+ if (!saved) {
102
+ if (__DEV__) {
103
+ console.log("[useAuthAwarePurchase] No saved purchase to execute");
104
+ }
105
+ return false;
106
+ }
107
+
108
+ if (__DEV__) {
109
+ console.log("[useAuthAwarePurchase] Executing saved purchase:", saved.pkg.product.identifier);
110
+ }
111
+
112
+ clearSavedPurchase();
113
+ return purchasePackage(saved.pkg);
114
+ }, [purchasePackage]);
115
+
116
+ return {
117
+ handlePurchase,
118
+ handleRestore,
119
+ executeSavedPurchase,
120
+ };
117
121
  };
@@ -18,7 +18,7 @@ import {
18
18
  syncPremiumStatus,
19
19
  notifyPurchaseCompleted,
20
20
  } from "../utils/PremiumStatusSyncer";
21
- import { usePendingPurchaseStore } from "../../../infrastructure/stores/PendingPurchaseStore";
21
+ import { getSavedPurchase, clearSavedPurchase } from "../../../presentation/hooks/useAuthAwarePurchase";
22
22
 
23
23
  export interface PurchaseHandlerDeps {
24
24
  config: RevenueCatConfig;
@@ -77,10 +77,9 @@ export async function handlePurchase(
77
77
  });
78
78
  }
79
79
 
80
- // Get purchase source from pending purchase store
81
- const pendingPurchaseStore = usePendingPurchaseStore.getState();
82
- const pending = pendingPurchaseStore.getPendingPurchase();
83
- const source = pending?.source;
80
+ // Get purchase source from saved purchase
81
+ const savedPurchase = getSavedPurchase();
82
+ const source = savedPurchase?.source;
84
83
 
85
84
  if (isConsumable) {
86
85
  if (__DEV__) {
@@ -94,7 +93,7 @@ export async function handlePurchase(
94
93
  source
95
94
  );
96
95
  // Clear pending purchase after successful purchase
97
- pendingPurchaseStore.clearPendingPurchase();
96
+ clearSavedPurchase();
98
97
  return {
99
98
  success: true,
100
99
  isPremium: false,
@@ -129,7 +128,7 @@ export async function handlePurchase(
129
128
  source
130
129
  );
131
130
  // Clear pending purchase after successful purchase
132
- pendingPurchaseStore.clearPendingPurchase();
131
+ clearSavedPurchase();
133
132
  return { success: true, isPremium: true, customerInfo };
134
133
  }
135
134
 
@@ -147,7 +146,7 @@ export async function handlePurchase(
147
146
  source
148
147
  );
149
148
  // Clear pending purchase after successful purchase
150
- pendingPurchaseStore.clearPendingPurchase();
149
+ clearSavedPurchase();
151
150
  return { success: true, isPremium: false, customerInfo };
152
151
  }
153
152
 
@@ -1,63 +0,0 @@
1
- /**
2
- * Pending Purchase Store
3
- * Manages pending purchase state for auth-required purchases
4
- */
5
-
6
- import { createStore } from "@umituz/react-native-design-system";
7
- import type { PurchasesPackage } from "react-native-purchases";
8
- import type { PurchaseSource } from "../../domain/entities/Credits";
9
-
10
- export interface PendingPurchaseData {
11
- package: PurchasesPackage;
12
- source: PurchaseSource;
13
- selectedAt: number;
14
- metadata?: Record<string, unknown>;
15
- }
16
-
17
- interface PendingPurchaseState {
18
- pending: PendingPurchaseData | null;
19
- isExecuting: boolean;
20
- }
21
-
22
- interface PendingPurchaseActions {
23
- setPendingPurchase: (data: PendingPurchaseData) => void;
24
- getPendingPurchase: () => PendingPurchaseData | null;
25
- clearPendingPurchase: () => void;
26
- hasPendingPurchase: () => boolean;
27
- setExecuting: (executing: boolean) => void;
28
- }
29
-
30
- const initialState: PendingPurchaseState = {
31
- pending: null,
32
- isExecuting: false,
33
- };
34
-
35
- export const usePendingPurchaseStore = createStore<
36
- PendingPurchaseState,
37
- PendingPurchaseActions
38
- >({
39
- name: "pending-purchase-store",
40
- initialState,
41
- persist: false,
42
- actions: (set, get) => ({
43
- setPendingPurchase: (data: PendingPurchaseData) => {
44
- set({ pending: data });
45
- },
46
-
47
- getPendingPurchase: () => {
48
- return get().pending;
49
- },
50
-
51
- clearPendingPurchase: () => {
52
- set({ pending: null });
53
- },
54
-
55
- hasPendingPurchase: () => {
56
- return get().pending !== null;
57
- },
58
-
59
- setExecuting: (executing: boolean) => {
60
- set({ isExecuting: executing });
61
- },
62
- }),
63
- });
@@ -1,79 +0,0 @@
1
- /**
2
- * Pending Purchase Handler Hook
3
- * Automatically executes pending purchase after successful authentication
4
- */
5
-
6
- import { useEffect, useRef } from "react";
7
- import { usePendingPurchaseStore } from "../../infrastructure/stores/PendingPurchaseStore";
8
- import { usePremium } from "./usePremium";
9
-
10
- declare const __DEV__: boolean;
11
-
12
- export interface UsePendingPurchaseHandlerParams {
13
- userId: string | undefined;
14
- isAuthenticated: boolean;
15
- }
16
-
17
- /**
18
- * Hook to handle pending purchases after authentication
19
- * Call this in app root after auth initialization
20
- */
21
- export const usePendingPurchaseHandler = ({
22
- userId,
23
- isAuthenticated,
24
- }: UsePendingPurchaseHandlerParams): void => {
25
- const { pending, clearPendingPurchase, setExecuting } = usePendingPurchaseStore();
26
- const { purchasePackage } = usePremium(userId);
27
- const isExecutingRef = useRef(false);
28
- const executedPackageIdRef = useRef<string | null>(null);
29
-
30
- useEffect(() => {
31
- if (!isAuthenticated || !userId || !pending) {
32
- return;
33
- }
34
-
35
- // Prevent duplicate executions
36
- if (isExecutingRef.current) {
37
- return;
38
- }
39
-
40
- // Prevent re-executing the same package
41
- if (executedPackageIdRef.current === pending.package.identifier) {
42
- return;
43
- }
44
-
45
- const executePendingPurchase = async () => {
46
- isExecutingRef.current = true;
47
- executedPackageIdRef.current = pending.package.identifier;
48
- setExecuting(true);
49
-
50
- if (__DEV__) {
51
- console.log(
52
- "[usePendingPurchaseHandler] Executing pending purchase:",
53
- {
54
- packageId: pending.package.identifier,
55
- source: pending.source,
56
- selectedAt: new Date(pending.selectedAt).toISOString(),
57
- }
58
- );
59
- }
60
-
61
- try {
62
- await purchasePackage(pending.package);
63
- } catch (error) {
64
- if (__DEV__) {
65
- console.error(
66
- "[usePendingPurchaseHandler] Failed to execute pending purchase:",
67
- error
68
- );
69
- }
70
- } finally {
71
- clearPendingPurchase();
72
- isExecutingRef.current = false;
73
- setExecuting(false);
74
- }
75
- };
76
-
77
- void executePendingPurchase();
78
- }, [isAuthenticated, userId, pending, clearPendingPurchase, purchasePackage, setExecuting]);
79
- };