@encorekit/react-native 1.1.15

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/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # Encore React Native SDK
2
+
3
+ Thin React Native bridge to the native [Encore iOS SDK](https://github.com/EncoreKit/ios-sdk) and [Encore Android SDK](https://github.com/EncoreKit/android-sdk). Presents retention offers with native UI and delegates purchases to your existing subscription manager.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @encorekit/react-native
9
+ ```
10
+
11
+ ### iOS
12
+
13
+ ```bash
14
+ cd ios && pod install
15
+ ```
16
+
17
+ ### Android
18
+
19
+ No additional setup required — `com.encorekit:encore` resolves from Maven Central, which is included by default in Android projects. The React Native module auto-links with RN 0.60+.
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Wrap Your App with EncoreProvider
24
+
25
+ ```tsx
26
+ import { EncoreProvider } from '@encorekit/react-native';
27
+
28
+ export default function App() {
29
+ return (
30
+ <EncoreProvider apiKey="your_api_key" options={{ logLevel: 'debug' }}>
31
+ <YourApp />
32
+ </EncoreProvider>
33
+ );
34
+ }
35
+ ```
36
+
37
+ The provider calls `configure` and `registerCallbacks` once on mount.
38
+
39
+ ### 2. Identify Users
40
+
41
+ ```tsx
42
+ import Encore from '@encorekit/react-native';
43
+
44
+ // After authentication
45
+ Encore.identify('user_123', {
46
+ email: 'user@example.com',
47
+ subscriptionTier: 'premium',
48
+ });
49
+
50
+ // On logout
51
+ Encore.reset();
52
+ ```
53
+
54
+ ### 3. Show a Placement
55
+
56
+ ```tsx
57
+ const result = await Encore.show('paywall');
58
+ // result.status: 'granted' | 'not_granted' | 'completed' | 'dismissed' | 'no_offers'
59
+ ```
60
+
61
+ ### 4. Handle Callbacks
62
+
63
+ ```tsx
64
+ // Purchase request — delegate to your subscription manager
65
+ Encore.onPurchaseRequest(async ({ requestId, productId, placementId }) => {
66
+ try {
67
+ await RevenueCat.purchase(productId);
68
+ await Encore.completePurchaseRequest(requestId, true);
69
+ } catch {
70
+ await Encore.completePurchaseRequest(requestId, false);
71
+ }
72
+ });
73
+
74
+ // Passthrough — user dismissed without purchasing
75
+ Encore.onPassthrough(({ placementId }) => {
76
+ // Resume your original flow
77
+ });
78
+
79
+ // Purchase complete (fire-and-forget, for syncing with backends)
80
+ Encore.onPurchaseComplete(({ productId, transactionId }) => {
81
+ console.log('Purchase completed:', productId);
82
+ });
83
+ ```
84
+
85
+ Each `on*` method returns an unsubscribe function:
86
+
87
+ ```tsx
88
+ const unsubscribe = Encore.onPassthrough(handler);
89
+ // Later:
90
+ unsubscribe();
91
+ ```
92
+
93
+ ## API Reference
94
+
95
+ ### `configure(apiKey, options?)`
96
+
97
+ Initialize the SDK. Called automatically by `EncoreProvider`.
98
+
99
+ | Parameter | Type | Description |
100
+ |:----------|:-----|:------------|
101
+ | `apiKey` | `string` | Your Encore API key |
102
+ | `options.logLevel` | `'none' \| 'error' \| 'warn' \| 'info' \| 'debug'` | Log verbosity (default: `'none'`) |
103
+
104
+ ### `identify(userId, attributes?)`
105
+
106
+ Associate a user ID with SDK events.
107
+
108
+ ### `setUserAttributes(attributes)`
109
+
110
+ Merge attributes into the current user profile.
111
+
112
+ ### `reset()`
113
+
114
+ Clear user data and generate a new anonymous ID. Call on logout.
115
+
116
+ ### `show(placementId)`
117
+
118
+ Present a native offer sheet. Returns `PlacementResult`.
119
+
120
+ ### `registerCallbacks()`
121
+
122
+ Register native event handlers (purchase request, purchase complete, passthrough). Called automatically by `EncoreProvider`.
123
+
124
+ ### `completePurchaseRequest(requestId, success)`
125
+
126
+ Signal completion of a purchase request initiated via `onPurchaseRequest`. The native SDK waits for this call before proceeding.
127
+
128
+ ### `onPurchaseRequest(handler)`
129
+
130
+ Subscribe to purchase request events. Your handler receives `{ requestId, productId, placementId }` and must call `completePurchaseRequest` when done.
131
+
132
+ ### `onPurchaseComplete(handler)`
133
+
134
+ Subscribe to purchase completion events. Receives `{ productId, transactionId }`.
135
+
136
+ ### `onPassthrough(handler)`
137
+
138
+ Subscribe to passthrough events (user dismissed without purchasing). Receives `{ placementId }`.
139
+
140
+ ### `EncoreProvider`
141
+
142
+ React context provider. Props:
143
+
144
+ | Prop | Type | Description |
145
+ |:-----|:-----|:------------|
146
+ | `apiKey` | `string` | Your Encore API key |
147
+ | `options` | `ConfigureOptions` | Optional configuration |
148
+
149
+ ### `useEncoreContext()`
150
+
151
+ Hook returning the full `EncoreSDK` interface. Must be used within `EncoreProvider`.
152
+
153
+ ## Types
154
+
155
+ ```typescript
156
+ interface UserAttributes {
157
+ email?: string;
158
+ firstName?: string;
159
+ lastName?: string;
160
+ phoneNumber?: string;
161
+ postalCode?: string;
162
+ city?: string;
163
+ state?: string;
164
+ countryCode?: string;
165
+ latitude?: string;
166
+ longitude?: string;
167
+ dateOfBirth?: string;
168
+ gender?: string;
169
+ language?: string;
170
+ subscriptionTier?: string;
171
+ monthsSubscribed?: string;
172
+ billingCycle?: string;
173
+ lastPaymentAmount?: string;
174
+ lastActiveDate?: string;
175
+ totalSessions?: string;
176
+ custom?: Record<string, string>;
177
+ }
178
+
179
+ interface PlacementResult {
180
+ status: 'granted' | 'not_granted' | 'completed' | 'dismissed' | 'no_offers';
181
+ reason?: string;
182
+ entitlement?: string;
183
+ offerId?: string;
184
+ campaignId?: string;
185
+ }
186
+ ```
187
+
188
+ ## Requirements
189
+
190
+ - React Native 0.60+
191
+ - iOS 15.0+
192
+ - Android API 21+
193
+ - Node 16+
194
+
195
+ ## Note on Entitlements
196
+
197
+ Entitlement tracking is handled by the native SDKs and your subscription manager. The React Native bridge does not expose entitlement query methods directly — use your subscription manager's React Native SDK (RevenueCat, Adapty, etc.) for entitlement checks.
198
+
199
+ ## License
200
+
201
+ MIT
@@ -0,0 +1,80 @@
1
+ import groovy.json.JsonSlurper
2
+
3
+ def packageJson = new JsonSlurper().parseText(file("$projectDir/../package.json").text)
4
+ def encoreAndroidVersion = packageJson.sdkVersions.android["com.encorekit:encore"]
5
+
6
+ buildscript {
7
+ ext.safeExtGet = {prop, fallback ->
8
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
9
+ }
10
+ }
11
+
12
+ apply plugin: "com.android.library"
13
+ apply plugin: "kotlin-android"
14
+ def getExtOrDefault(name) {
15
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["EncoreReactSDK_" + name]
16
+ }
17
+
18
+ def getExtOrIntegerDefault(name) {
19
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["EncoreReactSDK_" + name]).toInteger()
20
+ }
21
+
22
+ android {
23
+ namespace "com.encorereactsdk"
24
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
25
+
26
+ defaultConfig {
27
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
28
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
29
+ }
30
+
31
+ buildTypes {
32
+ release {
33
+ minifyEnabled false
34
+ }
35
+ }
36
+
37
+ lint {
38
+ disable "GradleCompatible"
39
+ }
40
+
41
+ compileOptions {
42
+ sourceCompatibility JavaVersion.VERSION_17
43
+ targetCompatibility JavaVersion.VERSION_17
44
+ }
45
+
46
+ kotlinOptions {
47
+ jvmTarget = "17"
48
+ }
49
+ }
50
+
51
+ def minKotlinVersion = project.properties["EncoreReactSDK_kotlinVersion"]
52
+ def hostKotlinVersion = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : null
53
+ def kotlin_version = hostKotlinVersion ?: minKotlinVersion
54
+
55
+ if (hostKotlinVersion != null) {
56
+ def hostParts = hostKotlinVersion.tokenize('.').collect { it.toInteger() }
57
+ def minParts = minKotlinVersion.tokenize('.').collect { it.toInteger() }
58
+ def hostVer = hostParts[0] * 10000 + hostParts[1] * 100 + hostParts[2]
59
+ def minVer = minParts[0] * 10000 + minParts[1] * 100 + minParts[2]
60
+ if (hostVer < minVer) {
61
+ throw new GradleException(
62
+ "@encorekit/react-native requires Kotlin >= ${minKotlinVersion}, " +
63
+ "but your project uses ${hostKotlinVersion}. " +
64
+ "Update kotlinVersion in your root build.gradle."
65
+ )
66
+ }
67
+ }
68
+
69
+ dependencies {
70
+ // Host app provides these
71
+ compileOnly "com.facebook.react:react-android:+"
72
+ compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
73
+
74
+ // Bridge uses coroutines for async calls to native SDK
75
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
76
+
77
+ // Encore native Android SDK — we own this version
78
+ implementation "com.encorekit:encore:${encoreAndroidVersion}"
79
+ }
80
+
@@ -0,0 +1,11 @@
1
+ # Android SDK versions
2
+ EncoreReactSDK_compileSdkVersion=34
3
+ EncoreReactSDK_minSdkVersion=21
4
+ EncoreReactSDK_targetSdkVersion=34
5
+
6
+ # Kotlin version
7
+ EncoreReactSDK_kotlinVersion=1.9.24
8
+
9
+ # AndroidX
10
+ android.useAndroidX=true
11
+
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+
4
+ <!-- Required for opening URLs -->
5
+ <uses-permission android:name="android.permission.INTERNET" />
6
+
7
+ </manifest>
8
+
9
+
@@ -0,0 +1,216 @@
1
+ // EncoreReactSDKModule.kt
2
+ // Android bridge — delegates all calls to the native encore-android-sdk.
3
+
4
+ package com.encorereactsdk
5
+
6
+ import com.encorekit.encore.Encore
7
+ import com.encorekit.encore.core.canonical.user.UserAttributes
8
+ import com.encorekit.encore.core.infrastructure.logging.LogLevel
9
+ import com.facebook.react.bridge.*
10
+ import com.facebook.react.module.annotations.ReactModule
11
+ import com.facebook.react.modules.core.DeviceEventManagerModule
12
+ import kotlinx.coroutines.CompletableDeferred
13
+ import kotlinx.coroutines.CoroutineScope
14
+ import kotlinx.coroutines.Dispatchers
15
+ import kotlinx.coroutines.SupervisorJob
16
+ import kotlinx.coroutines.launch
17
+
18
+ @ReactModule(name = EncoreReactSDKModule.NAME)
19
+ class EncoreReactSDKModule(reactContext: ReactApplicationContext) :
20
+ ReactContextBaseJavaModule(reactContext) {
21
+
22
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
23
+ @Volatile private var currentDeferred: CompletableDeferred<Boolean>? = null
24
+
25
+ companion object {
26
+ const val NAME = "EncoreReactSDK"
27
+ }
28
+
29
+ override fun getName(): String = NAME
30
+
31
+ // -- Configuration --
32
+
33
+ @ReactMethod
34
+ fun configure(apiKey: String, options: ReadableMap, promise: Promise) {
35
+ val logLevel = when (options.getStringOrNull("logLevel")) {
36
+ "error" -> LogLevel.ERROR
37
+ "warn" -> LogLevel.WARN
38
+ "info" -> LogLevel.INFO
39
+ "debug" -> LogLevel.DEBUG
40
+ else -> LogLevel.NONE
41
+ }
42
+ Encore.shared.configure(reactApplicationContext, apiKey = apiKey, logLevel = logLevel)
43
+ promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
44
+ }
45
+
46
+ // -- User Identity --
47
+
48
+ @ReactMethod
49
+ fun identify(userId: String, attributes: ReadableMap?, promise: Promise) {
50
+ Encore.shared.identify(userId = userId, attributes = parseAttributes(attributes))
51
+ promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
52
+ }
53
+
54
+ @ReactMethod
55
+ fun setUserAttributes(attributes: ReadableMap, promise: Promise) {
56
+ parseAttributes(attributes)?.let { Encore.shared.setUserAttributes(it) }
57
+ promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
58
+ }
59
+
60
+ @ReactMethod
61
+ fun reset(promise: Promise) {
62
+ Encore.shared.reset()
63
+ promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
64
+ }
65
+
66
+ // -- Claim Control --
67
+
68
+ @ReactMethod
69
+ fun setClaimEnabled(enabled: Boolean, promise: Promise) {
70
+ Encore.shared.placements.isClaimEnabled = enabled
71
+ promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
72
+ }
73
+
74
+ // -- Offers --
75
+
76
+ @ReactMethod
77
+ fun show(placementId: String, promise: Promise) {
78
+ val activity = currentActivity
79
+ if (activity == null) {
80
+ promise.reject("NO_ACTIVITY", "No current activity available")
81
+ return
82
+ }
83
+
84
+ scope.launch {
85
+ try {
86
+ val result = Encore.shared.placement(placementId).show(activity)
87
+ val map = Arguments.createMap()
88
+ when (result) {
89
+ is com.encorekit.encore.features.offers.PresentationResult.Completed -> {
90
+ map.putString("status", "completed")
91
+ map.putString("offerId", result.offerId)
92
+ map.putString("campaignId", result.campaignId)
93
+ }
94
+ is com.encorekit.encore.features.offers.PresentationResult.Dismissed -> {
95
+ map.putString("status", "dismissed")
96
+ map.putString("reason", result.reason.value)
97
+ }
98
+ is com.encorekit.encore.features.offers.PresentationResult.NoOffers -> {
99
+ map.putString("status", "no_offers")
100
+ }
101
+ }
102
+ promise.resolve(map)
103
+ } catch (e: Exception) {
104
+ promise.reject("SHOW_FAILED", e.message, e)
105
+ }
106
+ }
107
+ }
108
+
109
+ // -- Callbacks --
110
+
111
+ @ReactMethod
112
+ fun registerCallbacks(promise: Promise) {
113
+ Encore.shared
114
+ .onPurchaseRequest { request ->
115
+ // Fail stale deferred so the previous purchase flow resolves
116
+ currentDeferred?.complete(false)
117
+
118
+ val deferred = CompletableDeferred<Boolean>()
119
+ currentDeferred = deferred
120
+
121
+ try {
122
+ sendEvent("onPurchaseRequest", Arguments.createMap().apply {
123
+ putString("productId", request.productId)
124
+ putString("placementId", request.placementId)
125
+ putString("promoOfferId", request.promoOfferId)
126
+ })
127
+ } catch (_: Exception) {
128
+ // sendEvent can throw during context teardown — safe to ignore
129
+ }
130
+
131
+ val success = deferred.await()
132
+ currentDeferred = null
133
+
134
+ if (!success) {
135
+ throw Exception("Purchase failed by JS handler")
136
+ }
137
+ }
138
+ .onPurchaseComplete { result, productId ->
139
+ sendEvent("onPurchaseComplete", Arguments.createMap().apply {
140
+ putString("productId", productId)
141
+ putString("purchaseToken", result.purchaseToken)
142
+ putString("orderId", result.orderId)
143
+ })
144
+ }
145
+ .onPassthrough { placementId ->
146
+ sendEvent("onPassthrough", Arguments.createMap().apply {
147
+ putString("placementId", placementId)
148
+ })
149
+ }
150
+
151
+ promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
152
+ }
153
+
154
+ @ReactMethod
155
+ fun completePurchaseRequest(success: Boolean, promise: Promise) {
156
+ val deferred = currentDeferred
157
+ if (deferred != null) {
158
+ deferred.complete(success)
159
+ promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
160
+ } else {
161
+ promise.resolve(Arguments.createMap().apply {
162
+ putBoolean("success", false)
163
+ putString("error", "No pending purchase request")
164
+ })
165
+ }
166
+ }
167
+
168
+ // -- Helpers --
169
+
170
+ private fun sendEvent(eventName: String, params: WritableMap) {
171
+ reactApplicationContext
172
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
173
+ .emit(eventName, params)
174
+ }
175
+
176
+ private fun parseAttributes(map: ReadableMap?): UserAttributes? {
177
+ map ?: return null
178
+ return UserAttributes(
179
+ email = map.getStringOrNull("email"),
180
+ firstName = map.getStringOrNull("firstName"),
181
+ lastName = map.getStringOrNull("lastName"),
182
+ phoneNumber = map.getStringOrNull("phoneNumber"),
183
+ postalCode = map.getStringOrNull("postalCode"),
184
+ city = map.getStringOrNull("city"),
185
+ state = map.getStringOrNull("state"),
186
+ countryCode = map.getStringOrNull("countryCode"),
187
+ latitude = map.getStringOrNull("latitude"),
188
+ longitude = map.getStringOrNull("longitude"),
189
+ dateOfBirth = map.getStringOrNull("dateOfBirth"),
190
+ gender = map.getStringOrNull("gender"),
191
+ language = map.getStringOrNull("language"),
192
+ subscriptionTier = map.getStringOrNull("subscriptionTier"),
193
+ monthsSubscribed = map.getStringOrNull("monthsSubscribed"),
194
+ billingCycle = map.getStringOrNull("billingCycle"),
195
+ lastPaymentAmount = map.getStringOrNull("lastPaymentAmount"),
196
+ lastActiveDate = map.getStringOrNull("lastActiveDate"),
197
+ totalSessions = map.getStringOrNull("totalSessions"),
198
+ custom = map.getCustomMap(),
199
+ )
200
+ }
201
+
202
+ private fun ReadableMap.getStringOrNull(key: String): String? =
203
+ if (hasKey(key)) getString(key) else null
204
+
205
+ private fun ReadableMap.getCustomMap(): Map<String, String> {
206
+ if (!hasKey("custom")) return emptyMap()
207
+ val customMap = getMap("custom") ?: return emptyMap()
208
+ val result = mutableMapOf<String, String>()
209
+ val iterator = customMap.keySetIterator()
210
+ while (iterator.hasNextKey()) {
211
+ val key = iterator.nextKey()
212
+ customMap.getString(key)?.let { result[key] = it }
213
+ }
214
+ return result
215
+ }
216
+ }
@@ -0,0 +1,22 @@
1
+ // EncoreReactSDKPackage.kt
2
+ // React Native Package for Android
3
+
4
+ package com.encorereactsdk
5
+
6
+ import com.facebook.react.ReactPackage
7
+ import com.facebook.react.bridge.NativeModule
8
+ import com.facebook.react.bridge.ReactApplicationContext
9
+ import com.facebook.react.uimanager.ViewManager
10
+
11
+ class EncoreReactSDKPackage : ReactPackage {
12
+ @Deprecated("Deprecated in React Native")
13
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
14
+ return listOf(EncoreReactSDKModule(reactContext))
15
+ }
16
+
17
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
18
+ return emptyList()
19
+ }
20
+ }
21
+
22
+
@@ -0,0 +1,24 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "encore-react-sdk"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => "15.0" }
14
+ s.source = { :git => "https://github.com/EncoreKit/react-native-sdk.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
17
+
18
+ ios_version = package["sdkVersions"]["ios"]["EncoreKit"]
19
+ s.dependency "React-Core"
20
+ s.dependency "EncoreKit", ios_version
21
+
22
+ s.swift_version = "5.9"
23
+ s.frameworks = "SafariServices", "UIKit"
24
+ end
@@ -0,0 +1,7 @@
1
+ // EncoreReactSDK-Bridging-Header.h
2
+ // Bridging header for Swift/Objective-C interoperability
3
+
4
+ #import <React/RCTBridgeModule.h>
5
+ #import <React/RCTEventEmitter.h>
6
+
7
+
@@ -0,0 +1,42 @@
1
+ // EncoreReactSDK.m
2
+ // Objective-C bridge declarations for React Native.
3
+ // Each method here corresponds to a Swift method in EncoreReactSDK.swift.
4
+
5
+ #import <React/RCTBridgeModule.h>
6
+ #import <React/RCTEventEmitter.h>
7
+
8
+ @interface RCT_EXTERN_MODULE(EncoreReactSDK, RCTEventEmitter)
9
+
10
+ RCT_EXTERN_METHOD(configure:(NSString *)apiKey
11
+ options:(NSDictionary *)options
12
+ resolver:(RCTPromiseResolveBlock)resolve
13
+ rejecter:(RCTPromiseRejectBlock)reject)
14
+
15
+ RCT_EXTERN_METHOD(identify:(NSString *)userId
16
+ attributes:(NSDictionary *)attributes
17
+ resolver:(RCTPromiseResolveBlock)resolve
18
+ rejecter:(RCTPromiseRejectBlock)reject)
19
+
20
+ RCT_EXTERN_METHOD(setUserAttributes:(NSDictionary *)attributes
21
+ resolver:(RCTPromiseResolveBlock)resolve
22
+ rejecter:(RCTPromiseRejectBlock)reject)
23
+
24
+ RCT_EXTERN_METHOD(reset:(RCTPromiseResolveBlock)resolve
25
+ rejecter:(RCTPromiseRejectBlock)reject)
26
+
27
+ RCT_EXTERN_METHOD(setClaimEnabled:(BOOL)enabled
28
+ resolver:(RCTPromiseResolveBlock)resolve
29
+ rejecter:(RCTPromiseRejectBlock)reject)
30
+
31
+ RCT_EXTERN_METHOD(show:(NSString *)placementId
32
+ resolver:(RCTPromiseResolveBlock)resolve
33
+ rejecter:(RCTPromiseRejectBlock)reject)
34
+
35
+ RCT_EXTERN_METHOD(registerCallbacks:(RCTPromiseResolveBlock)resolve
36
+ rejecter:(RCTPromiseRejectBlock)reject)
37
+
38
+ RCT_EXTERN_METHOD(completePurchaseRequest:(BOOL)success
39
+ resolver:(RCTPromiseResolveBlock)resolve
40
+ rejecter:(RCTPromiseRejectBlock)reject)
41
+
42
+ @end