react-native-purchases-ui 9.10.5 → 9.11.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/android/build.gradle +1 -1
- package/android/src/main/java/com/revenuecat/purchases/react/ui/BasePaywallViewManager.kt +60 -0
- package/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallEventName.kt +3 -1
- package/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallFooterViewManager.kt +5 -0
- package/android/src/main/java/com/revenuecat/purchases/react/ui/PaywallViewManager.kt +5 -0
- package/android/src/main/java/com/revenuecat/purchases/react/ui/RNPaywallsModule.kt +6 -0
- package/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPerformPurchaseEvent.kt +24 -0
- package/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPerformRestoreEvent.kt +21 -0
- package/android/src/main/java/com/revenuecat/purchases/react/ui/views/WrappedPaywallComposeView.kt +5 -0
- package/ios/PaywallViewManager.m +10 -1
- package/ios/PaywallViewWrapper.h +5 -0
- package/ios/PaywallViewWrapper.m +41 -10
- package/ios/RNPaywalls.m +8 -0
- package/lib/commonjs/index.js +94 -16
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +97 -15
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +53 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/index.tsx +111 -20
package/android/build.gradle
CHANGED
|
@@ -12,8 +12,11 @@ import com.facebook.react.uimanager.annotations.ReactProp
|
|
|
12
12
|
import com.facebook.react.uimanager.events.Event
|
|
13
13
|
import com.revenuecat.purchases.Package
|
|
14
14
|
import com.revenuecat.purchases.PresentedOfferingContext
|
|
15
|
+
import com.revenuecat.purchases.hybridcommon.ui.HybridPurchaseLogicBridge
|
|
15
16
|
import com.revenuecat.purchases.hybridcommon.ui.PaywallListenerWrapper
|
|
16
17
|
import com.revenuecat.purchases.react.ui.events.OnDismissEvent
|
|
18
|
+
import com.revenuecat.purchases.react.ui.events.OnPerformPurchaseEvent
|
|
19
|
+
import com.revenuecat.purchases.react.ui.events.OnPerformRestoreEvent
|
|
17
20
|
import com.revenuecat.purchases.react.ui.events.OnPurchaseCancelledEvent
|
|
18
21
|
import com.revenuecat.purchases.react.ui.events.OnPurchaseCompletedEvent
|
|
19
22
|
import com.revenuecat.purchases.react.ui.events.OnPurchaseErrorEvent
|
|
@@ -24,6 +27,7 @@ import com.revenuecat.purchases.react.ui.events.OnRestoreErrorEvent
|
|
|
24
27
|
import com.revenuecat.purchases.react.ui.events.OnRestoreStartedEvent
|
|
25
28
|
import com.revenuecat.purchases.ui.revenuecatui.CustomVariableValue
|
|
26
29
|
import com.revenuecat.purchases.ui.revenuecatui.fonts.CustomFontProvider
|
|
30
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
27
31
|
|
|
28
32
|
internal abstract class BasePaywallViewManager<T : View> : SimpleViewManager<T>() {
|
|
29
33
|
|
|
@@ -34,18 +38,23 @@ internal abstract class BasePaywallViewManager<T : View> : SimpleViewManager<T>(
|
|
|
34
38
|
private const val OPTION_FONT_FAMILY = "fontFamily"
|
|
35
39
|
private const val OPTION_DISPLAY_CLOSE_BUTTON = "displayCloseButton"
|
|
36
40
|
private const val OPTION_CUSTOM_VARIABLES = "customVariables"
|
|
41
|
+
private const val OPTION_HAS_PURCHASE_LOGIC = "hasPurchaseLogic"
|
|
37
42
|
|
|
38
43
|
private const val OPTION_OFFERING_AVAILABLE_PACKAGES = "availablePackages"
|
|
39
44
|
|
|
40
45
|
private const val OPTION_OFFERING_AVAILABLE_PACKAGES_PRESENTED_OFFERING_CONTEXT = "presentedOfferingContext"
|
|
41
46
|
}
|
|
42
47
|
|
|
48
|
+
private val purchaseLogicBridges = ConcurrentHashMap<Int, HybridPurchaseLogicBridge>()
|
|
49
|
+
|
|
43
50
|
abstract fun setOfferingId(view: T, offeringId: String?, presentedOfferingContext: PresentedOfferingContext? = null)
|
|
44
51
|
|
|
45
52
|
abstract fun setDisplayDismissButton(view: T, display: Boolean)
|
|
46
53
|
|
|
47
54
|
abstract fun setCustomVariables(view: T, customVariables: Map<String, CustomVariableValue>)
|
|
48
55
|
|
|
56
|
+
abstract fun setPurchaseLogic(view: T, bridge: HybridPurchaseLogicBridge?)
|
|
57
|
+
|
|
49
58
|
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
|
|
50
59
|
return MapBuilder.builder<String, Any>()
|
|
51
60
|
.putEvent(PaywallEventName.ON_PURCHASE_STARTED)
|
|
@@ -58,6 +67,8 @@ internal abstract class BasePaywallViewManager<T : View> : SimpleViewManager<T>(
|
|
|
58
67
|
.putEvent(PaywallEventName.ON_DISMISS)
|
|
59
68
|
.putEvent(PaywallEventName.ON_MEASURE)
|
|
60
69
|
.putEvent(PaywallEventName.ON_PURCHASE_PACKAGE_INITIATED)
|
|
70
|
+
.putEvent(PaywallEventName.ON_PERFORM_PURCHASE)
|
|
71
|
+
.putEvent(PaywallEventName.ON_PERFORM_RESTORE)
|
|
61
72
|
.build()
|
|
62
73
|
}
|
|
63
74
|
|
|
@@ -66,6 +77,7 @@ internal abstract class BasePaywallViewManager<T : View> : SimpleViewManager<T>(
|
|
|
66
77
|
@ReactProp(name = PROP_OPTIONS)
|
|
67
78
|
fun setOptions(view: T, options: ReadableMap?) {
|
|
68
79
|
if (options != null) {
|
|
80
|
+
setPurchaseLogicProp(view, options)
|
|
69
81
|
setOfferingIdProp(view, options)
|
|
70
82
|
setFontFamilyProp(view, options)
|
|
71
83
|
setDisplayCloseButton(view, options)
|
|
@@ -73,6 +85,11 @@ internal abstract class BasePaywallViewManager<T : View> : SimpleViewManager<T>(
|
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
87
|
|
|
88
|
+
override fun onDropViewInstance(view: T) {
|
|
89
|
+
purchaseLogicBridges.remove(view.id)?.cancelPending()
|
|
90
|
+
super.onDropViewInstance(view)
|
|
91
|
+
}
|
|
92
|
+
|
|
76
93
|
private fun setOfferingIdProp(view: T, options: ReadableMap?) {
|
|
77
94
|
val optionsMap = options?.toHashMap()
|
|
78
95
|
if (optionsMap == null || !optionsMap.containsKey(OPTION_OFFERING)) {
|
|
@@ -135,6 +152,49 @@ internal abstract class BasePaywallViewManager<T : View> : SimpleViewManager<T>(
|
|
|
135
152
|
}
|
|
136
153
|
}
|
|
137
154
|
|
|
155
|
+
private fun setPurchaseLogicProp(view: T, options: ReadableMap) {
|
|
156
|
+
val hasPurchaseLogic = options.takeIf { it.hasKey(OPTION_HAS_PURCHASE_LOGIC) }
|
|
157
|
+
?.getBoolean(OPTION_HAS_PURCHASE_LOGIC) ?: false
|
|
158
|
+
if (!hasPurchaseLogic) {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Purchase logic is set once at view creation and cannot be changed later.
|
|
163
|
+
// Skip if already configured (e.g., on a React re-render).
|
|
164
|
+
if (purchaseLogicBridges.containsKey(view.id)) {
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
val themedReactContext = view.context as? ThemedReactContext ?: return
|
|
169
|
+
|
|
170
|
+
val bridge = HybridPurchaseLogicBridge(
|
|
171
|
+
onPerformPurchase = { eventData ->
|
|
172
|
+
val requestId = eventData[HybridPurchaseLogicBridge.EVENT_KEY_REQUEST_ID] as? String ?: return@HybridPurchaseLogicBridge
|
|
173
|
+
@Suppress("UNCHECKED_CAST")
|
|
174
|
+
val packageMap = eventData[HybridPurchaseLogicBridge.EVENT_KEY_PACKAGE_BEING_PURCHASED] as? Map<String, Any?> ?: emptyMap()
|
|
175
|
+
val event = OnPerformPurchaseEvent(
|
|
176
|
+
surfaceId = view.surfaceId,
|
|
177
|
+
viewTag = view.id,
|
|
178
|
+
packageMap = packageMap,
|
|
179
|
+
requestId = requestId,
|
|
180
|
+
)
|
|
181
|
+
emitEvent(themedReactContext, view.id, event)
|
|
182
|
+
},
|
|
183
|
+
onPerformRestore = { eventData ->
|
|
184
|
+
val requestId = eventData[HybridPurchaseLogicBridge.EVENT_KEY_REQUEST_ID] as? String ?: return@HybridPurchaseLogicBridge
|
|
185
|
+
val event = OnPerformRestoreEvent(
|
|
186
|
+
surfaceId = view.surfaceId,
|
|
187
|
+
viewTag = view.id,
|
|
188
|
+
requestId = requestId,
|
|
189
|
+
)
|
|
190
|
+
emitEvent(themedReactContext, view.id, event)
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
purchaseLogicBridges[view.id] = bridge
|
|
195
|
+
setPurchaseLogic(view, bridge)
|
|
196
|
+
}
|
|
197
|
+
|
|
138
198
|
internal fun createPaywallListenerWrapper(
|
|
139
199
|
themedReactContext: ThemedReactContext,
|
|
140
200
|
view: View
|
|
@@ -10,7 +10,9 @@ internal enum class PaywallEventName(val eventName: String) {
|
|
|
10
10
|
ON_RESTORE_ERROR("onRestoreError"),
|
|
11
11
|
ON_DISMISS("onDismiss"),
|
|
12
12
|
ON_MEASURE("onMeasure"),
|
|
13
|
-
ON_PURCHASE_PACKAGE_INITIATED("onPurchasePackageInitiated")
|
|
13
|
+
ON_PURCHASE_PACKAGE_INITIATED("onPurchasePackageInitiated"),
|
|
14
|
+
ON_PERFORM_PURCHASE("onPerformPurchase"),
|
|
15
|
+
ON_PERFORM_RESTORE("onPerformRestore");
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
internal enum class PaywallEventKey(val key: String) {
|
|
@@ -3,6 +3,7 @@ package com.revenuecat.purchases.react.ui
|
|
|
3
3
|
import androidx.core.view.children
|
|
4
4
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
5
|
import com.revenuecat.purchases.PresentedOfferingContext
|
|
6
|
+
import com.revenuecat.purchases.hybridcommon.ui.HybridPurchaseLogicBridge
|
|
6
7
|
import com.revenuecat.purchases.react.ui.events.OnMeasureEvent
|
|
7
8
|
import com.revenuecat.purchases.react.ui.views.WrappedPaywallFooterComposeView
|
|
8
9
|
import com.revenuecat.purchases.ui.revenuecatui.CustomVariableValue
|
|
@@ -94,4 +95,8 @@ internal class PaywallFooterViewManager : BasePaywallViewManager<WrappedPaywallF
|
|
|
94
95
|
// No-op: Footer paywalls (legacy templates) don't support custom variables
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
override fun setPurchaseLogic(view: WrappedPaywallFooterComposeView, bridge: HybridPurchaseLogicBridge?) {
|
|
99
|
+
// No-op: Footer paywalls don't support custom purchase logic
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
}
|
|
@@ -2,6 +2,7 @@ package com.revenuecat.purchases.react.ui
|
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
4
4
|
import com.revenuecat.purchases.PresentedOfferingContext
|
|
5
|
+
import com.revenuecat.purchases.hybridcommon.ui.HybridPurchaseLogicBridge
|
|
5
6
|
import com.revenuecat.purchases.react.ui.views.WrappedPaywallComposeView
|
|
6
7
|
import com.revenuecat.purchases.ui.revenuecatui.CustomVariableValue
|
|
7
8
|
import com.revenuecat.purchases.ui.revenuecatui.fonts.CustomFontProvider
|
|
@@ -48,4 +49,8 @@ internal class PaywallViewManager : BasePaywallViewManager<WrappedPaywallCompose
|
|
|
48
49
|
view.setCustomVariables(customVariables)
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
override fun setPurchaseLogic(view: WrappedPaywallComposeView, bridge: HybridPurchaseLogicBridge?) {
|
|
53
|
+
view.setPurchaseLogic(bridge)
|
|
54
|
+
}
|
|
55
|
+
|
|
51
56
|
}
|
|
@@ -7,6 +7,7 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
7
7
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
8
8
|
import com.facebook.react.bridge.ReactMethod
|
|
9
9
|
import com.facebook.react.bridge.ReadableMap
|
|
10
|
+
import com.revenuecat.purchases.hybridcommon.ui.HybridPurchaseLogicBridge
|
|
10
11
|
import com.revenuecat.purchases.hybridcommon.ui.PaywallListenerWrapper
|
|
11
12
|
import com.revenuecat.purchases.hybridcommon.ui.PaywallResultListener
|
|
12
13
|
import com.revenuecat.purchases.hybridcommon.ui.PaywallSource
|
|
@@ -83,6 +84,11 @@ internal class RNPaywallsModule(
|
|
|
83
84
|
PaywallListenerWrapper.resumePurchasePackageInitiated(requestId, shouldProceed)
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
@ReactMethod
|
|
88
|
+
fun resolvePurchaseLogicResult(requestId: String, result: String, errorMessage: String?) {
|
|
89
|
+
HybridPurchaseLogicBridge.resolveResult(requestId, result, errorMessage)
|
|
90
|
+
}
|
|
91
|
+
|
|
86
92
|
@ReactMethod
|
|
87
93
|
fun addListener(eventName: String?) {
|
|
88
94
|
// Keep: Required for RN built in Event Emitter Calls.
|
package/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPerformPurchaseEvent.kt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.revenuecat.purchases.react.ui.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.WritableMap
|
|
4
|
+
import com.revenuecat.purchases.react.ui.PaywallEventKey
|
|
5
|
+
import com.revenuecat.purchases.react.ui.PaywallEventName
|
|
6
|
+
|
|
7
|
+
internal class OnPerformPurchaseEvent(
|
|
8
|
+
surfaceId: Int,
|
|
9
|
+
viewTag: Int,
|
|
10
|
+
private val packageMap: Map<String, Any?>,
|
|
11
|
+
private val requestId: String,
|
|
12
|
+
) : PaywallEvent<OnPerformPurchaseEvent>(surfaceId, viewTag) {
|
|
13
|
+
override fun getPaywallEventName() = PaywallEventName.ON_PERFORM_PURCHASE
|
|
14
|
+
|
|
15
|
+
override fun getPayload() = mapOf(
|
|
16
|
+
PaywallEventKey.PACKAGE to packageMap,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
override fun getEventData(): WritableMap {
|
|
20
|
+
return super.getEventData().apply {
|
|
21
|
+
putString(PaywallEventKey.REQUEST_ID.key, requestId)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
package/android/src/main/java/com/revenuecat/purchases/react/ui/events/OnPerformRestoreEvent.kt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package com.revenuecat.purchases.react.ui.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.WritableMap
|
|
4
|
+
import com.revenuecat.purchases.react.ui.PaywallEventKey
|
|
5
|
+
import com.revenuecat.purchases.react.ui.PaywallEventName
|
|
6
|
+
|
|
7
|
+
internal class OnPerformRestoreEvent(
|
|
8
|
+
surfaceId: Int,
|
|
9
|
+
viewTag: Int,
|
|
10
|
+
private val requestId: String,
|
|
11
|
+
) : PaywallEvent<OnPerformRestoreEvent>(surfaceId, viewTag) {
|
|
12
|
+
override fun getPaywallEventName() = PaywallEventName.ON_PERFORM_RESTORE
|
|
13
|
+
|
|
14
|
+
override fun getPayload(): Map<PaywallEventKey, Map<String, Any?>> = emptyMap()
|
|
15
|
+
|
|
16
|
+
override fun getEventData(): WritableMap {
|
|
17
|
+
return super.getEventData().apply {
|
|
18
|
+
putString(PaywallEventKey.REQUEST_ID.key, requestId)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/android/src/main/java/com/revenuecat/purchases/react/ui/views/WrappedPaywallComposeView.kt
CHANGED
|
@@ -5,6 +5,7 @@ import android.util.AttributeSet
|
|
|
5
5
|
import com.revenuecat.purchases.PresentedOfferingContext
|
|
6
6
|
import com.revenuecat.purchases.ui.revenuecatui.CustomVariableValue
|
|
7
7
|
import com.revenuecat.purchases.ui.revenuecatui.PaywallListener
|
|
8
|
+
import com.revenuecat.purchases.ui.revenuecatui.PurchaseLogic
|
|
8
9
|
import com.revenuecat.purchases.ui.revenuecatui.fonts.FontProvider
|
|
9
10
|
import com.revenuecat.purchases.ui.revenuecatui.views.PaywallView
|
|
10
11
|
|
|
@@ -38,6 +39,10 @@ class WrappedPaywallComposeView(context: Context) : ComposeViewWrapper<PaywallVi
|
|
|
38
39
|
wrappedView?.setCustomVariables(customVariables)
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
fun setPurchaseLogic(purchaseLogic: PurchaseLogic?) {
|
|
43
|
+
wrappedView?.setPurchaseLogic(purchaseLogic)
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
override fun requestLayout() {
|
|
42
47
|
super.requestLayout()
|
|
43
48
|
post(measureAndLayout)
|
package/ios/PaywallViewManager.m
CHANGED
|
@@ -31,6 +31,8 @@ RCT_EXPORT_VIEW_PROPERTY(onRestoreCompleted, RCTDirectEventBlock)
|
|
|
31
31
|
RCT_EXPORT_VIEW_PROPERTY(onRestoreError, RCTDirectEventBlock)
|
|
32
32
|
RCT_EXPORT_VIEW_PROPERTY(onDismiss, RCTDirectEventBlock)
|
|
33
33
|
RCT_EXPORT_VIEW_PROPERTY(onPurchasePackageInitiated, RCTDirectEventBlock)
|
|
34
|
+
RCT_EXPORT_VIEW_PROPERTY(onPerformPurchase, RCTDirectEventBlock)
|
|
35
|
+
RCT_EXPORT_VIEW_PROPERTY(onPerformRestore, RCTDirectEventBlock)
|
|
34
36
|
|
|
35
37
|
RCT_EXPORT_MODULE(Paywall)
|
|
36
38
|
|
|
@@ -51,8 +53,15 @@ RCT_EXPORT_MODULE(Paywall)
|
|
|
51
53
|
- (UIView *)view
|
|
52
54
|
{
|
|
53
55
|
if (@available(iOS 15.0, *)) {
|
|
54
|
-
|
|
56
|
+
PaywallViewCreationParams *params = [PaywallViewCreationParams new];
|
|
57
|
+
RCPaywallViewController *viewController = [self.proxy createPaywallViewWithParams:params];
|
|
55
58
|
PaywallViewWrapper *wrapper = [[PaywallViewWrapper alloc] initWithPaywallViewController:viewController];
|
|
59
|
+
PaywallProxy *proxy = self.proxy;
|
|
60
|
+
wrapper.createViewController = ^UIViewController * _Nullable(HybridPurchaseLogicBridge * _Nonnull bridge) {
|
|
61
|
+
PaywallViewCreationParams *params = [PaywallViewCreationParams new];
|
|
62
|
+
params.purchaseLogicBridge = bridge;
|
|
63
|
+
return [proxy createPaywallViewWithParams:params];
|
|
64
|
+
};
|
|
56
65
|
self.proxy.delegate = wrapper;
|
|
57
66
|
|
|
58
67
|
return wrapper;
|
package/ios/PaywallViewWrapper.h
CHANGED
|
@@ -22,6 +22,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
22
22
|
@property (nonatomic, copy) RCTDirectEventBlock onRestoreError;
|
|
23
23
|
@property (nonatomic, copy) RCTDirectEventBlock onDismiss;
|
|
24
24
|
@property (nonatomic, copy, nullable) RCTDirectEventBlock onPurchasePackageInitiated;
|
|
25
|
+
@property (nonatomic, copy) RCTDirectEventBlock onPerformPurchase;
|
|
26
|
+
@property (nonatomic, copy) RCTDirectEventBlock onPerformRestore;
|
|
27
|
+
|
|
28
|
+
@property (nonatomic, strong, nullable) HybridPurchaseLogicBridge *purchaseLogicBridge;
|
|
29
|
+
@property (nonatomic, copy, nullable) UIViewController * _Nullable (^createViewController)(HybridPurchaseLogicBridge * _Nonnull);
|
|
25
30
|
|
|
26
31
|
- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;
|
|
27
32
|
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
|
package/ios/PaywallViewWrapper.m
CHANGED
|
@@ -30,6 +30,10 @@ API_AVAILABLE(ios(15.0))
|
|
|
30
30
|
|
|
31
31
|
@implementation PaywallViewWrapper
|
|
32
32
|
|
|
33
|
+
- (void)dealloc {
|
|
34
|
+
[self.purchaseLogicBridge cancelPending];
|
|
35
|
+
}
|
|
36
|
+
|
|
33
37
|
- (instancetype)initWithPaywallViewController:(RCPaywallViewController *)paywallViewController API_AVAILABLE(ios(15.0)) {
|
|
34
38
|
NSParameterAssert(paywallViewController);
|
|
35
39
|
|
|
@@ -89,6 +93,31 @@ API_AVAILABLE(ios(15.0))
|
|
|
89
93
|
|
|
90
94
|
- (void)setOptions:(NSDictionary *)options {
|
|
91
95
|
if (@available(iOS 15.0, *)) {
|
|
96
|
+
// Replace the default VC with one that uses custom purchase logic.
|
|
97
|
+
// This runs before the view is added to the hierarchy (RN sets props after view creation).
|
|
98
|
+
NSNumber *hasPurchaseLogicValue = options[@"hasPurchaseLogic"];
|
|
99
|
+
BOOL hasPurchaseLogic = [hasPurchaseLogicValue isKindOfClass:[NSNumber class]] ? [hasPurchaseLogicValue boolValue] : NO;
|
|
100
|
+
if (hasPurchaseLogic && self.addedToHierarchy) {
|
|
101
|
+
NSLog(@"RNPaywalls - Warning: Purchase logic must be set before the view is displayed. Ignoring.");
|
|
102
|
+
} else if (hasPurchaseLogic && self.createViewController != nil) {
|
|
103
|
+
|
|
104
|
+
__weak typeof(self) weakSelf = self;
|
|
105
|
+
HybridPurchaseLogicBridge *bridge = [[HybridPurchaseLogicBridge alloc]
|
|
106
|
+
initOnPerformPurchase:^(NSDictionary *eventData) {
|
|
107
|
+
if (weakSelf.onPerformPurchase) {
|
|
108
|
+
weakSelf.onPerformPurchase(eventData);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
onPerformRestore:^(NSDictionary *eventData) {
|
|
112
|
+
if (weakSelf.onPerformRestore) {
|
|
113
|
+
weakSelf.onPerformRestore(eventData);
|
|
114
|
+
}
|
|
115
|
+
}];
|
|
116
|
+
self.purchaseLogicBridge = bridge;
|
|
117
|
+
|
|
118
|
+
self.paywallViewController = self.createViewController(bridge);
|
|
119
|
+
}
|
|
120
|
+
|
|
92
121
|
self.pendingOptions = options ?: @{};
|
|
93
122
|
self.didReceiveInitialOptions = YES;
|
|
94
123
|
|
|
@@ -152,27 +181,27 @@ API_AVAILABLE(ios(15.0))
|
|
|
152
181
|
if (!availablePackages || ![availablePackages isKindOfClass:[NSArray class]] || [availablePackages count] < 1) {
|
|
153
182
|
return nil;
|
|
154
183
|
}
|
|
155
|
-
|
|
184
|
+
|
|
156
185
|
NSDictionary *firstAvailablePackage = availablePackages[0];
|
|
157
186
|
if (!firstAvailablePackage || ![firstAvailablePackage isKindOfClass:[NSDictionary class]]) {
|
|
158
187
|
return nil;
|
|
159
188
|
}
|
|
160
|
-
|
|
189
|
+
|
|
161
190
|
NSDictionary *presentedOfferingContextOptions = firstAvailablePackage[@"presentedOfferingContext"];
|
|
162
191
|
if (!presentedOfferingContextOptions || [presentedOfferingContextOptions isKindOfClass:[NSNull class]]) {
|
|
163
192
|
return nil;
|
|
164
193
|
}
|
|
165
|
-
|
|
194
|
+
|
|
166
195
|
NSString *offeringIdentifier = presentedOfferingContextOptions[@"offeringIdentifier"];
|
|
167
196
|
if (!offeringIdentifier || [offeringIdentifier isKindOfClass:[NSNull class]]) {
|
|
168
197
|
return nil;
|
|
169
198
|
}
|
|
170
|
-
|
|
199
|
+
|
|
171
200
|
NSString *placementIdentifier = presentedOfferingContextOptions[@"placementIdentifier"];
|
|
172
201
|
if (![placementIdentifier isKindOfClass:[NSString class]]) {
|
|
173
202
|
placementIdentifier = nil;
|
|
174
203
|
}
|
|
175
|
-
|
|
204
|
+
|
|
176
205
|
RCTargetingContext *targetingContext;
|
|
177
206
|
NSDictionary *targetingContextOptions = presentedOfferingContextOptions[@"targetingContext"];
|
|
178
207
|
if (targetingContextOptions && ![targetingContextOptions isKindOfClass:[NSNull class]]) {
|
|
@@ -182,7 +211,7 @@ API_AVAILABLE(ios(15.0))
|
|
|
182
211
|
targetingContext = [[RCTargetingContext alloc] initWithRevision:[revision integerValue] ruleId:ruleId];
|
|
183
212
|
}
|
|
184
213
|
}
|
|
185
|
-
|
|
214
|
+
|
|
186
215
|
return [[RCPresentedOfferingContext alloc] initWithOfferingIdentifier:offeringIdentifier
|
|
187
216
|
placementIdentifier:placementIdentifier
|
|
188
217
|
targetingContext:targetingContext];
|
|
@@ -198,10 +227,12 @@ API_AVAILABLE(ios(15.0))
|
|
|
198
227
|
- (void)paywallViewController:(RCPaywallViewController *)controller
|
|
199
228
|
didFinishPurchasingWithCustomerInfoDictionary:(NSDictionary *)customerInfoDictionary
|
|
200
229
|
transactionDictionary:(NSDictionary *)transactionDictionary API_AVAILABLE(ios(15.0)) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
230
|
+
NSMutableDictionary *event = [NSMutableDictionary dictionaryWithObject:customerInfoDictionary
|
|
231
|
+
forKey:KeyCustomerInfo];
|
|
232
|
+
if (transactionDictionary) {
|
|
233
|
+
event[KeyStoreTransaction] = transactionDictionary;
|
|
234
|
+
}
|
|
235
|
+
self.onPurchaseCompleted([event copy]);
|
|
205
236
|
}
|
|
206
237
|
|
|
207
238
|
- (void)paywallViewControllerDidCancelPurchase:(RCPaywallViewController *)controller API_AVAILABLE(ios(15.0)) {
|
package/ios/RNPaywalls.m
CHANGED
|
@@ -134,6 +134,14 @@ RCT_EXPORT_METHOD(resumePurchasePackageInitiated:(NSString *)requestId
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
RCT_EXPORT_METHOD(resolvePurchaseLogicResult:(NSString *)requestId
|
|
138
|
+
result:(NSString *)result
|
|
139
|
+
errorMessage:(nullable NSString *)errorMessage) {
|
|
140
|
+
if (@available(iOS 15.0, *)) {
|
|
141
|
+
[HybridPurchaseLogicBridge resolveResultWithRequestId:requestId resultString:result errorMessage:errorMessage];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
137
145
|
- (void)rejectPaywallsUnsupportedError:(RCTPromiseRejectBlock)reject {
|
|
138
146
|
NSLog(@"Error: attempted to present paywalls on unsupported iOS version.");
|
|
139
147
|
reject(@"PaywallsUnsupportedCode", @"Paywalls are not supported prior to iOS 15.", nil);
|
package/lib/commonjs/index.js
CHANGED
|
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "PAYWALL_RESULT", {
|
|
|
15
15
|
return _purchasesTypescriptInternal.PAYWALL_RESULT;
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
|
+
exports.PURCHASE_LOGIC_RESULT = void 0;
|
|
18
19
|
Object.defineProperty(exports, "convertCustomVariablesToStringMap", {
|
|
19
20
|
enumerable: true,
|
|
20
21
|
get: function () {
|
|
@@ -38,7 +39,32 @@ var _customVariables = require("./customVariables");
|
|
|
38
39
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
39
40
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
40
41
|
// Re-export for testing purposes (marked as @internal)
|
|
41
|
-
|
|
42
|
+
/**
|
|
43
|
+
* The result of a purchase or restore operation performed by custom app-based logic.
|
|
44
|
+
* Used when `purchasesAreCompletedBy` is set to `MY_APP`.
|
|
45
|
+
* @readonly
|
|
46
|
+
* @enum {string}
|
|
47
|
+
*/
|
|
48
|
+
let PURCHASE_LOGIC_RESULT = exports.PURCHASE_LOGIC_RESULT = /*#__PURE__*/function (PURCHASE_LOGIC_RESULT) {
|
|
49
|
+
/** The purchase or restore was successful. */
|
|
50
|
+
PURCHASE_LOGIC_RESULT["SUCCESS"] = "SUCCESS";
|
|
51
|
+
/** The purchase was cancelled by the user. */
|
|
52
|
+
PURCHASE_LOGIC_RESULT["CANCELLATION"] = "CANCELLATION";
|
|
53
|
+
/** An error occurred during the purchase or restore. */
|
|
54
|
+
PURCHASE_LOGIC_RESULT["ERROR"] = "ERROR";
|
|
55
|
+
return PURCHASE_LOGIC_RESULT;
|
|
56
|
+
}({});
|
|
57
|
+
/**
|
|
58
|
+
* The result of a purchase or restore operation performed by custom purchase logic.
|
|
59
|
+
* Uses a discriminated union to allow structured error information.
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Interface for handling purchases and restores within paywalls when
|
|
63
|
+
* `purchasesAreCompletedBy` is set to `MY_APP`.
|
|
64
|
+
*
|
|
65
|
+
* When provided, the paywall will call these functions instead of using
|
|
66
|
+
* RevenueCat's default purchase/restore behavior.
|
|
67
|
+
*/
|
|
42
68
|
const NATIVE_MODULE_NOT_FOUND_ERROR = `[RevenueCatUI] Native module not found. This can happen if:\n\n` + `- You are running in an unsupported environment (e.g., A browser or a container app that doesn't actually use the native modules)\n` + `- The native module failed to initialize\n` + `- react-native-purchases is not properly installed\n\n` + `To fix this:\n` + `- If using Expo, create a development build: https://docs.expo.dev/develop/development-builds/create-a-build/\n` + `- If using bare React Native, run 'pod install' and rebuild the app\n` + `- Make sure react-native-purchases is installed and you have rebuilt the app\n`;
|
|
43
69
|
|
|
44
70
|
// Get the native module or use the preview implementation
|
|
@@ -52,16 +78,66 @@ function throwIfNativeModulesNotAvailable() {
|
|
|
52
78
|
throw new Error(NATIVE_MODULE_NOT_FOUND_ERROR);
|
|
53
79
|
}
|
|
54
80
|
}
|
|
81
|
+
|
|
82
|
+
// Internal native props include purchase logic bridge events and native custom variable transforms
|
|
83
|
+
|
|
55
84
|
const NativePaywall = !usingPreviewAPIMode && _reactNative.UIManager.getViewManagerConfig('Paywall') != null ? (0, _reactNative.requireNativeComponent)('Paywall') : null;
|
|
56
85
|
const NativePaywallFooter = !usingPreviewAPIMode && _reactNative.UIManager.getViewManagerConfig('Paywall') != null ? (0, _reactNative.requireNativeComponent)('RCPaywallFooterView') : null;
|
|
57
86
|
|
|
58
87
|
// Only create event emitters if native modules are available
|
|
59
88
|
const eventEmitter = !usingPreviewAPIMode && RNPaywalls ? new _reactNative.NativeEventEmitter(RNPaywalls) : null;
|
|
60
89
|
const customerCenterEventEmitter = !usingPreviewAPIMode && RNCustomerCenter ? new _reactNative.NativeEventEmitter(RNCustomerCenter) : null;
|
|
90
|
+
function resolveLogicResult(requestId, logicResult) {
|
|
91
|
+
const errorMessage = logicResult.result === PURCHASE_LOGIC_RESULT.ERROR && logicResult.error ? logicResult.error.message : null;
|
|
92
|
+
RNPaywalls === null || RNPaywalls === void 0 || RNPaywalls.resolvePurchaseLogicResult(requestId, logicResult.result, errorMessage);
|
|
93
|
+
}
|
|
94
|
+
function createPurchaseLogicHandlers(purchaseLogic) {
|
|
95
|
+
if (!purchaseLogic) {
|
|
96
|
+
return {
|
|
97
|
+
nativeOptions: {},
|
|
98
|
+
handlePerformPurchase: undefined,
|
|
99
|
+
handlePerformRestore: undefined
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const nativeOptions = {
|
|
103
|
+
hasPurchaseLogic: true
|
|
104
|
+
};
|
|
105
|
+
const handlePerformPurchase = async event => {
|
|
106
|
+
const {
|
|
107
|
+
requestId,
|
|
108
|
+
packageBeingPurchased
|
|
109
|
+
} = event.nativeEvent;
|
|
110
|
+
try {
|
|
111
|
+
const logicResult = await purchaseLogic.performPurchase({
|
|
112
|
+
packageToPurchase: packageBeingPurchased
|
|
113
|
+
});
|
|
114
|
+
resolveLogicResult(requestId, logicResult);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
RNPaywalls === null || RNPaywalls === void 0 || RNPaywalls.resolvePurchaseLogicResult(requestId, PURCHASE_LOGIC_RESULT.ERROR, e instanceof Error ? e.message : null);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const handlePerformRestore = async event => {
|
|
120
|
+
const {
|
|
121
|
+
requestId
|
|
122
|
+
} = event.nativeEvent;
|
|
123
|
+
try {
|
|
124
|
+
const logicResult = await purchaseLogic.performRestore();
|
|
125
|
+
resolveLogicResult(requestId, logicResult);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
RNPaywalls === null || RNPaywalls === void 0 || RNPaywalls.resolvePurchaseLogicResult(requestId, PURCHASE_LOGIC_RESULT.ERROR, e instanceof Error ? e.message : null);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
nativeOptions,
|
|
132
|
+
handlePerformPurchase,
|
|
133
|
+
handlePerformRestore
|
|
134
|
+
};
|
|
135
|
+
}
|
|
61
136
|
const InternalPaywall = ({
|
|
62
137
|
style,
|
|
63
138
|
children,
|
|
64
139
|
options,
|
|
140
|
+
purchaseLogic,
|
|
65
141
|
onPurchaseStarted,
|
|
66
142
|
onPurchaseCompleted,
|
|
67
143
|
onPurchaseError,
|
|
@@ -72,6 +148,11 @@ const InternalPaywall = ({
|
|
|
72
148
|
onDismiss,
|
|
73
149
|
onPurchasePackageInitiated
|
|
74
150
|
}) => {
|
|
151
|
+
const {
|
|
152
|
+
nativeOptions,
|
|
153
|
+
handlePerformPurchase,
|
|
154
|
+
handlePerformRestore
|
|
155
|
+
} = createPurchaseLogicHandlers(purchaseLogic);
|
|
75
156
|
if (usingPreviewAPIMode) {
|
|
76
157
|
return /*#__PURE__*/_react.default.createElement(_previewComponents.PreviewPaywall, {
|
|
77
158
|
offering: options === null || options === void 0 ? void 0 : options.offering,
|
|
@@ -88,11 +169,14 @@ const InternalPaywall = ({
|
|
|
88
169
|
});
|
|
89
170
|
} else if (!!NativePaywall) {
|
|
90
171
|
// Transform options to native format (CustomVariables -> string map)
|
|
91
|
-
const
|
|
172
|
+
const transformedOptions = (0, _customVariables.transformOptionsForNative)(options);
|
|
92
173
|
return /*#__PURE__*/_react.default.createElement(NativePaywall, {
|
|
93
174
|
style: style,
|
|
94
175
|
children: children,
|
|
95
|
-
options:
|
|
176
|
+
options: {
|
|
177
|
+
...transformedOptions,
|
|
178
|
+
...nativeOptions
|
|
179
|
+
},
|
|
96
180
|
onPurchaseStarted: event => onPurchaseStarted && onPurchaseStarted(event.nativeEvent),
|
|
97
181
|
onPurchaseCompleted: event => onPurchaseCompleted && onPurchaseCompleted(event.nativeEvent),
|
|
98
182
|
onPurchaseError: event => onPurchaseError && onPurchaseError(event.nativeEvent),
|
|
@@ -117,7 +201,9 @@ const InternalPaywall = ({
|
|
|
117
201
|
} else {
|
|
118
202
|
RNPaywalls.resumePurchasePackageInitiated(requestId, true);
|
|
119
203
|
}
|
|
120
|
-
}
|
|
204
|
+
},
|
|
205
|
+
onPerformPurchase: handlePerformPurchase,
|
|
206
|
+
onPerformRestore: handlePerformRestore
|
|
121
207
|
});
|
|
122
208
|
}
|
|
123
209
|
throw new Error(NATIVE_MODULE_NOT_FOUND_ERROR);
|
|
@@ -152,11 +238,11 @@ const InternalPaywallFooterView = ({
|
|
|
152
238
|
});
|
|
153
239
|
} else if (!!NativePaywallFooter) {
|
|
154
240
|
// Transform options to native format (CustomVariables -> string map)
|
|
155
|
-
const
|
|
241
|
+
const transformedOptions = (0, _customVariables.transformOptionsForNative)(options);
|
|
156
242
|
return /*#__PURE__*/_react.default.createElement(NativePaywallFooter, {
|
|
157
243
|
style: style,
|
|
158
244
|
children: children,
|
|
159
|
-
options:
|
|
245
|
+
options: transformedOptions,
|
|
160
246
|
onPurchaseStarted: event => onPurchaseStarted && onPurchaseStarted(event.nativeEvent),
|
|
161
247
|
onPurchaseCompleted: event => onPurchaseCompleted && onPurchaseCompleted(event.nativeEvent),
|
|
162
248
|
onPurchaseError: event => onPurchaseError && onPurchaseError(event.nativeEvent),
|
|
@@ -178,16 +264,6 @@ const InternalPaywallFooterView = ({
|
|
|
178
264
|
* @internal
|
|
179
265
|
*/
|
|
180
266
|
|
|
181
|
-
/**
|
|
182
|
-
* Native props for FullScreenPaywall component.
|
|
183
|
-
* @internal
|
|
184
|
-
*/
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Native props for FooterPaywall component.
|
|
188
|
-
* @internal
|
|
189
|
-
*/
|
|
190
|
-
|
|
191
267
|
const InternalCustomerCenterView = !usingPreviewAPIMode && _reactNative.UIManager.getViewManagerConfig('CustomerCenterView') != null ? (0, _reactNative.requireNativeComponent)('CustomerCenterView') : null;
|
|
192
268
|
|
|
193
269
|
// This is to prevent breaking changes when the native SDK adds new options
|
|
@@ -255,6 +331,7 @@ class RevenueCatUI {
|
|
|
255
331
|
style,
|
|
256
332
|
children,
|
|
257
333
|
options,
|
|
334
|
+
purchaseLogic,
|
|
258
335
|
onPurchaseStarted,
|
|
259
336
|
onPurchaseCompleted,
|
|
260
337
|
onPurchaseError,
|
|
@@ -268,6 +345,7 @@ class RevenueCatUI {
|
|
|
268
345
|
return /*#__PURE__*/_react.default.createElement(InternalPaywall, {
|
|
269
346
|
options: options,
|
|
270
347
|
children: children,
|
|
348
|
+
purchaseLogic: purchaseLogic,
|
|
271
349
|
onPurchaseStarted: onPurchaseStarted,
|
|
272
350
|
onPurchaseCompleted: onPurchaseCompleted,
|
|
273
351
|
onPurchaseError: onPurchaseError,
|