UNPKG

react-native-purchases-ui

Version:

React Native in-app purchases and subscriptions made easy. Supports iOS and Android.

241 lines (231 loc) 11.3 kB
import { NativeEventEmitter, NativeModules, Platform, requireNativeComponent, ScrollView, UIManager, View } from "react-native"; import { PAYWALL_RESULT } from "@revenuecat/purchases-typescript-internal"; import React, { useEffect, useState } from "react"; import { shouldUsePreviewAPIMode } from "./utils/environment"; import { previewNativeModuleRNCustomerCenter, previewNativeModuleRNPaywalls } from "./preview/nativeModules"; export { PAYWALL_RESULT } from "@revenuecat/purchases-typescript-internal"; const LINKING_ERROR = `The package 'react-native-purchases-ui' doesn't seem to be linked. Make sure: \n\n` + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + '- You rebuilt the app after installing the package\n'; // Get the native module or use the preview implementation const usingPreviewAPIMode = shouldUsePreviewAPIMode(); const RNPaywalls = usingPreviewAPIMode ? previewNativeModuleRNPaywalls : NativeModules.RNPaywalls; const RNCustomerCenter = usingPreviewAPIMode ? previewNativeModuleRNCustomerCenter : NativeModules.RNCustomerCenter; if (!RNPaywalls) { throw new Error(LINKING_ERROR); } if (!RNCustomerCenter) { throw new Error(LINKING_ERROR); } const eventEmitter = new NativeEventEmitter(RNPaywalls); const customerCenterEventEmitter = new NativeEventEmitter(RNCustomerCenter); const InternalPaywall = UIManager.getViewManagerConfig('Paywall') != null ? requireNativeComponent('Paywall') : () => { throw new Error(LINKING_ERROR); }; const InternalPaywallFooterView = UIManager.getViewManagerConfig('Paywall') != null ? requireNativeComponent('RCPaywallFooterView') : () => { throw new Error(LINKING_ERROR); }; // Currently the same as the base type, but can be extended later if needed // This is to prevent breaking changes when the native SDK adds new options export default class RevenueCatUI { static Defaults = { PRESENT_PAYWALL_DISPLAY_CLOSE_BUTTON: true }; /** * The result of presenting a paywall. This will be the last situation the user experienced before the paywall closed. * @readonly * @enum {string} */ static PAYWALL_RESULT = PAYWALL_RESULT; /** * Presents a paywall to the user with optional customization. * * This method allows for presenting a specific offering's paywall to the user. The caller * can decide whether to display a close button on the paywall through the `displayCloseButton` * parameter. By default, the close button is displayed. * * @param {PresentPaywallParams} params - The options for presenting the paywall. * @returns {Promise<PAYWALL_RESULT>} A promise that resolves with the result of the paywall presentation. */ static presentPaywall({ offering, displayCloseButton = RevenueCatUI.Defaults.PRESENT_PAYWALL_DISPLAY_CLOSE_BUTTON, fontFamily } = {}) { RevenueCatUI.logWarningIfPreviewAPIMode("presentPaywall"); return RNPaywalls.presentPaywall((offering === null || offering === void 0 ? void 0 : offering.identifier) ?? null, displayCloseButton, fontFamily); } /** * Presents a paywall to the user if a specific entitlement is not already owned. * * This method evaluates whether the user already owns the specified entitlement. * If the entitlement is not owned, it presents a paywall for the specified offering (if provided), or the * default offering (if no offering is provided), to the user. The paywall will be presented * allowing the user the opportunity to purchase the offering. The caller * can decide whether to display a close button on the paywall through the `displayCloseButton` * parameter. By default, the close button is displayed. * * @param {PresentPaywallIfNeededParams} params - The parameters for presenting the paywall. * @returns {Promise<PAYWALL_RESULT>} A promise that resolves with the result of the paywall presentation. */ static presentPaywallIfNeeded({ requiredEntitlementIdentifier, offering, displayCloseButton = RevenueCatUI.Defaults.PRESENT_PAYWALL_DISPLAY_CLOSE_BUTTON, fontFamily }) { RevenueCatUI.logWarningIfPreviewAPIMode("presentPaywallIfNeeded"); return RNPaywalls.presentPaywallIfNeeded(requiredEntitlementIdentifier, (offering === null || offering === void 0 ? void 0 : offering.identifier) ?? null, displayCloseButton, fontFamily); } static Paywall = ({ style, children, options, onPurchaseStarted, onPurchaseCompleted, onPurchaseError, onPurchaseCancelled, onRestoreStarted, onRestoreCompleted, onRestoreError, onDismiss }) => /*#__PURE__*/React.createElement(InternalPaywall, { options: options, children: children, onPurchaseStarted: event => onPurchaseStarted && onPurchaseStarted(event.nativeEvent), onPurchaseCompleted: event => onPurchaseCompleted && onPurchaseCompleted(event.nativeEvent), onPurchaseError: event => onPurchaseError && onPurchaseError(event.nativeEvent), onPurchaseCancelled: () => onPurchaseCancelled && onPurchaseCancelled(), onRestoreStarted: () => onRestoreStarted && onRestoreStarted(), onRestoreCompleted: event => onRestoreCompleted && onRestoreCompleted(event.nativeEvent), onRestoreError: event => onRestoreError && onRestoreError(event.nativeEvent), onDismiss: () => onDismiss && onDismiss(), style: [{ flex: 1 }, style] }); static OriginalTemplatePaywallFooterContainerView = ({ style, children, options, onPurchaseStarted, onPurchaseCompleted, onPurchaseError, onPurchaseCancelled, onRestoreStarted, onRestoreCompleted, onRestoreError, onDismiss }) => { // We use 20 as the default paddingBottom because that's the corner radius in the Android native SDK. // We also listen to safeAreaInsetsDidChange which is only sent from iOS and which is triggered when the // safe area insets change. Not adding this extra padding on iOS will cause the content of the scrollview // to be hidden behind the rounded corners of the paywall. const [paddingBottom, setPaddingBottom] = useState(20); const [height, setHeight] = useState(0); useEffect(() => { const handleSafeAreaInsetsChange = ({ bottom }) => { setPaddingBottom(20 + bottom); }; const subscription = eventEmitter.addListener('safeAreaInsetsDidChange', handleSafeAreaInsetsChange); return () => { subscription.remove(); }; }, []); return /*#__PURE__*/React.createElement(View, { style: [{ flex: 1 }, style] }, /*#__PURE__*/React.createElement(ScrollView, { contentContainerStyle: { flexGrow: 1, paddingBottom } }, children), /*#__PURE__*/React.createElement(InternalPaywallFooterView, { style: Platform.select({ ios: { marginTop: -20 }, android: { marginTop: -20, height } }), options: options, onPurchaseStarted: event => onPurchaseStarted && onPurchaseStarted(event.nativeEvent), onPurchaseCompleted: event => onPurchaseCompleted && onPurchaseCompleted(event.nativeEvent), onPurchaseError: event => onPurchaseError && onPurchaseError(event.nativeEvent), onPurchaseCancelled: () => onPurchaseCancelled && onPurchaseCancelled(), onRestoreStarted: () => onRestoreStarted && onRestoreStarted(), onRestoreCompleted: event => onRestoreCompleted && onRestoreCompleted(event.nativeEvent), onRestoreError: event => onRestoreError && onRestoreError(event.nativeEvent), onDismiss: () => onDismiss && onDismiss(), onMeasure: event => setHeight(event.nativeEvent.measurements.height) })); }; /** * Presents the customer center to the user. * * @param {PresentCustomerCenterParams} params - Optional parameters for presenting the customer center. * @returns {Promise<void>} A promise that resolves when the customer center is presented. */ static presentCustomerCenter(params) { if (params !== null && params !== void 0 && params.callbacks) { const subscriptions = []; const callbacks = params.callbacks; if (callbacks.onFeedbackSurveyCompleted) { const subscription = customerCenterEventEmitter.addListener('onFeedbackSurveyCompleted', event => callbacks.onFeedbackSurveyCompleted && callbacks.onFeedbackSurveyCompleted(event)); subscriptions.push(subscription); } if (callbacks.onShowingManageSubscriptions) { const subscription = customerCenterEventEmitter.addListener('onShowingManageSubscriptions', () => callbacks.onShowingManageSubscriptions && callbacks.onShowingManageSubscriptions()); subscriptions.push(subscription); } if (callbacks.onRestoreCompleted) { const subscription = customerCenterEventEmitter.addListener('onRestoreCompleted', event => callbacks.onRestoreCompleted && callbacks.onRestoreCompleted(event)); subscriptions.push(subscription); } if (callbacks.onRestoreFailed) { const subscription = customerCenterEventEmitter.addListener('onRestoreFailed', event => callbacks.onRestoreFailed && callbacks.onRestoreFailed(event)); subscriptions.push(subscription); } if (callbacks.onRestoreStarted) { const subscription = customerCenterEventEmitter.addListener('onRestoreStarted', () => callbacks.onRestoreStarted && callbacks.onRestoreStarted()); subscriptions.push(subscription); } if (callbacks.onRefundRequestStarted) { const subscription = customerCenterEventEmitter.addListener('onRefundRequestStarted', event => callbacks.onRefundRequestStarted && callbacks.onRefundRequestStarted(event)); subscriptions.push(subscription); } if (callbacks.onRefundRequestCompleted) { const subscription = customerCenterEventEmitter.addListener('onRefundRequestCompleted', event => callbacks.onRefundRequestCompleted && callbacks.onRefundRequestCompleted(event)); subscriptions.push(subscription); } if (callbacks.onManagementOptionSelected) { const subscription = customerCenterEventEmitter.addListener('onManagementOptionSelected', event => callbacks.onManagementOptionSelected && callbacks.onManagementOptionSelected(event)); subscriptions.push(subscription); } // Return a promise that resolves when the customer center is dismissed return RNCustomerCenter.presentCustomerCenter().finally(() => { // Clean up all event listeners when the customer center is dismissed subscriptions.forEach(subscription => subscription.remove()); }); } RevenueCatUI.logWarningIfPreviewAPIMode("presentCustomerCenter"); return RNCustomerCenter.presentCustomerCenter(); } /** * @deprecated, Use {@link OriginalTemplatePaywallFooterContainerView} instead */ static PaywallFooterContainerView = RevenueCatUI.OriginalTemplatePaywallFooterContainerView; static logWarningIfPreviewAPIMode(methodName) { if (usingPreviewAPIMode) { // tslint:disable-next-line:no-console console.warn(`[RevenueCatUI] [${methodName}] This method is available but has no effect in Preview API mode.`); } } } //# sourceMappingURL=index.js.map