@stripe/stripe-react-native 0.65.1 → 0.66.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/detekt/detekt-baseline.xml +0 -10
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt +51 -57
- package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorElementEvent.kt +26 -0
- package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorElementView.kt +129 -16
- package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorElementViewManager.kt +17 -0
- package/android/src/main/java/com/reactnativestripesdk/EventEmitterCompat.kt +4 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +8 -19
- package/android/src/main/java/com/reactnativestripesdk/StripeAbstractComposeView.kt +1 -0
- package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +95 -26
- package/android/src/main/java/com/reactnativestripesdk/StripeSdkPackage.kt +1 -0
- package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt +2 -16
- package/android/src/main/java/com/reactnativestripesdk/utils/CheckoutMappers.kt +59 -33
- package/android/src/main/java/com/reactnativestripesdk/utils/DefaultActivityLifecycleCallbacks.kt +41 -0
- package/android/src/oldarch/java/com/reactnativestripesdk/NativeOnrampSdkModuleSpec.java +17 -3
- package/android/src/oldarch/java/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java +5 -1
- package/android/src/onramp/java/com/reactnativestripesdk/OnrampMappers.kt +116 -0
- package/android/src/onramp/java/com/reactnativestripesdk/OnrampSdkModule.kt +109 -1
- package/android/src/test/java/com/reactnativestripesdk/DrawableLoadingTest.kt +6 -2
- package/android/src/test/java/com/reactnativestripesdk/mappers/OnrampMappersTest.kt +107 -0
- package/android/src/test/java/com/reactnativestripesdk/utils/CheckoutMappersTest.kt +39 -30
- package/android/src/test/java/com/reactnativestripesdk/utils/CheckoutTestFixtures.java +8 -0
- package/ios/CurrencySelectorElementManager.m +7 -0
- package/ios/CurrencySelectorElementView.swift +142 -7
- package/ios/EmbeddedPaymentElementView.swift +1 -1
- package/ios/Mappers+Checkout.swift +251 -107
- package/ios/Mappers.swift +90 -2
- package/ios/NewArch/StripeCurrencySelectorElementComponentView.mm +14 -0
- package/ios/OldArch/StripeSdkEventEmitterCompat.h +1 -0
- package/ios/OldArch/StripeSdkEventEmitterCompat.m +6 -0
- package/ios/PaymentOptionDisplayData+ReactNative.swift +1 -1
- package/ios/PaymentSheetAppearance.swift +1 -1
- package/ios/StripeOnrampSdk.mm +19 -0
- package/ios/StripeSdk.mm +17 -4
- package/ios/StripeSdkEmitter.swift +1 -0
- package/ios/StripeSdkImpl+Checkout.swift +75 -23
- package/ios/StripeSdkImpl+CustomerSheet.swift +1 -1
- package/ios/StripeSdkImpl+Embedded.swift +1 -1
- package/ios/StripeSdkImpl+PaymentSheet.swift +1 -1
- package/ios/StripeSdkImpl.swift +88 -8
- package/lib/commonjs/components/PaymentMethodMessagingElement.js +1 -1
- package/lib/commonjs/components/PaymentMethodMessagingElement.js.map +1 -1
- package/lib/commonjs/hooks/useCheckout.js +1 -1
- package/lib/commonjs/hooks/useCheckout.js.map +1 -1
- package/lib/commonjs/hooks/useOnramp.js +1 -1
- package/lib/commonjs/hooks/useOnramp.js.map +1 -1
- package/lib/commonjs/specs/NativeOnrampSdkModule.js.map +1 -1
- package/lib/commonjs/specs/NativeStripeSdkModule.js.map +1 -1
- package/lib/module/components/PaymentMethodMessagingElement.js +1 -1
- package/lib/module/components/PaymentMethodMessagingElement.js.map +1 -1
- package/lib/module/hooks/useCheckout.js +1 -1
- package/lib/module/hooks/useCheckout.js.map +1 -1
- package/lib/module/hooks/useOnramp.js +1 -1
- package/lib/module/hooks/useOnramp.js.map +1 -1
- package/lib/module/specs/NativeOnrampSdkModule.js.map +1 -1
- package/lib/module/specs/NativeStripeSdkModule.js.map +1 -1
- package/lib/typescript/src/components/PaymentMethodMessagingElement.d.ts +1 -1
- package/lib/typescript/src/components/PaymentMethodMessagingElement.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useCheckout.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useOnramp.d.ts +22 -0
- package/lib/typescript/src/hooks/useOnramp.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts +3 -0
- package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts +2 -1
- package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts.map +1 -1
- package/lib/typescript/src/types/Checkout.d.ts +266 -84
- package/lib/typescript/src/types/Checkout.d.ts.map +1 -1
- package/lib/typescript/src/types/Onramp.d.ts +94 -0
- package/lib/typescript/src/types/Onramp.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/components/PaymentMethodMessagingElement.tsx +54 -52
- package/src/hooks/useCheckout.tsx +20 -4
- package/src/hooks/useOnramp.tsx +44 -0
- package/src/specs/NativeOnrampSdkModule.ts +5 -0
- package/src/specs/NativeStripeSdkModule.ts +8 -1
- package/src/types/Checkout.ts +289 -84
- package/src/types/Onramp.ts +109 -0
- package/stripe-react-native.podspec +1 -1
- package/ios/CurrencySelectorElementManager.swift +0 -12
|
@@ -11,16 +11,6 @@
|
|
|
11
11
|
<ID>CyclomaticComplexMethod:Mappers.kt:internal fun mapToPaymentMethodType: PaymentMethod.Type?</ID>
|
|
12
12
|
<ID>CyclomaticComplexMethod:PaymentMethodCreateParamsFactory.kt:PaymentMethodCreateParamsFactory$@Throws(PaymentMethodCreateParamsException::class) fun createPaymentMethodParams: PaymentMethodCreateParams</ID>
|
|
13
13
|
<ID>CyclomaticComplexMethod:PaymentSheetAppearance.kt:@SuppressLint("RestrictedApi") @Throws(PaymentSheetAppearanceException::class) private fun buildEmbeddedAppearance: PaymentSheet.Appearance.Embedded</ID>
|
|
14
|
-
<ID>EmptyFunctionBlock:CardFieldView.kt:CardFieldView.<no name provided>${ }</ID>
|
|
15
|
-
<ID>EmptyFunctionBlock:CardFieldView.kt:CardFieldView.<no name provided>${}</ID>
|
|
16
|
-
<ID>EmptyFunctionBlock:CustomerSheetManager.kt:CustomerSheetManager.<no name provided>${ }</ID>
|
|
17
|
-
<ID>EmptyFunctionBlock:CustomerSheetManager.kt:CustomerSheetManager.<no name provided>${}</ID>
|
|
18
|
-
<ID>EmptyFunctionBlock:DrawableLoadingTest.kt:DrawableLoadingTest.MockAsyncDrawable${}</ID>
|
|
19
|
-
<ID>EmptyFunctionBlock:PaymentSheetManager.kt:<no name provided>${}</ID>
|
|
20
|
-
<ID>EmptyFunctionBlock:PaymentSheetManager.kt:PaymentSheetManager.<no name provided>${ }</ID>
|
|
21
|
-
<ID>EmptyFunctionBlock:PaymentSheetManager.kt:PaymentSheetManager.<no name provided>${}</ID>
|
|
22
|
-
<ID>EmptyFunctionBlock:StripeAbstractComposeView.kt:StripeAbstractComposeView.CompatView${ }</ID>
|
|
23
|
-
<ID>EmptyFunctionBlock:StripeSdkModule.kt:StripeSdkModule.<no name provided>${}</ID>
|
|
24
14
|
<ID>ForbiddenComment:Mappers.kt:// TODO: Can't access, private</ID>
|
|
25
15
|
<ID>ForbiddenComment:PaymentSheetAppearance.kt:// TODO: The theme is so crazy long, why does each Color thing has the same redundant Theme...</ID>
|
|
26
16
|
<ID>ForbiddenComment:PaymentSheetAppearance.kt:// TODO: Why is background a string but successBackgroundColor a "dynamic" color?</ID>
|
|
@@ -3,4 +3,4 @@ StripeSdk_compileSdkVersion=36
|
|
|
3
3
|
StripeSdk_targetSdkVersion=36
|
|
4
4
|
StripeSdk_minSdkVersion=23
|
|
5
5
|
# Keep StripeSdk_stripeVersion in sync with https://github.com/stripe/stripe-identity-react-native/blob/main/android/gradle.properties
|
|
6
|
-
StripeSdk_stripeVersion=23.
|
|
6
|
+
StripeSdk_stripeVersion=23.9.+
|
|
@@ -367,32 +367,10 @@ class CardFieldView(
|
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
-
mCardWidget.setCardInputListener(
|
|
371
|
-
object : CardInputListener {
|
|
372
|
-
override fun onCardComplete() {}
|
|
373
|
-
|
|
374
|
-
override fun onExpirationComplete() {}
|
|
375
|
-
|
|
376
|
-
override fun onCvcComplete() {}
|
|
377
|
-
|
|
378
|
-
override fun onPostalCodeComplete() {}
|
|
379
|
-
|
|
380
|
-
override fun onFocusChange(focusField: CardInputListener.FocusField) {}
|
|
381
|
-
},
|
|
382
|
-
)
|
|
370
|
+
mCardWidget.setCardInputListener(DefaultCardInputListener())
|
|
383
371
|
|
|
384
372
|
mCardWidget.setExpiryDateTextWatcher(
|
|
385
|
-
object :
|
|
386
|
-
override fun beforeTextChanged(
|
|
387
|
-
p0: CharSequence?,
|
|
388
|
-
p1: Int,
|
|
389
|
-
p2: Int,
|
|
390
|
-
p3: Int,
|
|
391
|
-
) {
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
override fun afterTextChanged(p0: Editable?) {}
|
|
395
|
-
|
|
373
|
+
object : DefaultTextWatcher() {
|
|
396
374
|
override fun onTextChanged(
|
|
397
375
|
var1: CharSequence?,
|
|
398
376
|
var2: Int,
|
|
@@ -410,17 +388,7 @@ class CardFieldView(
|
|
|
410
388
|
)
|
|
411
389
|
|
|
412
390
|
mCardWidget.setPostalCodeTextWatcher(
|
|
413
|
-
object :
|
|
414
|
-
override fun beforeTextChanged(
|
|
415
|
-
p0: CharSequence?,
|
|
416
|
-
p1: Int,
|
|
417
|
-
p2: Int,
|
|
418
|
-
p3: Int,
|
|
419
|
-
) {
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
override fun afterTextChanged(p0: Editable?) {}
|
|
423
|
-
|
|
391
|
+
object : DefaultTextWatcher() {
|
|
424
392
|
override fun onTextChanged(
|
|
425
393
|
var1: CharSequence?,
|
|
426
394
|
var2: Int,
|
|
@@ -433,17 +401,7 @@ class CardFieldView(
|
|
|
433
401
|
)
|
|
434
402
|
|
|
435
403
|
mCardWidget.setCardNumberTextWatcher(
|
|
436
|
-
object :
|
|
437
|
-
override fun beforeTextChanged(
|
|
438
|
-
p0: CharSequence?,
|
|
439
|
-
p1: Int,
|
|
440
|
-
p2: Int,
|
|
441
|
-
p3: Int,
|
|
442
|
-
) {
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
override fun afterTextChanged(p0: Editable?) {}
|
|
446
|
-
|
|
404
|
+
object : DefaultTextWatcher() {
|
|
447
405
|
override fun onTextChanged(
|
|
448
406
|
var1: CharSequence?,
|
|
449
407
|
var2: Int,
|
|
@@ -458,17 +416,7 @@ class CardFieldView(
|
|
|
458
416
|
)
|
|
459
417
|
|
|
460
418
|
mCardWidget.setCvcNumberTextWatcher(
|
|
461
|
-
object :
|
|
462
|
-
override fun beforeTextChanged(
|
|
463
|
-
p0: CharSequence?,
|
|
464
|
-
p1: Int,
|
|
465
|
-
p2: Int,
|
|
466
|
-
p3: Int,
|
|
467
|
-
) {
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
override fun afterTextChanged(p0: Editable?) {}
|
|
471
|
-
|
|
419
|
+
object : DefaultTextWatcher() {
|
|
472
420
|
override fun onTextChanged(
|
|
473
421
|
var1: CharSequence?,
|
|
474
422
|
var2: Int,
|
|
@@ -534,4 +482,50 @@ class CardFieldView(
|
|
|
534
482
|
private companion object {
|
|
535
483
|
const val CARD_WIDGET_HORIZONTAL_PADDING = 20
|
|
536
484
|
}
|
|
485
|
+
|
|
486
|
+
private open class DefaultTextWatcher : TextWatcher {
|
|
487
|
+
override fun beforeTextChanged(
|
|
488
|
+
p0: CharSequence?,
|
|
489
|
+
p1: Int,
|
|
490
|
+
p2: Int,
|
|
491
|
+
p3: Int,
|
|
492
|
+
) {
|
|
493
|
+
// NO-OP
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
override fun afterTextChanged(p0: Editable?) {
|
|
497
|
+
// NO-OP
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
override fun onTextChanged(
|
|
501
|
+
var1: CharSequence?,
|
|
502
|
+
var2: Int,
|
|
503
|
+
var3: Int,
|
|
504
|
+
var4: Int,
|
|
505
|
+
) {
|
|
506
|
+
// NO-OP
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
private open class DefaultCardInputListener : CardInputListener {
|
|
511
|
+
override fun onCardComplete() {
|
|
512
|
+
// NO-OP
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
override fun onExpirationComplete() {
|
|
516
|
+
// NO-OP
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
override fun onCvcComplete() {
|
|
520
|
+
// NO-OP
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
override fun onPostalCodeComplete() {
|
|
524
|
+
// NO-OP
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
override fun onFocusChange(focusField: CardInputListener.FocusField) {
|
|
528
|
+
// NO-OP
|
|
529
|
+
}
|
|
530
|
+
}
|
|
537
531
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
package com.reactnativestripesdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.WritableMap
|
|
4
|
+
import com.facebook.react.uimanager.events.Event
|
|
5
|
+
|
|
6
|
+
internal class CurrencySelectorElementEvent(
|
|
7
|
+
surfaceId: Int,
|
|
8
|
+
viewTag: Int,
|
|
9
|
+
private val eventType: EventType,
|
|
10
|
+
private val eventMap: WritableMap?,
|
|
11
|
+
) : Event<CurrencySelectorElementEvent>(surfaceId, viewTag) {
|
|
12
|
+
enum class EventType {
|
|
13
|
+
OnHeightChange,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override fun getEventData() = eventMap
|
|
17
|
+
|
|
18
|
+
companion object {
|
|
19
|
+
const val ON_HEIGHT_CHANGE = "topHeightChange"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
override fun getEventName(): String =
|
|
23
|
+
when (eventType) {
|
|
24
|
+
EventType.OnHeightChange -> ON_HEIGHT_CHANGE
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,26 +1,139 @@
|
|
|
1
1
|
package com.reactnativestripesdk
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
|
-
import android.
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import androidx.compose.foundation.layout.Box
|
|
6
|
+
import androidx.compose.runtime.Composable
|
|
7
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
8
|
+
import androidx.compose.runtime.getValue
|
|
9
|
+
import androidx.compose.runtime.mutableStateOf
|
|
10
|
+
import androidx.compose.runtime.remember
|
|
11
|
+
import androidx.compose.runtime.setValue
|
|
12
|
+
import androidx.compose.ui.Modifier
|
|
13
|
+
import androidx.compose.ui.input.pointer.pointerInput
|
|
14
|
+
import androidx.compose.ui.layout.layout
|
|
15
|
+
import androidx.compose.ui.layout.onSizeChanged
|
|
16
|
+
import androidx.compose.ui.platform.LocalDensity
|
|
17
|
+
import androidx.compose.ui.unit.IntOffset
|
|
18
|
+
import androidx.compose.ui.unit.dp
|
|
19
|
+
import com.facebook.react.bridge.Arguments
|
|
20
|
+
import com.facebook.react.bridge.ReactContext
|
|
21
|
+
import com.facebook.react.bridge.WritableMap
|
|
22
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
23
|
+
import com.stripe.android.paymentelement.CheckoutSessionPreview
|
|
24
|
+
|
|
25
|
+
@OptIn(CheckoutSessionPreview::class)
|
|
14
26
|
class CurrencySelectorElementView(
|
|
15
|
-
context:
|
|
16
|
-
) :
|
|
17
|
-
|
|
27
|
+
context: Context,
|
|
28
|
+
) : StripeAbstractComposeView(context) {
|
|
29
|
+
private val reactContext get() = context as ReactContext
|
|
30
|
+
|
|
31
|
+
// Backing state for props. mutableStateOf so Compose recomposes on changes.
|
|
32
|
+
private var sessionKeyState = mutableStateOf<String?>(null)
|
|
33
|
+
private var disabledState = mutableStateOf(false)
|
|
34
|
+
|
|
18
35
|
fun setSessionKey(value: String?) {
|
|
19
|
-
|
|
36
|
+
sessionKeyState.value = value?.takeIf { it.isNotEmpty() }
|
|
20
37
|
}
|
|
21
38
|
|
|
22
|
-
@Suppress("UnusedParameter")
|
|
23
39
|
fun setDisabled(value: Boolean) {
|
|
24
|
-
|
|
40
|
+
disabledState.value = value
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@SuppressLint("RestrictedApi")
|
|
44
|
+
@Composable
|
|
45
|
+
override fun Content() {
|
|
46
|
+
val sessionKey by remember { sessionKeyState }
|
|
47
|
+
val disabled by remember { disabledState }
|
|
48
|
+
|
|
49
|
+
val checkout = remember(sessionKey) {
|
|
50
|
+
sessionKey?.let { key ->
|
|
51
|
+
val module = reactContext.getNativeModule(StripeSdkModule::class.java)
|
|
52
|
+
module?.checkoutInstances?.get(key)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (checkout != null) {
|
|
57
|
+
// We don't push state to JS from this view — `StripeSdkModule` already
|
|
58
|
+
// observes the same `Checkout` instance and emits a unified
|
|
59
|
+
// `checkoutSessionDidChangeState` event that `useCheckout` mirrors into
|
|
60
|
+
// its `state`. Keeping the bridge in one place avoids redundant
|
|
61
|
+
// round-trips and inconsistencies.
|
|
62
|
+
MeasuredCurrencySelector(
|
|
63
|
+
reportHeightChange = { reportHeightChange(it) },
|
|
64
|
+
) {
|
|
65
|
+
Box {
|
|
66
|
+
checkout.CurrencySelectorContent()
|
|
67
|
+
if (disabled) {
|
|
68
|
+
// The Stripe Android composable doesn't yet expose an `isEnabled`
|
|
69
|
+
// parameter, so swallow taps with a transparent pointer-input
|
|
70
|
+
// overlay while `disabled` is true. The toggle stays visible but
|
|
71
|
+
// can't be interacted with.
|
|
72
|
+
Box(
|
|
73
|
+
Modifier
|
|
74
|
+
.matchParentSize()
|
|
75
|
+
.pointerInput(Unit) { /* consume all gestures */ },
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// No checkout yet: take up zero height so the element collapses.
|
|
82
|
+
LaunchedEffect(Unit) {
|
|
83
|
+
reportHeightChange(0f)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@Composable
|
|
89
|
+
private fun MeasuredCurrencySelector(
|
|
90
|
+
reportHeightChange: (Float) -> Unit,
|
|
91
|
+
content: @Composable () -> Unit,
|
|
92
|
+
) {
|
|
93
|
+
val density = LocalDensity.current
|
|
94
|
+
var heightDp by remember { mutableStateOf(1.dp) } // non-zero sentinel
|
|
95
|
+
|
|
96
|
+
Box(
|
|
97
|
+
Modifier
|
|
98
|
+
.onSizeChanged { size ->
|
|
99
|
+
val h = with(density) { size.height.toDp() }
|
|
100
|
+
if (h != heightDp) {
|
|
101
|
+
heightDp = h
|
|
102
|
+
reportHeightChange(h.value)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
.layout { measurable, constraints ->
|
|
106
|
+
val widthPx = constraints.maxWidth
|
|
107
|
+
val minHpx = measurable.minIntrinsicHeight(widthPx).coerceAtLeast(0)
|
|
108
|
+
|
|
109
|
+
val placeable = measurable.measure(
|
|
110
|
+
constraints.copy(minHeight = minHpx, maxHeight = minHpx),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
layout(constraints.maxWidth, placeable.height) {
|
|
114
|
+
placeable.placeRelative(IntOffset.Zero)
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
) {
|
|
118
|
+
content()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private fun reportHeightChange(height: Float) {
|
|
123
|
+
val params = Arguments.createMap().apply {
|
|
124
|
+
putDouble("height", height.toDouble())
|
|
125
|
+
}
|
|
126
|
+
dispatchEvent(CurrencySelectorElementEvent.EventType.OnHeightChange, params)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private fun dispatchEvent(
|
|
130
|
+
eventType: CurrencySelectorElementEvent.EventType,
|
|
131
|
+
params: WritableMap,
|
|
132
|
+
) {
|
|
133
|
+
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
|
|
134
|
+
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
|
|
135
|
+
dispatcher?.dispatchEvent(
|
|
136
|
+
CurrencySelectorElementEvent(surfaceId, id, eventType, params),
|
|
137
|
+
)
|
|
25
138
|
}
|
|
26
139
|
}
|
package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorElementViewManager.kt
CHANGED
|
@@ -6,7 +6,9 @@ import com.facebook.react.uimanager.ViewGroupManager
|
|
|
6
6
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
7
7
|
import com.facebook.react.viewmanagers.StripeCurrencySelectorElementManagerDelegate
|
|
8
8
|
import com.facebook.react.viewmanagers.StripeCurrencySelectorElementManagerInterface
|
|
9
|
+
import com.stripe.android.paymentelement.CheckoutSessionPreview
|
|
9
10
|
|
|
11
|
+
@OptIn(CheckoutSessionPreview::class)
|
|
10
12
|
@ReactModule(name = CurrencySelectorElementViewManager.NAME)
|
|
11
13
|
class CurrencySelectorElementViewManager :
|
|
12
14
|
ViewGroupManager<CurrencySelectorElementView>(),
|
|
@@ -24,11 +26,26 @@ class CurrencySelectorElementViewManager :
|
|
|
24
26
|
override fun createViewInstance(ctx: ThemedReactContext): CurrencySelectorElementView =
|
|
25
27
|
CurrencySelectorElementView(ctx)
|
|
26
28
|
|
|
29
|
+
override fun onDropViewInstance(view: CurrencySelectorElementView) {
|
|
30
|
+
super.onDropViewInstance(view)
|
|
31
|
+
view.handleOnDropViewInstance()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun needsCustomLayoutForChildren(): Boolean = true
|
|
35
|
+
|
|
36
|
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
|
|
37
|
+
mutableMapOf(
|
|
38
|
+
CurrencySelectorElementEvent.ON_HEIGHT_CHANGE to
|
|
39
|
+
mutableMapOf("registrationName" to "onHeightChange"),
|
|
40
|
+
)
|
|
41
|
+
|
|
27
42
|
@ReactProp(name = "sessionKey")
|
|
28
43
|
override fun setSessionKey(
|
|
29
44
|
view: CurrencySelectorElementView,
|
|
30
45
|
value: String?,
|
|
31
46
|
) {
|
|
47
|
+
// Codegen marks `sessionKey` as required, but it can briefly arrive as
|
|
48
|
+
// null when the view is recycled. Treat empty/null the same.
|
|
32
49
|
view.setSessionKey(value)
|
|
33
50
|
}
|
|
34
51
|
|
|
@@ -103,4 +103,8 @@ class EventEmitterCompat(
|
|
|
103
103
|
fun emitOnCheckoutClientSecretRequested(value: ReadableMap?) {
|
|
104
104
|
invoke("onCheckoutClientSecretRequested", value)
|
|
105
105
|
}
|
|
106
|
+
|
|
107
|
+
fun emitCheckoutSessionDidChangeState(value: ReadableMap?) {
|
|
108
|
+
invoke("checkoutSessionDidChangeState", value)
|
|
109
|
+
}
|
|
106
110
|
}
|
|
@@ -2,7 +2,6 @@ package com.reactnativestripesdk
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.app.Activity
|
|
5
|
-
import android.app.Application
|
|
6
5
|
import android.content.Intent
|
|
7
6
|
import android.graphics.Bitmap
|
|
8
7
|
import android.graphics.Canvas
|
|
@@ -21,6 +20,7 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
21
20
|
import com.facebook.react.bridge.ReadableMap
|
|
22
21
|
import com.facebook.react.bridge.WritableMap
|
|
23
22
|
import com.reactnativestripesdk.addresssheet.AddressSheetView
|
|
23
|
+
import com.reactnativestripesdk.utils.DefaultActivityLifecycleCallbacks
|
|
24
24
|
import com.reactnativestripesdk.utils.ErrorType
|
|
25
25
|
import com.reactnativestripesdk.utils.KeepJsAwakeTask
|
|
26
26
|
import com.reactnativestripesdk.utils.PaymentSheetAppearanceException
|
|
@@ -63,7 +63,6 @@ import kotlinx.coroutines.launch
|
|
|
63
63
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
64
64
|
import kotlinx.coroutines.withTimeoutOrNull
|
|
65
65
|
import java.io.ByteArrayOutputStream
|
|
66
|
-
import kotlin.Exception
|
|
67
66
|
import kotlin.coroutines.resume
|
|
68
67
|
|
|
69
68
|
@OptIn(
|
|
@@ -468,7 +467,7 @@ class PaymentSheetManager(
|
|
|
468
467
|
var paymentSheetActivity: Activity? = null
|
|
469
468
|
|
|
470
469
|
val activityLifecycleCallbacks =
|
|
471
|
-
object :
|
|
470
|
+
object : DefaultActivityLifecycleCallbacks() {
|
|
472
471
|
override fun onActivityCreated(
|
|
473
472
|
activity: Activity,
|
|
474
473
|
savedInstanceState: Bundle?,
|
|
@@ -480,20 +479,6 @@ class PaymentSheetManager(
|
|
|
480
479
|
}
|
|
481
480
|
}
|
|
482
481
|
|
|
483
|
-
override fun onActivityStarted(activity: Activity) {}
|
|
484
|
-
|
|
485
|
-
override fun onActivityResumed(activity: Activity) {}
|
|
486
|
-
|
|
487
|
-
override fun onActivityPaused(activity: Activity) {}
|
|
488
|
-
|
|
489
|
-
override fun onActivityStopped(activity: Activity) {}
|
|
490
|
-
|
|
491
|
-
override fun onActivitySaveInstanceState(
|
|
492
|
-
activity: Activity,
|
|
493
|
-
outState: Bundle,
|
|
494
|
-
) {
|
|
495
|
-
}
|
|
496
|
-
|
|
497
482
|
override fun onActivityDestroyed(activity: Activity) {
|
|
498
483
|
if (activity.javaClass.name == PAYMENT_SHEET_ACTIVITY ||
|
|
499
484
|
activity.javaClass.name == PAYMENT_OPTIONS_ACTIVITY
|
|
@@ -702,12 +687,16 @@ suspend fun waitForDrawableToLoad(
|
|
|
702
687
|
who: Drawable,
|
|
703
688
|
what: Runnable,
|
|
704
689
|
`when`: Long,
|
|
705
|
-
) {
|
|
690
|
+
) {
|
|
691
|
+
// NO-OP
|
|
692
|
+
}
|
|
706
693
|
|
|
707
694
|
override fun unscheduleDrawable(
|
|
708
695
|
who: Drawable,
|
|
709
696
|
what: Runnable,
|
|
710
|
-
) {
|
|
697
|
+
) {
|
|
698
|
+
// NO-OP
|
|
699
|
+
}
|
|
711
700
|
}
|
|
712
701
|
|
|
713
702
|
drawable.callback = callback
|
|
@@ -2,7 +2,6 @@ package com.reactnativestripesdk
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.app.Activity
|
|
5
|
-
import android.app.Application
|
|
6
5
|
import android.content.Intent
|
|
7
6
|
import android.os.Bundle
|
|
8
7
|
import android.os.Handler
|
|
@@ -10,7 +9,6 @@ import android.os.Looper
|
|
|
10
9
|
import android.util.Log
|
|
11
10
|
import android.view.ViewGroup
|
|
12
11
|
import androidx.appcompat.app.AppCompatActivity
|
|
13
|
-
import androidx.browser.customtabs.CustomTabsIntent
|
|
14
12
|
import androidx.core.net.toUri
|
|
15
13
|
import androidx.fragment.app.FragmentActivity
|
|
16
14
|
import com.facebook.react.ReactActivity
|
|
@@ -31,6 +29,7 @@ import com.reactnativestripesdk.customersheet.CustomerSheetManager
|
|
|
31
29
|
import com.reactnativestripesdk.pushprovisioning.PushProvisioningProxy
|
|
32
30
|
import com.reactnativestripesdk.utils.ConfirmPaymentErrorType
|
|
33
31
|
import com.reactnativestripesdk.utils.CreateTokenErrorType
|
|
32
|
+
import com.reactnativestripesdk.utils.DefaultActivityLifecycleCallbacks
|
|
34
33
|
import com.reactnativestripesdk.utils.ErrorType
|
|
35
34
|
import com.reactnativestripesdk.utils.GooglePayErrorType
|
|
36
35
|
import com.reactnativestripesdk.utils.RetrievePaymentIntentErrorType
|
|
@@ -82,10 +81,15 @@ import com.stripe.android.model.Token
|
|
|
82
81
|
import com.stripe.android.paymentelement.CheckoutSessionPreview
|
|
83
82
|
import com.stripe.android.payments.bankaccount.CollectBankAccountConfiguration
|
|
84
83
|
import com.stripe.android.paymentsheet.PaymentSheet
|
|
84
|
+
import kotlinx.coroutines.CancellableContinuation
|
|
85
85
|
import kotlinx.coroutines.CompletableDeferred
|
|
86
86
|
import kotlinx.coroutines.CoroutineScope
|
|
87
87
|
import kotlinx.coroutines.Dispatchers
|
|
88
|
+
import kotlinx.coroutines.Job
|
|
89
|
+
import kotlinx.coroutines.flow.combine
|
|
90
|
+
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
88
91
|
import kotlinx.coroutines.launch
|
|
92
|
+
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
89
93
|
import org.json.JSONObject
|
|
90
94
|
import java.util.UUID
|
|
91
95
|
|
|
@@ -113,6 +117,13 @@ class StripeSdkModule(
|
|
|
113
117
|
private var googlePayLauncherManager: GooglePayLauncherManager? = null
|
|
114
118
|
private var googlePayPaymentMethodLauncherManager: GooglePayPaymentMethodLauncherManager? = null
|
|
115
119
|
internal val checkoutInstances = mutableMapOf<String, Checkout>()
|
|
120
|
+
private val serverUpdateContinuations =
|
|
121
|
+
mutableMapOf<String, CancellableContinuation<Result<Unit>>>()
|
|
122
|
+
|
|
123
|
+
// / Tracks the long-running coroutine that observes each Checkout's
|
|
124
|
+
// / `checkoutSession` + `isLoading` flows and forwards transitions to JS as
|
|
125
|
+
// / `checkoutSessionDidChangeState` events.
|
|
126
|
+
private val checkoutStateObservers = mutableMapOf<String, Job>()
|
|
116
127
|
|
|
117
128
|
private var customerSheetManager: CustomerSheetManager? = null
|
|
118
129
|
|
|
@@ -165,6 +176,8 @@ class StripeSdkModule(
|
|
|
165
176
|
|
|
166
177
|
stripeUIManagers.forEach { it.destroy() }
|
|
167
178
|
stripeUIManagers.clear()
|
|
179
|
+
checkoutStateObservers.values.forEach { it.cancel() }
|
|
180
|
+
checkoutStateObservers.clear()
|
|
168
181
|
checkoutInstances.clear()
|
|
169
182
|
}
|
|
170
183
|
|
|
@@ -1743,6 +1756,7 @@ class StripeSdkModule(
|
|
|
1743
1756
|
onSuccess = { checkout ->
|
|
1744
1757
|
val sessionKey = UUID.randomUUID().toString()
|
|
1745
1758
|
checkoutInstances[sessionKey] = checkout
|
|
1759
|
+
observeCheckoutState(sessionKey, checkout)
|
|
1746
1760
|
|
|
1747
1761
|
promise.resolve(
|
|
1748
1762
|
Arguments.createMap().apply {
|
|
@@ -1866,13 +1880,48 @@ class StripeSdkModule(
|
|
|
1866
1880
|
}
|
|
1867
1881
|
}
|
|
1868
1882
|
|
|
1869
|
-
override fun
|
|
1883
|
+
override fun checkoutRunServerUpdateStart(
|
|
1870
1884
|
sessionKey: String,
|
|
1871
1885
|
promise: Promise,
|
|
1872
1886
|
) {
|
|
1873
|
-
|
|
1874
|
-
|
|
1887
|
+
val checkout = checkoutInstances[sessionKey] ?: run {
|
|
1888
|
+
promise.reject(ErrorType.Failed.toString(), "Checkout session not found.")
|
|
1889
|
+
return
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
if (serverUpdateContinuations.containsKey(sessionKey)) {
|
|
1893
|
+
promise.reject(ErrorType.Failed.toString(), "A server update is already in progress for this session.")
|
|
1894
|
+
return
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
1898
|
+
checkout.runServerUpdate {
|
|
1899
|
+
suspendCancellableCoroutine { continuation ->
|
|
1900
|
+
serverUpdateContinuations[sessionKey] = continuation
|
|
1901
|
+
}
|
|
1902
|
+
}.fold(
|
|
1903
|
+
onSuccess = { promise.resolve(mapFromCheckoutState(checkout)) },
|
|
1904
|
+
onFailure = { promise.reject(ErrorType.Failed.toString(), it.message, it) },
|
|
1905
|
+
)
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
override fun checkoutRunServerUpdateComplete(
|
|
1910
|
+
sessionKey: String,
|
|
1911
|
+
error: String?,
|
|
1912
|
+
promise: Promise,
|
|
1913
|
+
) {
|
|
1914
|
+
val continuation = serverUpdateContinuations.remove(sessionKey) ?: run {
|
|
1915
|
+
promise.reject(ErrorType.Failed.toString(), "No pending server update for this session.")
|
|
1916
|
+
return
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
if (error != null) {
|
|
1920
|
+
continuation.resume(Result.failure(Exception(error))) {}
|
|
1921
|
+
} else {
|
|
1922
|
+
continuation.resume(Result.success(Unit)) {}
|
|
1875
1923
|
}
|
|
1924
|
+
promise.resolve(null)
|
|
1876
1925
|
}
|
|
1877
1926
|
|
|
1878
1927
|
private fun buildCheckoutConfiguration(configuration: ReadableMap): Checkout.Configuration {
|
|
@@ -1884,6 +1933,41 @@ class StripeSdkModule(
|
|
|
1884
1933
|
return checkoutConfiguration
|
|
1885
1934
|
}
|
|
1886
1935
|
|
|
1936
|
+
/**
|
|
1937
|
+
* Forwards the checkout's `(checkoutSession, isLoading)` flow to JS as
|
|
1938
|
+
* `checkoutSessionDidChangeState` events. Equivalent to the iOS
|
|
1939
|
+
* `CheckoutDelegate` bridge — every native-side mutation (currency selection,
|
|
1940
|
+
* promo code, refresh, etc.) goes through one canonical channel that
|
|
1941
|
+
* `useCheckout` listens to.
|
|
1942
|
+
*/
|
|
1943
|
+
private fun observeCheckoutState(
|
|
1944
|
+
sessionKey: String,
|
|
1945
|
+
checkout: Checkout,
|
|
1946
|
+
) {
|
|
1947
|
+
checkoutStateObservers[sessionKey]?.cancel()
|
|
1948
|
+
|
|
1949
|
+
val job = CoroutineScope(Dispatchers.Main).launch {
|
|
1950
|
+
// `combine` re-emits on every upstream tick. `distinctUntilChanged`
|
|
1951
|
+
// collapses runs where neither field actually moved — both flows are
|
|
1952
|
+
// StateFlows so we'd otherwise see a redundant initial replay when
|
|
1953
|
+
// wiring up.
|
|
1954
|
+
combine(
|
|
1955
|
+
checkout.checkoutSession,
|
|
1956
|
+
checkout.isLoading,
|
|
1957
|
+
) { session, isLoading -> session to isLoading }
|
|
1958
|
+
.distinctUntilChanged()
|
|
1959
|
+
.collect {
|
|
1960
|
+
eventEmitter.emitCheckoutSessionDidChangeState(
|
|
1961
|
+
Arguments.createMap().apply {
|
|
1962
|
+
putString("sessionKey", sessionKey)
|
|
1963
|
+
putMap("state", mapFromCheckoutState(checkout))
|
|
1964
|
+
},
|
|
1965
|
+
)
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
checkoutStateObservers[sessionKey] = job
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1887
1971
|
private fun performCheckoutMutation(
|
|
1888
1972
|
sessionKey: String,
|
|
1889
1973
|
promise: Promise,
|
|
@@ -1925,14 +2009,14 @@ class StripeSdkModule(
|
|
|
1925
2009
|
private var isRecreatingReactActivity = false
|
|
1926
2010
|
private var isAuthWebViewActive = false
|
|
1927
2011
|
private val activityLifecycleCallbacks =
|
|
1928
|
-
object :
|
|
2012
|
+
object : DefaultActivityLifecycleCallbacks() {
|
|
1929
2013
|
override fun onActivityCreated(
|
|
1930
2014
|
activity: Activity,
|
|
1931
|
-
|
|
2015
|
+
savedInstanceState: Bundle?,
|
|
1932
2016
|
) {
|
|
1933
|
-
// Only set flag when ReactActivity is actually being recreated (
|
|
1934
|
-
//
|
|
1935
|
-
if (activity is ReactActivity &&
|
|
2017
|
+
// Only set flag when ReactActivity is actually being recreated (savedInstanceState != null)
|
|
2018
|
+
// savedInstanceState != null means this is a recreation, not first creation
|
|
2019
|
+
if (activity is ReactActivity && savedInstanceState != null) {
|
|
1936
2020
|
isRecreatingReactActivity = true
|
|
1937
2021
|
}
|
|
1938
2022
|
|
|
@@ -1951,21 +2035,6 @@ class StripeSdkModule(
|
|
|
1951
2035
|
}
|
|
1952
2036
|
}
|
|
1953
2037
|
}
|
|
1954
|
-
|
|
1955
|
-
override fun onActivityStarted(activity: Activity) {}
|
|
1956
|
-
|
|
1957
|
-
override fun onActivityResumed(activity: Activity) {}
|
|
1958
|
-
|
|
1959
|
-
override fun onActivityPaused(activity: Activity) {}
|
|
1960
|
-
|
|
1961
|
-
override fun onActivityStopped(activity: Activity) {}
|
|
1962
|
-
|
|
1963
|
-
override fun onActivitySaveInstanceState(
|
|
1964
|
-
activity: Activity,
|
|
1965
|
-
bundle: Bundle,
|
|
1966
|
-
) {}
|
|
1967
|
-
|
|
1968
|
-
override fun onActivityDestroyed(activity: Activity) {}
|
|
1969
2038
|
}
|
|
1970
2039
|
|
|
1971
2040
|
/**
|
|
@@ -2006,7 +2075,7 @@ class StripeSdkModule(
|
|
|
2006
2075
|
// Timeout for auth webview fallback (if JavaScript doesn't call authWebViewDeepLinkHandled)
|
|
2007
2076
|
private const val AUTH_WEBVIEW_FALLBACK_TIMEOUT_MS = 60_000L
|
|
2008
2077
|
|
|
2009
|
-
private const val FILE_CLEANUP_DELAY_MS =
|
|
2078
|
+
private const val FILE_CLEANUP_DELAY_MS = 3_000L
|
|
2010
2079
|
|
|
2011
2080
|
// SDK-managed storage for pending stripe-connect:// URLs
|
|
2012
2081
|
// This is static because deep links can arrive before ReactContext is available
|