@umituz/react-native-subscription 2.27.11 → 2.27.13
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.27.
|
|
3
|
+
"version": "2.27.13",
|
|
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",
|
package/src/index.ts
CHANGED
|
@@ -96,3 +96,18 @@ export * from "./utils";
|
|
|
96
96
|
|
|
97
97
|
// RevenueCat
|
|
98
98
|
export * from "./revenuecat";
|
|
99
|
+
|
|
100
|
+
// App Service Helpers (for configureAppServices)
|
|
101
|
+
export {
|
|
102
|
+
createCreditService,
|
|
103
|
+
createPaywallService,
|
|
104
|
+
type CreditServiceConfig,
|
|
105
|
+
type ICreditService,
|
|
106
|
+
type IPaywallService,
|
|
107
|
+
} from "./infrastructure/services/app-service-helpers";
|
|
108
|
+
|
|
109
|
+
// Init Module Factory
|
|
110
|
+
export {
|
|
111
|
+
createSubscriptionInitModule,
|
|
112
|
+
type SubscriptionInitModuleConfig,
|
|
113
|
+
} from './init';
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Service Helpers
|
|
3
|
+
* Creates ready-to-use service implementations for configureAppServices
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
getCreditsRepository,
|
|
8
|
+
getRevenueCatService,
|
|
9
|
+
getPremiumEntitlement,
|
|
10
|
+
} from "./SubscriptionInitializer";
|
|
11
|
+
import { creditsQueryKeys } from "../../presentation/hooks/useCredits";
|
|
12
|
+
import { paywallControl } from "../../domains/paywall/hooks/usePaywall";
|
|
13
|
+
import {
|
|
14
|
+
getGlobalQueryClient,
|
|
15
|
+
hasGlobalQueryClient,
|
|
16
|
+
} from "@umituz/react-native-design-system";
|
|
17
|
+
import {
|
|
18
|
+
useAuthStore,
|
|
19
|
+
selectUserId,
|
|
20
|
+
} from "@umituz/react-native-auth";
|
|
21
|
+
|
|
22
|
+
declare const __DEV__: boolean;
|
|
23
|
+
|
|
24
|
+
export interface CreditServiceConfig {
|
|
25
|
+
entitlementId: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ICreditService {
|
|
29
|
+
checkCredits: (cost: number) => Promise<boolean>;
|
|
30
|
+
deductCredits: (cost: number) => Promise<void>;
|
|
31
|
+
refundCredits: (amount: number, error?: unknown) => Promise<void>;
|
|
32
|
+
calculateCost: (capability: string, metadata?: Record<string, unknown>) => number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface IPaywallService {
|
|
36
|
+
showPaywall: (requiredCredits: number) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const checkPremiumStatus = async (entitlementId: string): Promise<boolean> => {
|
|
40
|
+
try {
|
|
41
|
+
const rcService = getRevenueCatService();
|
|
42
|
+
if (!rcService) return false;
|
|
43
|
+
|
|
44
|
+
const customerInfo = await rcService.getCustomerInfo();
|
|
45
|
+
if (!customerInfo) return false;
|
|
46
|
+
|
|
47
|
+
return !!getPremiumEntitlement(customerInfo, entitlementId);
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a credit service implementation
|
|
55
|
+
*/
|
|
56
|
+
export function createCreditService(config: CreditServiceConfig): ICreditService {
|
|
57
|
+
const { entitlementId } = config;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
checkCredits: async (cost: number): Promise<boolean> => {
|
|
61
|
+
const userId = selectUserId(useAuthStore.getState());
|
|
62
|
+
if (!userId) return false;
|
|
63
|
+
|
|
64
|
+
// Premium users bypass credit check
|
|
65
|
+
if (await checkPremiumStatus(entitlementId)) return true;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const repository = getCreditsRepository();
|
|
69
|
+
const result = await repository.getCredits(userId);
|
|
70
|
+
if (!result.success || !result.data) return false;
|
|
71
|
+
return (result.data.credits ?? 0) >= cost;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
deductCredits: async (cost: number): Promise<void> => {
|
|
78
|
+
const userId = selectUserId(useAuthStore.getState());
|
|
79
|
+
if (!userId) return;
|
|
80
|
+
|
|
81
|
+
// Premium users don't consume credits
|
|
82
|
+
if (await checkPremiumStatus(entitlementId)) return;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const repository = getCreditsRepository();
|
|
86
|
+
await repository.deductCredit(userId, cost);
|
|
87
|
+
|
|
88
|
+
if (hasGlobalQueryClient()) {
|
|
89
|
+
getGlobalQueryClient().invalidateQueries({
|
|
90
|
+
queryKey: creditsQueryKeys.user(userId),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
95
|
+
console.error("[CreditService] Deduct error:", error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
refundCredits: async (): Promise<void> => {
|
|
101
|
+
// No-op for now
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
calculateCost: (): number => 1,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Creates a paywall service implementation
|
|
110
|
+
*/
|
|
111
|
+
export function createPaywallService(): IPaywallService {
|
|
112
|
+
return {
|
|
113
|
+
showPaywall: () => paywallControl.open(),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription Init Module Factory
|
|
3
|
+
* Creates a ready-to-use InitModule for app initialization
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { initializeSubscription, type SubscriptionInitConfig } from '../infrastructure/services/SubscriptionInitializer';
|
|
7
|
+
|
|
8
|
+
declare const __DEV__: boolean;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* InitModule interface (from @umituz/react-native-design-system)
|
|
12
|
+
*/
|
|
13
|
+
export interface InitModule {
|
|
14
|
+
name: string;
|
|
15
|
+
init: () => Promise<boolean>;
|
|
16
|
+
critical?: boolean;
|
|
17
|
+
dependsOn?: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SubscriptionInitModuleConfig extends Omit<SubscriptionInitConfig, 'apiKey'> {
|
|
21
|
+
/**
|
|
22
|
+
* RevenueCat API key getter function
|
|
23
|
+
* Returns the API key or undefined if not available
|
|
24
|
+
*/
|
|
25
|
+
getApiKey: () => string | undefined;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Optional RevenueCat test store key getter
|
|
29
|
+
*/
|
|
30
|
+
getTestStoreKey?: () => string | undefined;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether this module is critical for app startup
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
critical?: boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Module dependencies
|
|
40
|
+
* @default ["auth"]
|
|
41
|
+
*/
|
|
42
|
+
dependsOn?: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates a Subscription initialization module for use with createAppInitializer
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* import { createAppInitializer } from "@umituz/react-native-design-system";
|
|
51
|
+
* import { createFirebaseInitModule } from "@umituz/react-native-firebase";
|
|
52
|
+
* import { createAuthInitModule } from "@umituz/react-native-auth";
|
|
53
|
+
* import { createSubscriptionInitModule } from "@umituz/react-native-subscription";
|
|
54
|
+
*
|
|
55
|
+
* export const initializeApp = createAppInitializer({
|
|
56
|
+
* modules: [
|
|
57
|
+
* createFirebaseInitModule(),
|
|
58
|
+
* createAuthInitModule({ userCollection: "users" }),
|
|
59
|
+
* createSubscriptionInitModule({
|
|
60
|
+
* getApiKey: () => getRevenueCatApiKey(),
|
|
61
|
+
* entitlementId: "premium",
|
|
62
|
+
* credits: {
|
|
63
|
+
* collectionName: "credits",
|
|
64
|
+
* creditLimit: 500,
|
|
65
|
+
* enableFreeCredits: true,
|
|
66
|
+
* freeCredits: 1,
|
|
67
|
+
* },
|
|
68
|
+
* }),
|
|
69
|
+
* ],
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export function createSubscriptionInitModule(
|
|
74
|
+
config: SubscriptionInitModuleConfig
|
|
75
|
+
): InitModule {
|
|
76
|
+
const {
|
|
77
|
+
getApiKey,
|
|
78
|
+
getTestStoreKey,
|
|
79
|
+
critical = true,
|
|
80
|
+
dependsOn = ['auth'],
|
|
81
|
+
...subscriptionConfig
|
|
82
|
+
} = config;
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
name: 'subscription',
|
|
86
|
+
critical,
|
|
87
|
+
dependsOn,
|
|
88
|
+
init: async () => {
|
|
89
|
+
try {
|
|
90
|
+
const apiKey = getApiKey();
|
|
91
|
+
|
|
92
|
+
if (!apiKey) {
|
|
93
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
94
|
+
console.log('[createSubscriptionInitModule] No API key - skipping');
|
|
95
|
+
}
|
|
96
|
+
return true; // Not an error, just skip
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const testStoreKey = getTestStoreKey?.();
|
|
100
|
+
|
|
101
|
+
await initializeSubscription({
|
|
102
|
+
apiKey,
|
|
103
|
+
testStoreKey,
|
|
104
|
+
...subscriptionConfig,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
108
|
+
console.log('[createSubscriptionInitModule] Subscription initialized');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return true;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
114
|
+
console.error('[createSubscriptionInitModule] Error:', error);
|
|
115
|
+
}
|
|
116
|
+
// Continue on error - subscription is not critical for app launch
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|