react-native-purchases-ui 9.10.5 → 9.11.1

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
@@ -32,6 +32,56 @@ export { CustomVariableValue, type CustomVariables } from "./customVariables";
32
32
  // Re-export for testing purposes (marked as @internal)
33
33
  export { convertCustomVariablesToStringMap, transformOptionsForNative } from "./customVariables";
34
34
 
35
+ /**
36
+ * The result of a purchase or restore operation performed by custom app-based logic.
37
+ * Used when `purchasesAreCompletedBy` is set to `MY_APP`.
38
+ * @readonly
39
+ * @enum {string}
40
+ */
41
+ export enum PURCHASE_LOGIC_RESULT {
42
+ /** The purchase or restore was successful. */
43
+ SUCCESS = "SUCCESS",
44
+ /** The purchase was cancelled by the user. */
45
+ CANCELLATION = "CANCELLATION",
46
+ /** An error occurred during the purchase or restore. */
47
+ ERROR = "ERROR",
48
+ }
49
+
50
+ /**
51
+ * The result of a purchase or restore operation performed by custom purchase logic.
52
+ * Uses a discriminated union to allow structured error information.
53
+ */
54
+ export type PurchaseLogicResult =
55
+ | { result: PURCHASE_LOGIC_RESULT.SUCCESS }
56
+ | { result: PURCHASE_LOGIC_RESULT.CANCELLATION }
57
+ | { result: PURCHASE_LOGIC_RESULT.ERROR; error?: PurchasesError };
58
+
59
+ /**
60
+ * Interface for handling purchases and restores within paywalls when
61
+ * `purchasesAreCompletedBy` is set to `MY_APP`.
62
+ *
63
+ * When provided, the paywall will call these functions instead of using
64
+ * RevenueCat's default purchase/restore behavior.
65
+ */
66
+ export interface PurchaseLogic {
67
+ /**
68
+ * Called when the paywall wants to perform a purchase.
69
+ * Implement this to execute your custom purchase logic.
70
+ *
71
+ * @param args.packageToPurchase - The package the user wants to purchase.
72
+ * @returns A promise resolving to a PurchaseLogicResult indicating the outcome.
73
+ */
74
+ performPurchase: (args: { packageToPurchase: PurchasesPackage }) => Promise<PurchaseLogicResult>;
75
+
76
+ /**
77
+ * Called when the paywall wants to perform a restore.
78
+ * Implement this to execute your custom restore logic.
79
+ *
80
+ * @returns A promise resolving to a PurchaseLogicResult indicating the outcome.
81
+ */
82
+ performRestore: () => Promise<PurchaseLogicResult>;
83
+ }
84
+
35
85
  const NATIVE_MODULE_NOT_FOUND_ERROR =
36
86
  `[RevenueCatUI] Native module not found. This can happen if:\n\n` +
37
87
  `- You are running in an unsupported environment (e.g., A browser or a container app that doesn't actually use the native modules)\n` +
@@ -56,22 +106,71 @@ function throwIfNativeModulesNotAvailable(): void {
56
106
  }
57
107
  }
58
108
 
109
+ // Internal native props include purchase logic bridge events and native custom variable transforms
110
+ type NativeFullScreenPaywallViewProps = Omit<FullScreenPaywallViewProps, 'options'> & {
111
+ options?: WithNativeCustomVariables<FullScreenPaywallViewOptions>;
112
+ onPerformPurchase?: (event: any) => void;
113
+ onPerformRestore?: (event: any) => void;
114
+ };
115
+
116
+ type NativeFooterPaywallViewProps = Omit<InternalFooterPaywallViewProps, 'options'> & {
117
+ options?: WithNativeCustomVariables<FooterPaywallViewOptions>;
118
+ };
119
+
59
120
  const NativePaywall = !usingPreviewAPIMode && UIManager.getViewManagerConfig('Paywall') != null
60
121
  ? requireNativeComponent<NativeFullScreenPaywallViewProps>('Paywall')
61
122
  : null;
62
123
 
63
124
  const NativePaywallFooter = !usingPreviewAPIMode && UIManager.getViewManagerConfig('Paywall') != null
