@stripe/stripe-react-native 0.58.0 → 0.59.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 +2 -0
- package/android/src/main/AndroidManifest.xml +27 -1
- package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +2 -0
- package/android/src/main/java/com/reactnativestripesdk/EventEmitterCompat.kt +8 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentElementConfig.kt +8 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementConfig.kt +147 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementView.kt +164 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.kt +65 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentSheetAppearance.kt +1 -1
- package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +55 -26
- package/android/src/main/java/com/reactnativestripesdk/StripeConnectDeepLinkInterceptorActivity.kt +77 -0
- package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +325 -22
- package/android/src/main/java/com/reactnativestripesdk/StripeSdkPackage.kt +1 -0
- package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt +3 -0
- package/android/src/main/java/com/reactnativestripesdk/utils/Errors.kt +8 -0
- package/android/src/main/res/xml/file_paths.xml +4 -0
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/PaymentMethodMessagingElementViewManagerDelegate.java +36 -0
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/PaymentMethodMessagingElementViewManagerInterface.java +18 -0
- package/android/src/oldarch/java/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java +20 -0
- package/android/src/test/java/com/reactnativestripesdk/PaymentElementConfigTest.kt +37 -0
- package/android/src/test/java/com/reactnativestripesdk/PaymentMethodMessagingElementConfigTest.kt +543 -0
- package/android/src/test/java/com/reactnativestripesdk/PaymentSheetManagerTest.kt +70 -0
- package/ios/CustomerSheet/CustomerSheetUtils.swift +4 -0
- package/ios/OldArch/StripeSdkEventEmitterCompat.h +2 -0
- package/ios/OldArch/StripeSdkEventEmitterCompat.m +13 -1
- package/ios/PaymentMethodMessagingElementConfig.swift +116 -0
- package/ios/PaymentMethodMessagingElementHandler.m +9 -0
- package/ios/PaymentMethodMessagingElementView.swift +139 -0
- package/ios/StripeSdk.mm +40 -0
- package/ios/StripeSdkEmitter.swift +2 -0
- package/ios/StripeSdkImpl+CustomerSheet.swift +1 -0
- package/ios/StripeSdkImpl+Embedded.swift +4 -0
- package/ios/StripeSdkImpl+PaymentSheet.swift +16 -0
- package/ios/StripeSdkImpl.swift +132 -0
- package/jest/mock.js +20 -0
- package/lib/commonjs/connect/Components.js.map +1 -1
- package/lib/commonjs/connect/ConnectComponentsProvider.js +1 -1
- package/lib/commonjs/connect/ConnectComponentsProvider.js.map +1 -1
- package/lib/commonjs/connect/EmbeddedComponent.js +5 -5
- package/lib/commonjs/connect/EmbeddedComponent.js.map +1 -1
- package/lib/commonjs/connect/analytics/AnalyticsClient.js +2 -0
- package/lib/commonjs/connect/analytics/AnalyticsClient.js.map +1 -0
- package/lib/commonjs/connect/analytics/ComponentAnalyticsClient.js +2 -0
- package/lib/commonjs/connect/analytics/ComponentAnalyticsClient.js.map +1 -0
- package/lib/commonjs/connect/analytics/events.js +2 -0
- package/lib/commonjs/connect/analytics/events.js.map +1 -0
- package/lib/commonjs/connect/testUtils.js +2 -0
- package/lib/commonjs/connect/testUtils.js.map +1 -0
- package/lib/commonjs/events.js.map +1 -1
- package/lib/commonjs/functions.js +1 -1
- package/lib/commonjs/functions.js.map +1 -1
- package/lib/commonjs/hooks/useStripe.js +1 -1
- package/lib/commonjs/hooks/useStripe.js.map +1 -1
- package/lib/commonjs/specs/NativePaymentMethodMessagingElement.js +2 -0
- package/lib/commonjs/specs/NativePaymentMethodMessagingElement.js.map +1 -0
- package/lib/commonjs/specs/NativeStripeSdkModule.js.map +1 -1
- package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
- package/lib/commonjs/types/Errors.js +1 -1
- package/lib/commonjs/types/Errors.js.map +1 -1
- package/lib/commonjs/types/PaymentSheet.js.map +1 -1
- package/lib/commonjs/types/components/PaymentMethodMessagingElementComponent.js +2 -0
- package/lib/commonjs/types/components/PaymentMethodMessagingElementComponent.js.map +1 -0
- package/lib/commonjs/types/index.js.map +1 -1
- package/lib/module/connect/Components.js.map +1 -1
- package/lib/module/connect/ConnectComponentsProvider.js +1 -1
- package/lib/module/connect/ConnectComponentsProvider.js.map +1 -1
- package/lib/module/connect/EmbeddedComponent.js +5 -5
- package/lib/module/connect/EmbeddedComponent.js.map +1 -1
- package/lib/module/connect/analytics/AnalyticsClient.js +2 -0
- package/lib/module/connect/analytics/AnalyticsClient.js.map +1 -0
- package/lib/module/connect/analytics/ComponentAnalyticsClient.js +2 -0
- package/lib/module/connect/analytics/ComponentAnalyticsClient.js.map +1 -0
- package/lib/module/connect/analytics/events.js +2 -0
- package/lib/module/connect/analytics/events.js.map +1 -0
- package/lib/module/connect/testUtils.js +2 -0
- package/lib/module/connect/testUtils.js.map +1 -0
- package/lib/module/events.js.map +1 -1
- package/lib/module/functions.js +1 -1
- package/lib/module/functions.js.map +1 -1
- package/lib/module/hooks/useStripe.js +1 -1
- package/lib/module/hooks/useStripe.js.map +1 -1
- package/lib/module/specs/NativePaymentMethodMessagingElement.js +2 -0
- package/lib/module/specs/NativePaymentMethodMessagingElement.js.map +1 -0
- package/lib/module/specs/NativeStripeSdkModule.js.map +1 -1
- package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
- package/lib/module/types/Errors.js +1 -1
- package/lib/module/types/Errors.js.map +1 -1
- package/lib/module/types/PaymentSheet.js.map +1 -1
- package/lib/module/types/components/PaymentMethodMessagingElementComponent.js +2 -0
- package/lib/module/types/components/PaymentMethodMessagingElementComponent.js.map +1 -0
- package/lib/module/types/index.js.map +1 -1
- package/lib/typescript/src/connect/Components.d.ts +91 -0
- package/lib/typescript/src/connect/Components.d.ts.map +1 -1
- package/lib/typescript/src/connect/ConnectComponentsProvider.d.ts +61 -0
- package/lib/typescript/src/connect/ConnectComponentsProvider.d.ts.map +1 -1
- package/lib/typescript/src/connect/EmbeddedComponent.d.ts.map +1 -1
- package/lib/typescript/src/connect/analytics/AnalyticsClient.d.ts +32 -0
- package/lib/typescript/src/connect/analytics/AnalyticsClient.d.ts.map +1 -0
- package/lib/typescript/src/connect/analytics/ComponentAnalyticsClient.d.ts +94 -0
- package/lib/typescript/src/connect/analytics/ComponentAnalyticsClient.d.ts.map +1 -0
- package/lib/typescript/src/connect/analytics/events.d.ts +215 -0
- package/lib/typescript/src/connect/analytics/events.d.ts.map +1 -0
- package/lib/typescript/src/connect/testUtils.d.ts +45 -0
- package/lib/typescript/src/connect/testUtils.d.ts.map +1 -0
- package/lib/typescript/src/events.d.ts +2 -0
- package/lib/typescript/src/events.d.ts.map +1 -1
- package/lib/typescript/src/functions.d.ts +13 -1
- package/lib/typescript/src/functions.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useStripe.d.ts +2 -1
- package/lib/typescript/src/hooks/useStripe.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativePaymentMethodMessagingElement.d.ts +16 -0
- package/lib/typescript/src/specs/NativePaymentMethodMessagingElement.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts +16 -1
- package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts.map +1 -1
- package/lib/typescript/src/types/CustomerSheet.d.ts +5 -0
- package/lib/typescript/src/types/CustomerSheet.d.ts.map +1 -1
- package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts +5 -0
- package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
- package/lib/typescript/src/types/Errors.d.ts +4 -0
- package/lib/typescript/src/types/Errors.d.ts.map +1 -1
- package/lib/typescript/src/types/PaymentSheet.d.ts +5 -0
- package/lib/typescript/src/types/PaymentSheet.d.ts.map +1 -1
- package/lib/typescript/src/types/components/PaymentMethodMessagingElementComponent.d.ts +69 -0
- package/lib/typescript/src/types/components/PaymentMethodMessagingElementComponent.d.ts.map +1 -0
- package/lib/typescript/src/types/index.d.ts +8 -1
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/connect/Components.tsx +91 -0
- package/src/connect/ConnectComponentsProvider.tsx +69 -2
- package/src/connect/EmbeddedComponent.tsx +254 -30
- package/src/connect/analytics/AnalyticsClient.ts +75 -0
- package/src/connect/analytics/ComponentAnalyticsClient.ts +315 -0
- package/src/connect/analytics/events.ts +253 -0
- package/src/connect/testUtils.ts +37 -0
- package/src/events.ts +2 -0
- package/src/functions.ts +10 -0
- package/src/hooks/useStripe.tsx +8 -0
- package/src/specs/NativePaymentMethodMessagingElement.ts +25 -0
- package/src/specs/NativeStripeSdkModule.ts +21 -1
- package/src/types/CustomerSheet.ts +5 -0
- package/src/types/EmbeddedPaymentElement.tsx +5 -0
- package/src/types/Errors.ts +5 -0
- package/src/types/PaymentSheet.ts +6 -1
- package/src/types/components/PaymentMethodMessagingElementComponent.tsx +74 -0
- package/src/types/index.ts +11 -0
package/android/src/main/java/com/reactnativestripesdk/StripeConnectDeepLinkInterceptorActivity.kt
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
package com.reactnativestripesdk
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.os.Bundle
|
|
6
|
+
import android.util.Log
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Transparent interceptor Activity that captures stripe-connect:// deep links
|
|
10
|
+
* before they reach React Native's Linking module.
|
|
11
|
+
*
|
|
12
|
+
* This prevents Expo Router from receiving the URL and dismissing the current screen.
|
|
13
|
+
* The URL is stored in StripeSdkModule's internal storage and retrieved via polling.
|
|
14
|
+
*
|
|
15
|
+
* HOW IT WORKS:
|
|
16
|
+
* 1. Android launches this Activity when a stripe-connect:// URL is opened
|
|
17
|
+
* 2. onCreate() extracts the URL and stores it via StripeSdkModule.storeStripeConnectDeepLink()
|
|
18
|
+
* 3. Launches an Intent to bring the main app to the foreground
|
|
19
|
+
* 4. The Activity immediately finishes without showing any UI
|
|
20
|
+
* 5. JavaScript polls for URLs via NativeStripeSdk.pollAndClearPendingStripeConnectUrls()
|
|
21
|
+
*
|
|
22
|
+
* MANIFEST CONFIGURATION:
|
|
23
|
+
* The AndroidManifest.xml declares this Activity with:
|
|
24
|
+
* - android:exported="true" - allows deep links from outside the app
|
|
25
|
+
* - android:launchMode="singleTask" - reuses existing instance if available
|
|
26
|
+
* - android:theme="@android:style/Theme.Translucent.NoTitleBar" - transparent UI
|
|
27
|
+
* - Intent filter for stripe-connect:// scheme
|
|
28
|
+
*
|
|
29
|
+
* USER IMPACT:
|
|
30
|
+
* - Zero configuration required by users
|
|
31
|
+
* - Works automatically for all package names
|
|
32
|
+
* - No MainActivity override needed
|
|
33
|
+
*/
|
|
34
|
+
class StripeConnectDeepLinkInterceptorActivity : Activity() {
|
|
35
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
36
|
+
super.onCreate(savedInstanceState)
|
|
37
|
+
|
|
38
|
+
// Extract the deep link URL from the Intent
|
|
39
|
+
val url = intent?.data?.toString()
|
|
40
|
+
|
|
41
|
+
if (url != null && url.startsWith("stripe-connect://")) {
|
|
42
|
+
// Store in SDK's internal storage (thread-safe)
|
|
43
|
+
StripeSdkModule.storeStripeConnectDeepLink(url)
|
|
44
|
+
} else {
|
|
45
|
+
Log.w(TAG, "Unexpected URL scheme in StripeConnectDeepLinkInterceptor: $url")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Bring the main app back to the foreground
|
|
49
|
+
// This is critical because when Custom Tabs opens a deep link,
|
|
50
|
+
// the interceptor Activity starts in a new task. When it finishes,
|
|
51
|
+
// Android doesn't automatically return to the main app - it just shows
|
|
52
|
+
// the Custom Tab again. We need to explicitly bring the app forward.
|
|
53
|
+
try {
|
|
54
|
+
val packageName = applicationContext.packageName
|
|
55
|
+
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
|
|
56
|
+
|
|
57
|
+
if (launchIntent != null) {
|
|
58
|
+
// Flags to bring existing task to front without recreating activities
|
|
59
|
+
launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
|
60
|
+
launchIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
61
|
+
startActivity(launchIntent)
|
|
62
|
+
} else {
|
|
63
|
+
Log.w(TAG, "Could not get launch intent for package: $packageName")
|
|
64
|
+
}
|
|
65
|
+
} catch (e: Exception) {
|
|
66
|
+
Log.e(TAG, "Error bringing app to foreground", e)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Immediately finish - don't show any UI
|
|
70
|
+
// This makes the Activity transparent to the user
|
|
71
|
+
finish()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
companion object {
|
|
75
|
+
private const val TAG = "StripeConnectInterceptor"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -5,9 +5,13 @@ import android.app.Activity
|
|
|
5
5
|
import android.app.Application
|
|
6
6
|
import android.content.Intent
|
|
7
7
|
import android.os.Bundle
|
|
8
|
+
import android.os.Handler
|
|
9
|
+
import android.os.Looper
|
|
8
10
|
import android.util.Log
|
|
9
11
|
import android.view.ViewGroup
|
|
12
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
10
13
|
import androidx.browser.customtabs.CustomTabsIntent
|
|
14
|
+
import androidx.core.net.toUri
|
|
11
15
|
import androidx.fragment.app.FragmentActivity
|
|
12
16
|
import com.facebook.react.ReactActivity
|
|
13
17
|
import com.facebook.react.bridge.Arguments
|
|
@@ -19,6 +23,7 @@ import com.facebook.react.bridge.ReadableArray
|
|
|
19
23
|
import com.facebook.react.bridge.ReadableMap
|
|
20
24
|
import com.facebook.react.bridge.UiThreadUtil
|
|
21
25
|
import com.facebook.react.bridge.WritableMap
|
|
26
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
22
27
|
import com.facebook.react.module.annotations.ReactModule
|
|
23
28
|
import com.reactnativestripesdk.addresssheet.AddressLauncherManager
|
|
24
29
|
import com.reactnativestripesdk.customersheet.CustomerSheetManager
|
|
@@ -63,6 +68,7 @@ import com.stripe.android.model.ConfirmPaymentIntentParams
|
|
|
63
68
|
import com.stripe.android.model.ConfirmSetupIntentParams
|
|
64
69
|
import com.stripe.android.model.PaymentIntent
|
|
65
70
|
import com.stripe.android.model.PaymentMethod
|
|
71
|
+
import com.stripe.android.model.RadarSession
|
|
66
72
|
import com.stripe.android.model.SetupIntent
|
|
67
73
|
import com.stripe.android.model.Token
|
|
68
74
|
import com.stripe.android.payments.bankaccount.CollectBankAccountConfiguration
|
|
@@ -106,6 +112,10 @@ class StripeSdkModule(
|
|
|
106
112
|
|
|
107
113
|
internal var composeCompatView: StripeAbstractComposeView.CompatView? = null
|
|
108
114
|
|
|
115
|
+
// Storage for pending stripe-connect:// deep link URLs to prevent Expo Router from receiving them
|
|
116
|
+
private val pendingStripeConnectUrls = mutableListOf<String>()
|
|
117
|
+
private val pendingUrlsLock = Any()
|
|
118
|
+
|
|
109
119
|
val eventEmitter: EventEmitterCompat by lazy { EventEmitterCompat(reactApplicationContext) }
|
|
110
120
|
|
|
111
121
|
private val mActivityEventListener =
|
|
@@ -128,11 +138,6 @@ class StripeSdkModule(
|
|
|
128
138
|
it,
|
|
129
139
|
)
|
|
130
140
|
createPlatformPayPaymentMethodPromise = null
|
|
131
|
-
} ?: run {
|
|
132
|
-
Log.d(
|
|
133
|
-
"StripeReactNative",
|
|
134
|
-
"No promise was found, Google Pay result went unhandled,",
|
|
135
|
-
)
|
|
136
141
|
}
|
|
137
142
|
}
|
|
138
143
|
}
|
|
@@ -179,14 +184,38 @@ class StripeSdkModule(
|
|
|
179
184
|
}
|
|
180
185
|
|
|
181
186
|
@SuppressLint("RestrictedApi")
|
|
182
|
-
override fun getTypedExportedConstants()
|
|
183
|
-
|
|
187
|
+
override fun getTypedExportedConstants(): Map<String, Any> {
|
|
188
|
+
val packageInfo =
|
|
189
|
+
try {
|
|
190
|
+
reactApplicationContext.packageManager.getPackageInfo(
|
|
191
|
+
reactApplicationContext.packageName,
|
|
192
|
+
0,
|
|
193
|
+
)
|
|
194
|
+
} catch (e: Exception) {
|
|
195
|
+
null
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return mapOf(
|
|
184
199
|
"API_VERSIONS" to
|
|
185
200
|
mapOf(
|
|
186
201
|
"CORE" to ApiVersion.API_VERSION_CODE,
|
|
187
202
|
"ISSUING" to PushProvisioningProxy.getApiVersion(),
|
|
188
203
|
),
|
|
204
|
+
"SYSTEM_INFO" to
|
|
205
|
+
mapOf(
|
|
206
|
+
"sdkVersion" to STRIPE_ANDROID_SDK_VERSION,
|
|
207
|
+
"osVersion" to android.os.Build.VERSION.RELEASE,
|
|
208
|
+
"deviceType" to "${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL}",
|
|
209
|
+
"appName" to (
|
|
210
|
+
reactApplicationContext.applicationInfo
|
|
211
|
+
.loadLabel(
|
|
212
|
+
reactApplicationContext.packageManager,
|
|
213
|
+
).toString()
|
|
214
|
+
),
|
|
215
|
+
"appVersion" to (packageInfo?.versionName ?: ""),
|
|
216
|
+
),
|
|
189
217
|
)
|
|
218
|
+
}
|
|
190
219
|
|
|
191
220
|
@ReactMethod
|
|
192
221
|
override fun initialise(
|
|
@@ -1300,6 +1329,31 @@ class StripeSdkModule(
|
|
|
1300
1329
|
}
|
|
1301
1330
|
}
|
|
1302
1331
|
|
|
1332
|
+
@ReactMethod
|
|
1333
|
+
override fun createRadarSession(promise: Promise) {
|
|
1334
|
+
if (!::stripe.isInitialized) {
|
|
1335
|
+
promise.resolve(createMissingInitError())
|
|
1336
|
+
return
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
stripe.createRadarSession(
|
|
1340
|
+
stripeAccountId = stripeAccountId,
|
|
1341
|
+
callback =
|
|
1342
|
+
object : com.stripe.android.ApiResultCallback<RadarSession> {
|
|
1343
|
+
override fun onSuccess(session: RadarSession) {
|
|
1344
|
+
val result = WritableNativeMap()
|
|
1345
|
+
result.putString("id", session.id)
|
|
1346
|
+
promise.resolve(result)
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
override fun onError(e: Exception) {
|
|
1350
|
+
promise.resolve(createError(ErrorType.Failed.toString(), e))
|
|
1351
|
+
}
|
|
1352
|
+
},
|
|
1353
|
+
activity = getCurrentActivityOrResolveWithError(promise) as? AppCompatActivity,
|
|
1354
|
+
)
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1303
1357
|
@ReactMethod
|
|
1304
1358
|
override fun createEmbeddedPaymentElement(
|
|
1305
1359
|
intentConfig: ReadableMap,
|
|
@@ -1347,11 +1401,12 @@ class StripeSdkModule(
|
|
|
1347
1401
|
url: String,
|
|
1348
1402
|
promise: Promise,
|
|
1349
1403
|
) {
|
|
1404
|
+
isAuthWebViewActive = true
|
|
1350
1405
|
val activity = getCurrentActivityOrResolveWithError(promise) ?: return
|
|
1351
1406
|
|
|
1352
1407
|
UiThreadUtil.runOnUiThread {
|
|
1353
1408
|
try {
|
|
1354
|
-
val uri =
|
|
1409
|
+
val uri = url.toUri()
|
|
1355
1410
|
val builder =
|
|
1356
1411
|
androidx.browser.customtabs.CustomTabsIntent
|
|
1357
1412
|
.Builder()
|
|
@@ -1362,18 +1417,214 @@ class StripeSdkModule(
|
|
|
1362
1417
|
|
|
1363
1418
|
val customTabsIntent = builder.build()
|
|
1364
1419
|
|
|
1420
|
+
// NOTE: We intentionally do NOT use FLAG_ACTIVITY_NO_HISTORY here.
|
|
1421
|
+
// That flag can cause React Native's state restoration to fail when returning from Custom Tabs,
|
|
1422
|
+
// resulting in the navigation stack being reset and the previous screen being dismissed.
|
|
1423
|
+
// Custom Tabs will be properly cleaned up when the user navigates back or the session completes.
|
|
1424
|
+
|
|
1365
1425
|
// Note: Custom Tabs doesn't have built-in redirect handling like iOS ASWebAuthenticationSession.
|
|
1366
1426
|
// The redirect will be handled via deep linking when the auth server redirects to stripe-connect://
|
|
1367
1427
|
// The React Native Linking module will capture the deep link and pass it back to the JS layer.
|
|
1368
1428
|
customTabsIntent.launchUrl(activity, uri)
|
|
1369
1429
|
|
|
1430
|
+
// Fallback: Reset after timeout if JavaScript doesn't call authWebViewDeepLinkHandled
|
|
1431
|
+
Handler(Looper.getMainLooper()).postDelayed({
|
|
1432
|
+
if (isAuthWebViewActive) {
|
|
1433
|
+
isAuthWebViewActive = false
|
|
1434
|
+
}
|
|
1435
|
+
}, AUTH_WEBVIEW_FALLBACK_TIMEOUT_MS)
|
|
1436
|
+
|
|
1370
1437
|
promise.resolve(null)
|
|
1371
1438
|
} catch (e: Exception) {
|
|
1439
|
+
isAuthWebViewActive = false
|
|
1372
1440
|
promise.resolve(createError("Failed", e))
|
|
1373
1441
|
}
|
|
1374
1442
|
}
|
|
1375
1443
|
}
|
|
1376
1444
|
|
|
1445
|
+
@ReactMethod
|
|
1446
|
+
override fun downloadAndShareFile(
|
|
1447
|
+
url: String,
|
|
1448
|
+
filename: String?,
|
|
1449
|
+
promise: Promise,
|
|
1450
|
+
) {
|
|
1451
|
+
CoroutineScope(Dispatchers.IO).launch {
|
|
1452
|
+
try {
|
|
1453
|
+
// Download file
|
|
1454
|
+
val client = okhttp3.OkHttpClient()
|
|
1455
|
+
val request =
|
|
1456
|
+
okhttp3.Request
|
|
1457
|
+
.Builder()
|
|
1458
|
+
.url(url)
|
|
1459
|
+
.build()
|
|
1460
|
+
val response = client.newCall(request).execute()
|
|
1461
|
+
|
|
1462
|
+
if (!response.isSuccessful) {
|
|
1463
|
+
promise.resolve(
|
|
1464
|
+
Arguments.createMap().apply {
|
|
1465
|
+
putBoolean("success", false)
|
|
1466
|
+
putString("error", "NetworkError")
|
|
1467
|
+
putString("message", "HTTP ${response.code}")
|
|
1468
|
+
},
|
|
1469
|
+
)
|
|
1470
|
+
return@launch
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// Save to cache directory
|
|
1474
|
+
val exportsDir = java.io.File(reactApplicationContext.cacheDir, "stripe-exports")
|
|
1475
|
+
exportsDir.mkdirs()
|
|
1476
|
+
|
|
1477
|
+
val file = java.io.File(exportsDir, "export-${java.util.UUID.randomUUID()}.csv")
|
|
1478
|
+
|
|
1479
|
+
response.body?.byteStream()?.use { input ->
|
|
1480
|
+
file.outputStream().use { output ->
|
|
1481
|
+
input.copyTo(output)
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// Share on main thread
|
|
1486
|
+
UiThreadUtil.runOnUiThread {
|
|
1487
|
+
shareFile(file, promise)
|
|
1488
|
+
}
|
|
1489
|
+
} catch (e: Exception) {
|
|
1490
|
+
promise.resolve(
|
|
1491
|
+
Arguments.createMap().apply {
|
|
1492
|
+
putBoolean("success", false)
|
|
1493
|
+
putString("error", "DownloadFailed")
|
|
1494
|
+
putString("message", e.message ?: "Unknown error")
|
|
1495
|
+
},
|
|
1496
|
+
)
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
private fun shareFile(
|
|
1502
|
+
file: java.io.File,
|
|
1503
|
+
promise: Promise,
|
|
1504
|
+
) {
|
|
1505
|
+
val activity = reactApplicationContext.getCurrentActivity()
|
|
1506
|
+
if (activity == null) {
|
|
1507
|
+
promise.resolve(
|
|
1508
|
+
Arguments.createMap().apply {
|
|
1509
|
+
putBoolean("success", false)
|
|
1510
|
+
putString("error", "NoActivity")
|
|
1511
|
+
putString("message", "No activity available")
|
|
1512
|
+
},
|
|
1513
|
+
)
|
|
1514
|
+
return
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
try {
|
|
1518
|
+
val uri =
|
|
1519
|
+
androidx.core.content.FileProvider.getUriForFile(
|
|
1520
|
+
reactApplicationContext,
|
|
1521
|
+
"${reactApplicationContext.packageName}.stripe.fileprovider",
|
|
1522
|
+
file,
|
|
1523
|
+
)
|
|
1524
|
+
|
|
1525
|
+
val shareIntent =
|
|
1526
|
+
Intent(Intent.ACTION_SEND).apply {
|
|
1527
|
+
type = "text/csv"
|
|
1528
|
+
putExtra(Intent.EXTRA_STREAM, uri)
|
|
1529
|
+
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
val chooser = Intent.createChooser(shareIntent, "Share CSV Export")
|
|
1533
|
+
activity.startActivity(chooser)
|
|
1534
|
+
|
|
1535
|
+
// Schedule cleanup
|
|
1536
|
+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
1537
|
+
file.delete()
|
|
1538
|
+
}, 3000)
|
|
1539
|
+
|
|
1540
|
+
promise.resolve(
|
|
1541
|
+
Arguments.createMap().apply {
|
|
1542
|
+
putBoolean("success", true)
|
|
1543
|
+
},
|
|
1544
|
+
)
|
|
1545
|
+
} catch (e: Exception) {
|
|
1546
|
+
promise.resolve(
|
|
1547
|
+
Arguments.createMap().apply {
|
|
1548
|
+
putBoolean("success", false)
|
|
1549
|
+
putString("error", "ShareFailed")
|
|
1550
|
+
putString("message", e.message ?: "Unknown error")
|
|
1551
|
+
},
|
|
1552
|
+
)
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
@ReactMethod
|
|
1557
|
+
override fun authWebViewDeepLinkHandled(
|
|
1558
|
+
id: String,
|
|
1559
|
+
promise: Promise,
|
|
1560
|
+
) {
|
|
1561
|
+
isAuthWebViewActive = false
|
|
1562
|
+
promise.resolve(null)
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
/**
|
|
1566
|
+
* Store a stripe-connect:// deep link URL for later retrieval.
|
|
1567
|
+
* This prevents the URL from being broadcast to Expo Router.
|
|
1568
|
+
*/
|
|
1569
|
+
@ReactMethod
|
|
1570
|
+
override fun storeStripeConnectDeepLink(
|
|
1571
|
+
url: String,
|
|
1572
|
+
promise: Promise,
|
|
1573
|
+
) {
|
|
1574
|
+
synchronized(pendingUrlsLock) {
|
|
1575
|
+
pendingStripeConnectUrls.add(url)
|
|
1576
|
+
}
|
|
1577
|
+
promise.resolve(null)
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
/**
|
|
1581
|
+
* Poll for pending stripe-connect:// deep link URLs.
|
|
1582
|
+
* Returns all pending URLs and clears the queue.
|
|
1583
|
+
*
|
|
1584
|
+
* URLs are captured by StripeConnectDeepLinkInterceptor Activity, which prevents
|
|
1585
|
+
* Expo Router from receiving the URLs and dismissing the current screen.
|
|
1586
|
+
*/
|
|
1587
|
+
@ReactMethod
|
|
1588
|
+
override fun pollAndClearPendingStripeConnectUrls(promise: Promise) {
|
|
1589
|
+
try {
|
|
1590
|
+
val urlsArray = Arguments.createArray()
|
|
1591
|
+
|
|
1592
|
+
// Get URLs from SDK's internal storage (set by StripeConnectDeepLinkInterceptor)
|
|
1593
|
+
val sdkUrls = retrieveAndClearPendingUrls()
|
|
1594
|
+
sdkUrls.forEach { url ->
|
|
1595
|
+
urlsArray.pushString(url)
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// Legacy: Support old MainActivity pattern (deprecated, will be removed in future version)
|
|
1599
|
+
// This maintains backward compatibility for apps that implemented the manual pattern
|
|
1600
|
+
try {
|
|
1601
|
+
val mainActivityClass = Class.forName("com.stripe.examplestripeconnect.MainActivity")
|
|
1602
|
+
val getPendingUrlsMethod = mainActivityClass.getMethod("getPendingUrls")
|
|
1603
|
+
|
|
1604
|
+
@Suppress("UNCHECKED_CAST")
|
|
1605
|
+
val mainActivityUrls = getPendingUrlsMethod.invoke(null) as? List<String>
|
|
1606
|
+
if (!mainActivityUrls.isNullOrEmpty()) {
|
|
1607
|
+
Log.w(
|
|
1608
|
+
TAG,
|
|
1609
|
+
"Using deprecated MainActivity.getPendingUrls() pattern. " +
|
|
1610
|
+
"This pattern is no longer needed and will be removed in a future version. " +
|
|
1611
|
+
"The SDK now handles stripe-connect:// URLs automatically.",
|
|
1612
|
+
)
|
|
1613
|
+
mainActivityUrls.forEach { url ->
|
|
1614
|
+
urlsArray.pushString(url)
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
} catch (e: Exception) {
|
|
1618
|
+
// Expected when not using deprecated pattern - this is fine
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
promise.resolve(urlsArray)
|
|
1622
|
+
} catch (e: Exception) {
|
|
1623
|
+
Log.e(TAG, "Error polling URLs", e)
|
|
1624
|
+
promise.reject("PollError", "Failed to poll pending Stripe Connect URLs: ${e.message}", e)
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1377
1628
|
override fun addListener(eventType: String?) {
|
|
1378
1629
|
// noop, iOS only
|
|
1379
1630
|
}
|
|
@@ -1429,40 +1680,49 @@ class StripeSdkModule(
|
|
|
1429
1680
|
}
|
|
1430
1681
|
|
|
1431
1682
|
private var isRecreatingReactActivity = false
|
|
1683
|
+
private var isAuthWebViewActive = false
|
|
1432
1684
|
private val activityLifecycleCallbacks =
|
|
1433
1685
|
object : Application.ActivityLifecycleCallbacks {
|
|
1434
1686
|
override fun onActivityCreated(
|
|
1435
1687
|
activity: Activity,
|
|
1436
1688
|
bundle: Bundle?,
|
|
1437
1689
|
) {
|
|
1438
|
-
|
|
1690
|
+
// Only set flag when ReactActivity is actually being recreated (bundle != null)
|
|
1691
|
+
// bundle != null means this is a recreation, not first creation
|
|
1692
|
+
if (activity is ReactActivity && bundle != null) {
|
|
1439
1693
|
isRecreatingReactActivity = true
|
|
1440
1694
|
}
|
|
1441
|
-
|
|
1695
|
+
|
|
1696
|
+
// Don't finish Stripe activities during auth webview flow to prevent dismissing the previous screen
|
|
1697
|
+
val isStripeActivity = activity.javaClass.name.startsWith("com.stripe.android")
|
|
1698
|
+
val shouldFinish = isRecreatingReactActivity && isStripeActivity && !isAuthWebViewActive
|
|
1699
|
+
|
|
1700
|
+
if (shouldFinish) {
|
|
1442
1701
|
activity.finish()
|
|
1443
1702
|
}
|
|
1444
|
-
}
|
|
1445
1703
|
|
|
1446
|
-
|
|
1704
|
+
// Reset flag after finishing Stripe activities
|
|
1705
|
+
if (isRecreatingReactActivity && shouldFinish) {
|
|
1706
|
+
Handler(Looper.getMainLooper()).post {
|
|
1707
|
+
isRecreatingReactActivity = false
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1447
1710
|
}
|
|
1448
1711
|
|
|
1449
|
-
override fun
|
|
1450
|
-
}
|
|
1712
|
+
override fun onActivityStarted(activity: Activity) {}
|
|
1451
1713
|
|
|
1452
|
-
override fun
|
|
1453
|
-
}
|
|
1714
|
+
override fun onActivityResumed(activity: Activity) {}
|
|
1454
1715
|
|
|
1455
|
-
override fun
|
|
1456
|
-
|
|
1716
|
+
override fun onActivityPaused(activity: Activity) {}
|
|
1717
|
+
|
|
1718
|
+
override fun onActivityStopped(activity: Activity) {}
|
|
1457
1719
|
|
|
1458
1720
|
override fun onActivitySaveInstanceState(
|
|
1459
1721
|
activity: Activity,
|
|
1460
1722
|
bundle: Bundle,
|
|
1461
|
-
) {
|
|
1462
|
-
}
|
|
1723
|
+
) {}
|
|
1463
1724
|
|
|
1464
|
-
override fun onActivityDestroyed(activity: Activity) {
|
|
1465
|
-
}
|
|
1725
|
+
override fun onActivityDestroyed(activity: Activity) {}
|
|
1466
1726
|
}
|
|
1467
1727
|
|
|
1468
1728
|
/**
|
|
@@ -1491,5 +1751,48 @@ class StripeSdkModule(
|
|
|
1491
1751
|
|
|
1492
1752
|
companion object {
|
|
1493
1753
|
const val NAME = NativeStripeSdkModuleSpec.NAME
|
|
1754
|
+
private const val TAG = "StripeSdkModule"
|
|
1755
|
+
|
|
1756
|
+
// Read the Stripe Android SDK version from gradle.properties at build time
|
|
1757
|
+
private val STRIPE_ANDROID_SDK_VERSION = BuildConfig.STRIPE_ANDROID_SDK_VERSION
|
|
1758
|
+
|
|
1759
|
+
// Timeout for auth webview fallback (if JavaScript doesn't call authWebViewDeepLinkHandled)
|
|
1760
|
+
private const val AUTH_WEBVIEW_FALLBACK_TIMEOUT_MS = 60_000L
|
|
1761
|
+
|
|
1762
|
+
// SDK-managed storage for pending stripe-connect:// URLs
|
|
1763
|
+
// This is static because deep links can arrive before ReactContext is available
|
|
1764
|
+
private val pendingConnectUrls = mutableListOf<String>()
|
|
1765
|
+
private val urlsLock = Any()
|
|
1766
|
+
|
|
1767
|
+
/**
|
|
1768
|
+
* Store a stripe-connect:// deep link URL.
|
|
1769
|
+
* Called automatically by StripeConnectDeepLinkInterceptor.
|
|
1770
|
+
* Can also be called manually from MainActivity if users implement custom handling.
|
|
1771
|
+
*
|
|
1772
|
+
* This method is thread-safe and can be called from any thread.
|
|
1773
|
+
*
|
|
1774
|
+
* @param url The stripe-connect:// URL to store
|
|
1775
|
+
*/
|
|
1776
|
+
@JvmStatic
|
|
1777
|
+
fun storeStripeConnectDeepLink(url: String) {
|
|
1778
|
+
synchronized(urlsLock) {
|
|
1779
|
+
pendingConnectUrls.add(url)
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
/**
|
|
1784
|
+
* Retrieve and clear pending URLs.
|
|
1785
|
+
* Internal method used by pollAndClearPendingStripeConnectUrls() bridge method.
|
|
1786
|
+
*
|
|
1787
|
+
* @return List of pending stripe-connect:// URLs
|
|
1788
|
+
*/
|
|
1789
|
+
@JvmStatic
|
|
1790
|
+
internal fun retrieveAndClearPendingUrls(): List<String> {
|
|
1791
|
+
synchronized(urlsLock) {
|
|
1792
|
+
val urls = pendingConnectUrls.toList()
|
|
1793
|
+
pendingConnectUrls.clear()
|
|
1794
|
+
return urls
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1494
1797
|
}
|
|
1495
1798
|
}
|
|
@@ -60,6 +60,7 @@ class StripeSdkPackage : BaseReactPackage() {
|
|
|
60
60
|
AddressSheetViewManager(),
|
|
61
61
|
EmbeddedPaymentElementViewManager(),
|
|
62
62
|
NavigationBarManager(),
|
|
63
|
+
PaymentMethodMessagingElementViewManager(),
|
|
63
64
|
)
|
|
64
65
|
|
|
65
66
|
private fun getOnrampModuleClass(): Class<out NativeModule?> {
|
package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt
CHANGED
|
@@ -64,6 +64,8 @@ class CustomerSheetManager(
|
|
|
64
64
|
val billingConfigParams = arguments.getMap("billingDetailsCollectionConfiguration")
|
|
65
65
|
val allowsRemovalOfLastSavedPaymentMethod =
|
|
66
66
|
arguments.getBooleanOr("allowsRemovalOfLastSavedPaymentMethod", true)
|
|
67
|
+
val opensCardScannerAutomatically =
|
|
68
|
+
arguments.getBooleanOr("opensCardScannerAutomatically", false)
|
|
67
69
|
val paymentMethodOrder = arguments.getStringList("paymentMethodOrder")
|
|
68
70
|
|
|
69
71
|
val appearance =
|
|
@@ -83,6 +85,7 @@ class CustomerSheetManager(
|
|
|
83
85
|
.preferredNetworks(
|
|
84
86
|
mapToPreferredNetworks(arguments.getIntegerList("preferredNetworks")),
|
|
85
87
|
).allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod)
|
|
88
|
+
.opensCardScannerAutomatically(opensCardScannerAutomatically)
|
|
86
89
|
.cardBrandAcceptance(mapToCardBrandAcceptance(arguments))
|
|
87
90
|
|
|
88
91
|
paymentMethodOrder?.let { configuration.paymentMethodOrder(it) }
|
|
@@ -58,6 +58,14 @@ class PaymentSheetException(
|
|
|
58
58
|
message: String,
|
|
59
59
|
) : Exception(message)
|
|
60
60
|
|
|
61
|
+
class PaymentMethodMessagingElementAppearanceException(
|
|
62
|
+
message: String,
|
|
63
|
+
) : Exception(message)
|
|
64
|
+
|
|
65
|
+
class PaymentMethodMessagingElementConfigurationException(
|
|
66
|
+
message: String,
|
|
67
|
+
) : Exception(message)
|
|
68
|
+
|
|
61
69
|
internal fun mapError(
|
|
62
70
|
code: String,
|
|
63
71
|
message: String?,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
3
|
+
*
|
|
4
|
+
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
5
|
+
* once the code is regenerated.
|
|
6
|
+
*
|
|
7
|
+
* @generated by codegen project: GeneratePropsJavaDelegate.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
package com.facebook.react.viewmanagers;
|
|
11
|
+
|
|
12
|
+
import android.view.View;
|
|
13
|
+
import androidx.annotation.Nullable;
|
|
14
|
+
import com.facebook.react.bridge.DynamicFromObject;
|
|
15
|
+
import com.facebook.react.uimanager.BaseViewManager;
|
|
16
|
+
import com.facebook.react.uimanager.BaseViewManagerDelegate;
|
|
17
|
+
import com.facebook.react.uimanager.LayoutShadowNode;
|
|
18
|
+
|
|
19
|
+
public class PaymentMethodMessagingElementViewManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & PaymentMethodMessagingElementViewManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
|
|
20
|
+
public PaymentMethodMessagingElementViewManagerDelegate(U viewManager) {
|
|
21
|
+
super(viewManager);
|
|
22
|
+
}
|
|
23
|
+
@Override
|
|
24
|
+
public void setProperty(T view, String propName, @Nullable Object value) {
|
|
25
|
+
switch (propName) {
|
|
26
|
+
case "appearance":
|
|
27
|
+
mViewManager.setAppearance(view, new DynamicFromObject(value));
|
|
28
|
+
break;
|
|
29
|
+
case "configuration":
|
|
30
|
+
mViewManager.setConfiguration(view, new DynamicFromObject(value));
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
super.setProperty(view, propName, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
3
|
+
*
|
|
4
|
+
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
5
|
+
* once the code is regenerated.
|
|
6
|
+
*
|
|
7
|
+
* @generated by codegen project: GeneratePropsJavaInterface.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
package com.facebook.react.viewmanagers;
|
|
11
|
+
|
|
12
|
+
import android.view.View;
|
|
13
|
+
import com.facebook.react.bridge.Dynamic;
|
|
14
|
+
|
|
15
|
+
public interface PaymentMethodMessagingElementViewManagerInterface<T extends View> {
|
|
16
|
+
void setAppearance(T view, Dynamic value);
|
|
17
|
+
void setConfiguration(T view, Dynamic value);
|
|
18
|
+
}
|
|
@@ -272,6 +272,22 @@ public abstract class NativeStripeSdkModuleSpec extends ReactContextBaseJavaModu
|
|
|
272
272
|
@DoNotStrip
|
|
273
273
|
public abstract void openAuthenticatedWebView(String id, String url, Promise promise);
|
|
274
274
|
|
|
275
|
+
@ReactMethod
|
|
276
|
+
@DoNotStrip
|
|
277
|
+
public abstract void downloadAndShareFile(String url, @Nullable String filename, Promise promise);
|
|
278
|
+
|
|
279
|
+
@ReactMethod
|
|
280
|
+
@DoNotStrip
|
|
281
|
+
public abstract void authWebViewDeepLinkHandled(String id, Promise promise);
|
|
282
|
+
|
|
283
|
+
@ReactMethod
|
|
284
|
+
@DoNotStrip
|
|
285
|
+
public abstract void storeStripeConnectDeepLink(String url, Promise promise);
|
|
286
|
+
|
|
287
|
+
@ReactMethod
|
|
288
|
+
@DoNotStrip
|
|
289
|
+
public abstract void pollAndClearPendingStripeConnectUrls(Promise promise);
|
|
290
|
+
|
|
275
291
|
@ReactMethod
|
|
276
292
|
@DoNotStrip
|
|
277
293
|
public abstract void addListener(String eventType);
|
|
@@ -279,4 +295,8 @@ public abstract class NativeStripeSdkModuleSpec extends ReactContextBaseJavaModu
|
|
|
279
295
|
@ReactMethod
|
|
280
296
|
@DoNotStrip
|
|
281
297
|
public abstract void removeListeners(double count);
|
|
298
|
+
|
|
299
|
+
@ReactMethod
|
|
300
|
+
@DoNotStrip
|
|
301
|
+
public abstract void createRadarSession(Promise promise);
|
|
282
302
|
}
|