react-native-purchases-ui 8.11.10 → 9.0.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/src/index.tsx CHANGED
@@ -19,12 +19,13 @@ import {
19
19
  import React, { type ReactNode, useEffect, useState } from "react";
20
20
  import { shouldUsePreviewAPIMode } from "./utils/environment";
21
21
  import { previewNativeModuleRNCustomerCenter, previewNativeModuleRNPaywalls } from "./preview/nativeModules";
22
+ import { PreviewPaywall } from "./preview/previewComponents";
22
23
 
23
24
  export { PAYWALL_RESULT } from "@revenuecat/purchases-typescript-internal";
24
25
 
25
26
  const LINKING_ERROR =
26
27
  `The package 'react-native-purchases-ui' doesn't seem to be linked. Make sure: \n\n` +
27
- Platform.select({ios: "- You have run 'pod install'\n", default: ''}) +
28
+ '- You have run \'pod install\'\n' +
28
29
  '- You rebuilt the app after installing the package\n';
29
30
 
30
31
 
@@ -42,21 +43,118 @@ if (!RNCustomerCenter) {
42
43
  throw new Error(LINKING_ERROR);
43
44
  }
44
45
 
45
- const eventEmitter = new NativeEventEmitter(RNPaywalls);
46
- const customerCenterEventEmitter = new NativeEventEmitter(RNCustomerCenter);
46
+ const NativePaywall = !usingPreviewAPIMode && UIManager.getViewManagerConfig('Paywall') != null
47
+ ? requireNativeComponent<FullScreenPaywallViewProps>('Paywall')
48
+ : null;
47
49
 
48
- const InternalPaywall =
49
- UIManager.getViewManagerConfig('Paywall') != null
50
- ? requireNativeComponent<FullScreenPaywallViewProps>('Paywall')
51
- : () => {
52
- throw new Error(LINKING_ERROR);
53
- };
54
-
55
- const InternalPaywallFooterView = UIManager.getViewManagerConfig('Paywall') != null
50
+ const NativePaywallFooter = !usingPreviewAPIMode && UIManager.getViewManagerConfig('Paywall') != null
56
51
  ? requireNativeComponent<InternalFooterPaywallViewProps>('RCPaywallFooterView')
57
- : () => {
58
- throw new Error(LINKING_ERROR);
59
- };
52
+ : null;
53
+
54
+ const eventEmitter = usingPreviewAPIMode ? null : new NativeEventEmitter(RNPaywalls);
55
+ const customerCenterEventEmitter = usingPreviewAPIMode ? null : new NativeEventEmitter(RNCustomerCenter);
56
+
57
+ const InternalPaywall: React.FC<FullScreenPaywallViewProps> = ({
58
+ style,
59
+ children,
60
+ options,
61
+ onPurchaseStarted,
62
+ onPurchaseCompleted,
63
+ onPurchaseError,
64
+ onPurchaseCancelled,
65
+ onRestoreStarted,
66
+ onRestoreCompleted,
67
+ onRestoreError,
68
+ onDismiss,
69
+ }) => {
70
+ if (usingPreviewAPIMode) {
71
+ return (
72
+ <PreviewPaywall
73
+ offering={options?.offering}
74
+ displayCloseButton={options?.displayCloseButton}
75
+ fontFamily={options?.fontFamily}
76
+ onPurchaseStarted={onPurchaseStarted}
77
+ onPurchaseCompleted={onPurchaseCompleted}
78
+ onPurchaseError={onPurchaseError}
79
+ onPurchaseCancelled={onPurchaseCancelled}
80
+ onRestoreStarted={onRestoreStarted}
81
+ onRestoreCompleted={onRestoreCompleted}
82
+ onRestoreError={onRestoreError}
83
+ onDismiss={onDismiss}
84
+ />
85
+ );
86
+ } else if (!!NativePaywall) {
87
+ return (
88
+ <NativePaywall
89
+ style={style}
90
+ children={children}
91
+ options={options}
92
+ onPurchaseStarted={(event: any) => onPurchaseStarted && onPurchaseStarted(event.nativeEvent)}
93
+ onPurchaseCompleted={(event: any) => onPurchaseCompleted && onPurchaseCompleted(event.nativeEvent)}
94
+ onPurchaseError={(event: any) => onPurchaseError && onPurchaseError(event.nativeEvent)}
95
+ onPurchaseCancelled={() => onPurchaseCancelled && onPurchaseCancelled()}
96
+ onRestoreStarted={() => onRestoreStarted && onRestoreStarted()}
97
+ onRestoreCompleted={(event: any) => onRestoreCompleted && onRestoreCompleted(event.nativeEvent)}
98
+ onRestoreError={(event: any) => onRestoreError && onRestoreError(event.nativeEvent)}
99
+ onDismiss={() => onDismiss && onDismiss()}
100
+ />
101
+ );
102
+ }
103
+
104
+ throw new Error(LINKING_ERROR);
105
+ };
106
+
107
+ const InternalPaywallFooterView: React.FC<InternalFooterPaywallViewProps> = ({
108
+ style,
109
+ children,
110
+ options,
111
+ onPurchaseStarted,
112
+ onPurchaseCompleted,
113
+ onPurchaseError,
114
+ onPurchaseCancelled,
115
+ onRestoreStarted,
116
+ onRestoreCompleted,
117
+ onRestoreError,
118
+ onDismiss,
119
+ onMeasure,
120
+ }) => {
121
+ if (usingPreviewAPIMode) {
122
+ return (
123
+ <PreviewPaywall
124
+ offering={options?.offering}
125
+ displayCloseButton={true}
126
+ fontFamily={options?.fontFamily}
127
+ onPurchaseStarted={onPurchaseStarted}
128
+ onPurchaseCompleted={onPurchaseCompleted}
129
+ onPurchaseError={onPurchaseError}
130
+ onPurchaseCancelled={onPurchaseCancelled}
131
+ onRestoreStarted={onRestoreStarted}
132
+ onRestoreCompleted={onRestoreCompleted}
133
+ onRestoreError={onRestoreError}
134
+ onDismiss={onDismiss}
135
+ />
136
+ );
137
+ } else if (!!NativePaywallFooter) {
138
+ return (
139
+ <NativePaywallFooter
140
+ style={style}
141
+ children={children}
142
+ options={options}
143
+ onPurchaseStarted={(event: any) => onPurchaseStarted && onPurchaseStarted(event.nativeEvent)}
144
+ onPurchaseCompleted={(event: any) => onPurchaseCompleted && onPurchaseCompleted(event.nativeEvent)}
145
+ onPurchaseError={(event: any) => onPurchaseError && onPurchaseError(event.nativeEvent)}
146
+ onPurchaseCancelled={() => onPurchaseCancelled && onPurchaseCancelled()}
147
+ onRestoreStarted={() => onRestoreStarted && onRestoreStarted()}
148
+ onRestoreCompleted={(event: any) => onRestoreCompleted && onRestoreCompleted(event.nativeEvent)}
149
+ onRestoreError={(event: any) => onRestoreError && onRestoreError(event.nativeEvent)}
150
+ onDismiss={() => onDismiss && onDismiss()}
151
+ onMeasure={onMeasure}
152
+ />
153
+ );
154
+ }
155
+
156
+ throw new Error(LINKING_ERROR);
157
+ };
60
158
 
61
159
  export interface PresentPaywallParams {
62
160
  /**
@@ -302,19 +400,23 @@ export default class RevenueCatUI {
302
400
  onRestoreCompleted,
303
401
  onRestoreError,
304
402
  onDismiss,
305
- }) => (
306
- <InternalPaywall options={options}
307
- children={children}
308
- onPurchaseStarted={(event: any) => onPurchaseStarted && onPurchaseStarted(event.nativeEvent)}
309
- onPurchaseCompleted={(event: any) => onPurchaseCompleted && onPurchaseCompleted(event.nativeEvent)}
310
- onPurchaseError={(event: any) => onPurchaseError && onPurchaseError(event.nativeEvent)}
311
- onPurchaseCancelled={() => onPurchaseCancelled && onPurchaseCancelled()}
312
- onRestoreStarted={() => onRestoreStarted && onRestoreStarted()}
313
- onRestoreCompleted={(event: any) => onRestoreCompleted && onRestoreCompleted(event.nativeEvent)}
314
- onRestoreError={(event: any) => onRestoreError && onRestoreError(event.nativeEvent)}
315
- onDismiss={() => onDismiss && onDismiss()}
316
- style={[{flex: 1}, style]}/>
317
- );
403
+ }) => {
404
+ return (
405
+ <InternalPaywall
406
+ options={options}
407
+ children={children}
408
+ onPurchaseStarted={onPurchaseStarted}
409
+ onPurchaseCompleted={onPurchaseCompleted}
410
+ onPurchaseError={onPurchaseError}
411
+ onPurchaseCancelled={onPurchaseCancelled}
412
+ onRestoreStarted={onRestoreStarted}
413
+ onRestoreCompleted={onRestoreCompleted}
414
+ onRestoreError={onRestoreError}
415
+ onDismiss={onDismiss}
416
+ style={[{flex: 1}, style]}
417
+ />
418
+ );
419
+ };
318
420
 
319
421
  public static OriginalTemplatePaywallFooterContainerView: React.FC<FooterPaywallViewProps> = ({
320
422
  style,
@@ -345,13 +447,13 @@ export default class RevenueCatUI {
345
447
  setPaddingBottom(20 + bottom);
346
448
  };
347
449
 
348
- const subscription = eventEmitter.addListener(
450
+ const subscription = eventEmitter?.addListener(
349
451
  'safeAreaInsetsDidChange',
350
452
  handleSafeAreaInsetsChange
351
453
  );
352
454
 
353
455
  return () => {
354
- subscription.remove();
456
+ subscription?.remove();
355
457
  };
356
458
  }, []);
357
459
 
@@ -367,14 +469,14 @@ export default class RevenueCatUI {
367
469
  android: {marginTop: -20, height}
368
470
  })}
369
471
  options={options}
370
- onPurchaseStarted={(event: any) => onPurchaseStarted && onPurchaseStarted(event.nativeEvent)}
371
- onPurchaseCompleted={(event: any) => onPurchaseCompleted && onPurchaseCompleted(event.nativeEvent)}
372
- onPurchaseError={(event: any) => onPurchaseError && onPurchaseError(event.nativeEvent)}
373
- onPurchaseCancelled={() => onPurchaseCancelled && onPurchaseCancelled()}
374
- onRestoreStarted={() => onRestoreStarted && onRestoreStarted()}
375
- onRestoreCompleted={(event: any) => onRestoreCompleted && onRestoreCompleted(event.nativeEvent)}
376
- onRestoreError={(event: any) => onRestoreError && onRestoreError(event.nativeEvent)}
377
- onDismiss={() => onDismiss && onDismiss()}
472
+ onPurchaseStarted={onPurchaseStarted}
473
+ onPurchaseCompleted={onPurchaseCompleted}
474
+ onPurchaseError={onPurchaseError}
475
+ onPurchaseCancelled={onPurchaseCancelled}
476
+ onRestoreStarted={onRestoreStarted}
477
+ onRestoreCompleted={onRestoreCompleted}
478
+ onRestoreError={onRestoreError}
479
+ onDismiss={onDismiss}
378
480
  onMeasure={(event: any) => setHeight(event.nativeEvent.measurements.height)}
379
481
  />
380
482
  </View>
@@ -393,73 +495,89 @@ export default class RevenueCatUI {
393
495
  const callbacks = params.callbacks as CustomerCenterCallbacks;
394
496
 
395
497
  if (callbacks.onFeedbackSurveyCompleted) {
396
- const subscription = customerCenterEventEmitter.addListener(
498
+ const subscription = customerCenterEventEmitter?.addListener(
397
499
  'onFeedbackSurveyCompleted',
398
500
  (event: { feedbackSurveyOptionId: string }) => callbacks.onFeedbackSurveyCompleted &&
399
501
  callbacks.onFeedbackSurveyCompleted(event)
400
502
  );
401
- subscriptions.push(subscription);
503
+ if (subscription) {
504
+ subscriptions.push(subscription);
505
+ }
402
506
  }
403
507
 
404
508
  if (callbacks.onShowingManageSubscriptions) {
405
- const subscription = customerCenterEventEmitter.addListener(
509
+ const subscription = customerCenterEventEmitter?.addListener(
406
510
  'onShowingManageSubscriptions',
407
511
  () => callbacks.onShowingManageSubscriptions && callbacks.onShowingManageSubscriptions()
408
512
  );
409
- subscriptions.push(subscription);
513
+ if (subscription) {
514
+ subscriptions.push(subscription);
515
+ }
410
516
  }
411
517
 
412
518
  if (callbacks.onRestoreCompleted) {
413
- const subscription = customerCenterEventEmitter.addListener(
519
+ const subscription = customerCenterEventEmitter?.addListener(
414
520
  'onRestoreCompleted',
415
521
  (event: { customerInfo: CustomerInfo }) => callbacks.onRestoreCompleted &&
416
522
  callbacks.onRestoreCompleted(event)
417
523
  );
418
- subscriptions.push(subscription);
524
+ if (subscription) {
525
+ subscriptions.push(subscription);
526
+ }
419
527
  }
420
528
 
421
529
  if (callbacks.onRestoreFailed) {
422
- const subscription = customerCenterEventEmitter.addListener(
530
+ const subscription = customerCenterEventEmitter?.addListener(
423
531
  'onRestoreFailed',
424
532
  (event: { error: PurchasesError }) => callbacks.onRestoreFailed &&
425
533
  callbacks.onRestoreFailed(event)
426
534
  );
427
- subscriptions.push(subscription);
535
+ if (subscription) {
536
+ subscriptions.push(subscription);
537
+ }
428
538
  }
429
539
 
430
540
  if (callbacks.onRestoreStarted) {
431
- const subscription = customerCenterEventEmitter.addListener(
541
+ const subscription = customerCenterEventEmitter?.addListener(
432
542
  'onRestoreStarted',
433
543
  () => callbacks.onRestoreStarted && callbacks.onRestoreStarted()
434
544
  );
435
- subscriptions.push(subscription);
545
+ if (subscription) {
546
+ subscriptions.push(subscription);
547
+ }
436
548
  }
437
549
 
438
550
  if (callbacks.onRefundRequestStarted) {
439
- const subscription = customerCenterEventEmitter.addListener(
551
+ const subscription = customerCenterEventEmitter?.addListener(
440
552
  'onRefundRequestStarted',
441
553
  (event: { productIdentifier: string }) => callbacks.onRefundRequestStarted &&
442
554
  callbacks.onRefundRequestStarted(event)
443
555
  );
444
- subscriptions.push(subscription);
556
+ if (subscription) {
557
+ subscriptions.push(subscription);
558
+ }
445
559
  }
446
560
 
447
561
  if (callbacks.onRefundRequestCompleted) {
448
- const subscription = customerCenterEventEmitter.addListener(
562
+ const subscription = customerCenterEventEmitter?.addListener(
449
563
  'onRefundRequestCompleted',
450
564
  (event: { productIdentifier: string; refundRequestStatus: REFUND_REQUEST_STATUS }) => callbacks.onRefundRequestCompleted &&
451
565
  callbacks.onRefundRequestCompleted(event)
452
566
  );
453
- subscriptions.push(subscription);
567
+ if (subscription) {
568
+ subscriptions.push(subscription);
569
+ }
454
570
  }
455
571
 
456
572
  if (callbacks.onManagementOptionSelected) {
457
- const subscription = customerCenterEventEmitter.addListener(
573
+ const subscription = customerCenterEventEmitter?.addListener(
458
574
  'onManagementOptionSelected',
459
575
  (event: CustomerCenterManagementOptionEvent) => callbacks.onManagementOptionSelected &&
460
576
  callbacks.onManagementOptionSelected(event)
461
577
  );
462
- subscriptions.push(subscription);
578
+ if (subscription) {
579
+ subscriptions.push(subscription);
580
+ }
463
581
  }
464
582
 
465
583
  // Return a promise that resolves when the customer center is dismissed
@@ -0,0 +1,171 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
3
+ import {
4
+ type CustomerInfo,
5
+ type PurchasesError,
6
+ type PurchasesOffering,
7
+ type PurchasesPackage,
8
+ type PurchasesStoreTransaction,
9
+ } from "@revenuecat/purchases-typescript-internal";
10
+
11
+ export interface PreviewPaywallProps {
12
+ offering?: PurchasesOffering | null;
13
+ displayCloseButton?: boolean;
14
+ fontFamily?: string | null;
15
+ onPurchaseStarted?: ({packageBeingPurchased}: { packageBeingPurchased: PurchasesPackage }) => void;
16
+ onPurchaseCompleted?: ({
17
+ customerInfo,
18
+ storeTransaction
19
+ }: { customerInfo: CustomerInfo, storeTransaction: PurchasesStoreTransaction }) => void;
20
+ onPurchaseError?: ({error}: { error: PurchasesError }) => void;
21
+ onPurchaseCancelled?: () => void;
22
+ onRestoreStarted?: () => void;
23
+ onRestoreCompleted?: ({customerInfo}: { customerInfo: CustomerInfo }) => void;
24
+ onRestoreError?: ({error}: { error: PurchasesError }) => void;
25
+ onDismiss?: () => void;
26
+ }
27
+
28
+ export const PreviewPaywall: React.FC<PreviewPaywallProps> = ({
29
+ displayCloseButton = true,
30
+ fontFamily,
31
+ onDismiss,
32
+ }) => {
33
+ const handleClose = () => {
34
+ onDismiss?.();
35
+ };
36
+
37
+ const textStyle = fontFamily ? { fontFamily } : undefined;
38
+
39
+ return (
40
+ <View style={styles.container}>
41
+ <View style={styles.header}>
42
+ <Text style={[styles.title, textStyle]}>
43
+ Preview Paywall
44
+ </Text>
45
+ {displayCloseButton && (
46
+ <TouchableOpacity onPress={handleClose} style={styles.closeButton}>
47
+ <Text style={styles.closeButtonText}>✕</Text>
48
+ </TouchableOpacity>
49
+ )}
50
+ </View>
51
+
52
+ <View style={styles.content}>
53
+ <Text style={[styles.notSupportedMessage, textStyle]}>
54
+ Web paywalls are not supported yet.
55
+ </Text>
56
+ <Text style={[styles.fakeMessage, textStyle]}>
57
+ This is a fake preview implementation.
58
+ </Text>
59
+ <Text style={[styles.previewMode, textStyle]}>
60
+ Currently in preview mode
61
+ </Text>
62
+
63
+ <TouchableOpacity style={styles.closePaywallButton} onPress={handleClose}>
64
+ <Text style={[styles.closePaywallButtonText, textStyle]}>
65
+ Close Paywall
66
+ </Text>
67
+ </TouchableOpacity>
68
+ </View>
69
+ </View>
70
+ );
71
+ };
72
+
73
+ const styles = StyleSheet.create({
74
+ container: {
75
+ flex: 1,
76
+ backgroundColor: '#ffffff',
77
+ borderRadius: 12,
78
+ overflow: 'hidden',
79
+ maxWidth: 400,
80
+ maxHeight: 600,
81
+ },
82
+ header: {
83
+ flexDirection: 'row',
84
+ justifyContent: 'space-between',
85
+ alignItems: 'center',
86
+ padding: 20,
87
+ borderBottomWidth: 1,
88
+ borderBottomColor: '#e0e0e0',
89
+ },
90
+ title: {
91
+ fontSize: 24,
92
+ fontWeight: 'bold',
93
+ color: '#333333',
94
+ },
95
+ closeButton: {
96
+ width: 30,
97
+ height: 30,
98
+ borderRadius: 15,
99
+ backgroundColor: '#f0f0f0',
100
+ justifyContent: 'center',
101
+ alignItems: 'center',
102
+ },
103
+ closeButtonText: {
104
+ fontSize: 16,
105
+ color: '#666666',
106
+ },
107
+ content: {
108
+ flex: 1,
109
+ padding: 20,
110
+ justifyContent: 'center',
111
+ alignItems: 'center',
112
+ },
113
+ notSupportedMessage: {
114
+ fontSize: 18,
115
+ fontWeight: 'bold',
116
+ color: '#333333',
117
+ textAlign: 'center',
118
+ marginBottom: 16,
119
+ },
120
+ fakeMessage: {
121
+ fontSize: 16,
122
+ color: '#666666',
123
+ textAlign: 'center',
124
+ marginBottom: 8,
125
+ },
126
+ previewMode: {
127
+ fontSize: 14,
128
+ color: '#999999',
129
+ textAlign: 'center',
130
+ marginBottom: 32,
131
+ },
132
+ closePaywallButton: {
133
+ backgroundColor: '#007AFF',
134
+ padding: 16,
135
+ borderRadius: 8,
136
+ minWidth: 120,
137
+ alignItems: 'center',
138
+ },
139
+ closePaywallButtonText: {
140
+ fontSize: 16,
141
+ fontWeight: 'bold',
142
+ color: '#ffffff',
143
+ },
144
+ description: {
145
+ fontSize: 16,
146
+ color: '#666666',
147
+ marginBottom: 20,
148
+ textAlign: 'center',
149
+ },
150
+ optionButton: {
151
+ backgroundColor: '#f8f9fa',
152
+ padding: 16,
153
+ borderRadius: 8,
154
+ marginBottom: 12,
155
+ alignItems: 'center',
156
+ },
157
+ optionButtonText: {
158
+ fontSize: 16,
159
+ color: '#333333',
160
+ },
161
+ footer: {
162
+ padding: 20,
163
+ borderTopWidth: 1,
164
+ borderTopColor: '#e0e0e0',
165
+ },
166
+ disclaimer: {
167
+ fontSize: 12,
168
+ color: '#999999',
169
+ textAlign: 'center',
170
+ },
171
+ });
@@ -1,18 +1,22 @@
1
- import { NativeModules } from "react-native";
1
+ import { NativeModules, Platform } from "react-native";
2
2
 
3
3
  /**
4
4
  * Detects if the app is running in an environment where native modules are not available
5
- * (like Expo Go) or if the required native modules are missing.
5
+ * (like Expo Go or Web) or if the required native modules are missing.
6
6
  *
7
7
  * @returns {boolean} True if the app is running in an environment where native modules are not available
8
- * (like Expo Go) or if the required native modules are missing.
8
+ * (like Expo Go or Web) or if the required native modules are missing.
9
9
  */
10
10
  export function shouldUsePreviewAPIMode(): boolean {
11
- let usePreviewAPIMode = isExpoGo();
12
- if (usePreviewAPIMode) {
11
+ if (isExpoGo()) {
13
12
  console.log('Expo Go app detected. Using RevenueCat in Preview API Mode.');
13
+ return true;
14
+ } else if (isWebPlatform()) {
15
+ console.log('Web platform detected. Using RevenueCat in Preview API Mode.');
16
+ return true;
17
+ } else {
18
+ return false;
14
19
  }
15
- return usePreviewAPIMode;
16
20
  }
17
21
 
18
22
  declare global {
@@ -32,4 +36,11 @@ function isExpoGo(): boolean {
32
36
  }
33
37
 
34
38
  return !!globalThis.expo?.modules?.ExpoGo;
39
+ }
40
+
41
+ /**
42
+ * Detects if the app is running on web platform
43
+ */
44
+ function isWebPlatform(): boolean {
45
+ return Platform.OS === 'web';
35
46
  }