64
- ? requireNativeComponent<NativeInternalFooterPaywallViewProps>('RCPaywallFooterView')
125
+ ? requireNativeComponent<NativeFooterPaywallViewProps>('RCPaywallFooterView')
65
126
  : null;
66
127
 
67
128
  // Only create event emitters if native modules are available
68
129
  const eventEmitter = !usingPreviewAPIMode && RNPaywalls ? new NativeEventEmitter(RNPaywalls) : null;
69
130
  const customerCenterEventEmitter = !usingPreviewAPIMode && RNCustomerCenter ? new NativeEventEmitter(RNCustomerCenter) : null;
70
131
 
132
+ function resolveLogicResult(requestId: string, logicResult: PurchaseLogicResult) {
133
+ const errorMessage = logicResult.result === PURCHASE_LOGIC_RESULT.ERROR && logicResult.error
134
+ ? logicResult.error.message
135
+ : null;
136
+ RNPaywalls?.resolvePurchaseLogicResult(requestId, logicResult.result, errorMessage);
137
+ }
138
+
139
+ function createPurchaseLogicHandlers(purchaseLogic?: PurchaseLogic) {
140
+ if (!purchaseLogic) {
141
+ return { nativeOptions: {}, handlePerformPurchase: undefined, handlePerformRestore: undefined };
142
+ }
143
+
144
+ const nativeOptions = { hasPurchaseLogic: true };
145
+
146
+ const handlePerformPurchase = async (event: any) => {
147
+ const { requestId, packageBeingPurchased } = event.nativeEvent;
148
+ try {
149
+ const logicResult = await purchaseLogic.performPurchase({ packageToPurchase: packageBeingPurchased });
150
+ resolveLogicResult(requestId, logicResult);
151
+ } catch (e) {
152
+ RNPaywalls?.resolvePurchaseLogicResult(requestId, PURCHASE_LOGIC_RESULT.ERROR, e instanceof Error ? e.message : null);
153
+ }
154
+ };
155
+
156
+ const handlePerformRestore = async (event: any) => {
157
+ const { requestId } = event.nativeEvent;
158
+ try {
159
+ const logicResult = await purchaseLogic.performRestore();
160
+ resolveLogicResult(requestId, logicResult);
161
+ } catch (e) {
162
+ RNPaywalls?.resolvePurchaseLogicResult(requestId, PURCHASE_LOGIC_RESULT.ERROR, e instanceof Error ? e.message : null);
163
+ }
164
+ };
165
+
166
+ return { nativeOptions, handlePerformPurchase, handlePerformRestore };
167
+ }
168
+
71
169
  const InternalPaywall: React.FC<FullScreenPaywallViewProps> = ({
72
170
  style,
73
171
  children,
74
172
  options,
173
+ purchaseLogic,
75
174
  onPurchaseStarted,
76
175
  onPurchaseCompleted,
77
176
  onPurchaseError,
@@ -82,6 +181,8 @@ const InternalPaywall: React.FC<FullScreenPaywallViewProps> = ({
82
181
  onDismiss,
83
182
  onPurchasePackageInitiated,
84
183
  }) => {
184
+ const { nativeOptions, handlePerformPurchase, handlePerformRestore } = createPurchaseLogicHandlers(purchaseLogic);
185
+
85
186
  if (usingPreviewAPIMode) {
86
187
  return (
87
188
  <PreviewPaywall
@@ -100,12 +201,12 @@ const InternalPaywall: React.FC<FullScreenPaywallViewProps> = ({
100
201
  );
101
202
  } else if (!!NativePaywall) {
102
203
  // Transform options to native format (CustomVariables -> string map)
103
- const nativeOptions = transformOptionsForNative(options);
204
+ const transformedOptions = transformOptionsForNative(options);
104
205
  return (
105
206
  <NativePaywall
106
207
  style={style}
107
208
  children={children}
108
- options={nativeOptions}
209
+ options={{ ...transformedOptions, ...nativeOptions }}
109
210
  onPurchaseStarted={(event: any) => onPurchaseStarted && onPurchaseStarted(event.nativeEvent)}
110
211
  onPurchaseCompleted={(event: any) => onPurchaseCompleted && onPurchaseCompleted(event.nativeEvent)}
111
212
  onPurchaseError={(event: any) => onPurchaseError && onPurchaseError(event.nativeEvent)}
@@ -125,6 +226,8 @@ const InternalPaywall: React.FC<FullScreenPaywallViewProps> = ({
125
226
  RNPaywalls!.resumePurchasePackageInitiated(requestId, true);
126
227
  }
127
228
  }}
229
+ onPerformPurchase={handlePerformPurchase}
230
+ onPerformRestore={handlePerformRestore}
128
231
  />
129
232
  );
130
233
  }
@@ -164,12 +267,12 @@ const InternalPaywallFooterView: React.FC<InternalFooterPaywallViewProps> = ({
164
267
  );
165
268
  } else if (!!NativePaywallFooter) {
166
269
  // Transform options to native format (CustomVariables -> string map)
167
- const nativeOptions = transformOptionsForNative(options);
270
+ const transformedOptions = transformOptionsForNative(options);
168
271
  return (
169
272
  <NativePaywallFooter
170
273
  style={style}
171
274
  children={children}
172
- options={nativeOptions}
275
+ options={transformedOptions}
173
276
  onPurchaseStarted={(event: any) => onPurchaseStarted && onPurchaseStarted(event.nativeEvent)}
174
277
  onPurchaseCompleted={(event: any) => onPurchaseCompleted && onPurchaseCompleted(event.nativeEvent)}
175
278
  onPurchaseError={(event: any) => onPurchaseError && onPurchaseError(event.nativeEvent)}
@@ -288,6 +391,7 @@ type FullScreenPaywallViewProps = {
288
391
  style?: StyleProp<ViewStyle>;
289
392
  children?: ReactNode;
290
393
  options?: FullScreenPaywallViewOptions;
394
+ purchaseLogic?: PurchaseLogic;
291
395
  onPurchaseStarted?: ({packageBeingPurchased}: { packageBeingPurchased: PurchasesPackage }) => void;
292
396
  onPurchaseCompleted?: ({
293
397
  customerInfo,
@@ -333,21 +437,6 @@ type InternalFooterPaywallViewProps = FooterPaywallViewProps & {
333
437
  type WithNativeCustomVariables<T extends { customVariables?: CustomVariables }> =
334
438
  Omit<T, 'customVariables'> & { customVariables?: NativeCustomVariables | null };
335
439
 
336
- /**
337
- * Native props for FullScreenPaywall component.
338
- * @internal
339
- */
340
- type NativeFullScreenPaywallViewProps = Omit<FullScreenPaywallViewProps, 'options'> & {
341
- options?: WithNativeCustomVariables<FullScreenPaywallViewOptions>;
342
- };
343
-
344
- /**
345
- * Native props for FooterPaywall component.
346
- * @internal
347
- */
348
- type NativeInternalFooterPaywallViewProps = Omit<InternalFooterPaywallViewProps, 'options'> & {
349
- options?: WithNativeCustomVariables<FooterPaywallViewOptions>;
350
- };
351
440
 
352
441
  const InternalCustomerCenterView = !usingPreviewAPIMode && UIManager.getViewManagerConfig('CustomerCenterView') != null
353
442
  ? requireNativeComponent<CustomerCenterViewProps>('CustomerCenterView')
@@ -517,6 +606,7 @@ export default class RevenueCatUI {
517
606
  style,
518
607
  children,
519
608
  options,
609
+ purchaseLogic,
520
610
  onPurchaseStarted,
521
611
  onPurchaseCompleted,
522
612
  onPurchaseError,
@@ -531,6 +621,7 @@ export default class RevenueCatUI {
531
621
  <InternalPaywall
532
622
  options={options}
533
623
  children={children}
624
+ purchaseLogic={purchaseLogic}
534
625
  onPurchaseStarted={onPurchaseStarted}
535
626
  onPurchaseCompleted={onPurchaseCompleted}
536
627
  onPurchaseError={onPurchaseError}