@stripe/stripe-react-native 0.59.0 → 0.59.2

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.
Files changed (96) hide show
  1. package/README.md +5 -2
  2. package/android/gradle.properties +1 -1
  3. package/android/src/main/AndroidManifest.xml +2 -2
  4. package/android/src/main/java/com/reactnativestripesdk/FakeOnrampSdkModule.kt +0 -5
  5. package/android/src/main/java/com/reactnativestripesdk/PaymentElementConfig.kt +17 -5
  6. package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementConfig.kt +1 -1
  7. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +1 -8
  8. package/android/src/main/java/com/reactnativestripesdk/StripeFileProvider.kt +17 -0
  9. package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +1 -1
  10. package/android/src/oldarch/java/com/reactnativestripesdk/NativeOnrampSdkModuleSpec.java +0 -4
  11. package/android/src/onramp/java/com/reactnativestripesdk/OnrampMappers.kt +103 -0
  12. package/android/src/onramp/java/com/reactnativestripesdk/OnrampSdkModule.kt +97 -187
  13. package/android/src/test/java/com/reactnativestripesdk/PaymentElementConfigTest.kt +52 -17
  14. package/android/src/test/java/com/reactnativestripesdk/PaymentMethodMessagingElementConfigTest.kt +3 -3
  15. package/android/src/test/java/com/reactnativestripesdk/mappers/OnrampMappersTest.kt +219 -0
  16. package/ios/PaymentMethodMessagingElementConfig.swift +3 -3
  17. package/ios/StripeOnrampSdk.mm +0 -6
  18. package/ios/StripeSdkImpl+PaymentSheet.swift +20 -7
  19. package/ios/StripeSdkImpl.swift +2 -36
  20. package/jest/setup.js +0 -1
  21. package/lib/commonjs/components/AddToWalletButton.js +1 -1
  22. package/lib/commonjs/components/AddressSheet.js +1 -1
  23. package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
  24. package/lib/commonjs/components/CardField.js +1 -1
  25. package/lib/commonjs/components/CardForm.js +1 -1
  26. package/lib/commonjs/components/PlatformPayButton.js +1 -1
  27. package/lib/commonjs/components/StripeContainer.js +1 -1
  28. package/lib/commonjs/connect/Components.js +1 -1
  29. package/lib/commonjs/connect/ConnectComponentsProvider.js +1 -1
  30. package/lib/commonjs/connect/EmbeddedComponent.js +1 -1
  31. package/lib/commonjs/connect/ModalCloseButton.js +1 -1
  32. package/lib/commonjs/connect/NavigationBar.js +1 -1
  33. package/lib/commonjs/helpers.js +1 -1
  34. package/lib/commonjs/hooks/useOnramp.js +1 -1
  35. package/lib/commonjs/hooks/useOnramp.js.map +1 -1
  36. package/lib/commonjs/specs/NativeAddToWalletButton.js +1 -1
  37. package/lib/commonjs/specs/NativeAddressSheet.js +1 -1
  38. package/lib/commonjs/specs/NativeApplePayButton.js +1 -1
  39. package/lib/commonjs/specs/NativeAuBECSDebitForm.js +1 -1
  40. package/lib/commonjs/specs/NativeCardField.js +1 -1
  41. package/lib/commonjs/specs/NativeCardForm.js +1 -1
  42. package/lib/commonjs/specs/NativeConnectAccountOnboardingView.js +1 -1
  43. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js +1 -1
  44. package/lib/commonjs/specs/NativeGooglePayButton.js +1 -1
  45. package/lib/commonjs/specs/NativeNavigationBar.js +1 -1
  46. package/lib/commonjs/specs/NativeOnrampSdkModule.js.map +1 -1
  47. package/lib/commonjs/specs/NativePaymentMethodMessagingElement.js +1 -1
  48. package/lib/commonjs/specs/NativeStripeContainer.js +1 -1
  49. package/lib/commonjs/types/EmbeddedPaymentElement.js +1 -1
  50. package/lib/commonjs/types/PaymentSheet.js +1 -1
  51. package/lib/commonjs/types/PaymentSheet.js.map +1 -1
  52. package/lib/module/components/AddToWalletButton.js +1 -1
  53. package/lib/module/components/AddressSheet.js +1 -1
  54. package/lib/module/components/AuBECSDebitForm.js +1 -1
  55. package/lib/module/components/CardField.js +1 -1
  56. package/lib/module/components/CardForm.js +1 -1
  57. package/lib/module/components/PlatformPayButton.js +1 -1
  58. package/lib/module/components/StripeContainer.js +1 -1
  59. package/lib/module/connect/Components.js +1 -1
  60. package/lib/module/connect/ConnectComponentsProvider.js +1 -1
  61. package/lib/module/connect/EmbeddedComponent.js +1 -1
  62. package/lib/module/connect/ModalCloseButton.js +1 -1
  63. package/lib/module/connect/NavigationBar.js +1 -1
  64. package/lib/module/helpers.js +1 -1
  65. package/lib/module/hooks/useOnramp.js +1 -1
  66. package/lib/module/hooks/useOnramp.js.map +1 -1
  67. package/lib/module/specs/NativeAddToWalletButton.js +1 -1
  68. package/lib/module/specs/NativeAddressSheet.js +1 -1
  69. package/lib/module/specs/NativeApplePayButton.js +1 -1
  70. package/lib/module/specs/NativeAuBECSDebitForm.js +1 -1
  71. package/lib/module/specs/NativeCardField.js +1 -1
  72. package/lib/module/specs/NativeCardForm.js +1 -1
  73. package/lib/module/specs/NativeConnectAccountOnboardingView.js +1 -1
  74. package/lib/module/specs/NativeEmbeddedPaymentElement.js +1 -1
  75. package/lib/module/specs/NativeGooglePayButton.js +1 -1
  76. package/lib/module/specs/NativeNavigationBar.js +1 -1
  77. package/lib/module/specs/NativeOnrampSdkModule.js.map +1 -1
  78. package/lib/module/specs/NativePaymentMethodMessagingElement.js +1 -1
  79. package/lib/module/specs/NativeStripeContainer.js +1 -1
  80. package/lib/module/types/EmbeddedPaymentElement.js +1 -1
  81. package/lib/module/types/PaymentSheet.js +1 -1
  82. package/lib/module/types/PaymentSheet.js.map +1 -1
  83. package/lib/typescript/src/hooks/useOnramp.d.ts +2 -8
  84. package/lib/typescript/src/hooks/useOnramp.d.ts.map +1 -1
  85. package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts +0 -1
  86. package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts.map +1 -1
  87. package/lib/typescript/src/types/Onramp.d.ts +0 -12
  88. package/lib/typescript/src/types/Onramp.d.ts.map +1 -1
  89. package/lib/typescript/src/types/PaymentSheet.d.ts +22 -0
  90. package/lib/typescript/src/types/PaymentSheet.d.ts.map +1 -1
  91. package/package.json +1 -1
  92. package/src/hooks/useOnramp.tsx +2 -14
  93. package/src/specs/NativeOnrampSdkModule.ts +0 -1
  94. package/src/types/Onramp.ts +0 -15
  95. package/src/types/PaymentSheet.ts +23 -0
  96. package/stripe-react-native.podspec +1 -1
package/README.md CHANGED
@@ -23,7 +23,10 @@ Get started with our [📚 integration guides](https://stripe.com/docs/payments/
23
23
 
24
24
  **Native UI**: We provide native screens and elements to securely collect payment details on Android and iOS.
25
25
 
26
- **PaymentSheet**: [Learn how to integrate](https://stripe.com/docs/payments/accept-a-payment) PaymentSheet, our new pre-built payments UI for mobile apps. PaymentSheet lets you accept cards, Apple Pay, Google Pay, and much more out of the box and also supports saving & reusing payment methods. PaymentSheet currently accepts the following payment methods: Card, Apple Pay, Google Pay, SEPA Debit, Bancontact, Billie, iDEAL, EPS, P24, Afterpay/Clearpay, Klarna, Giropay, and ACH.
26
+ **PaymentSheet**: [Learn how to integrate](https://stripe.com/docs/payments/accept-a-payment) PaymentSheet, our new pre-built payments UI for mobile apps.
27
+ - PaymentSheet lets you accept cards, Apple Pay, Google Pay, and much more out of the box and also supports saving & reusing payment methods.
28
+ - PaymentSheet currently accepts the following payment methods: Card, Apple Pay, Google Pay, SEPA Debit, Bancontact, Billie, iDEAL, EPS, P24, Afterpay/Clearpay, Klarna, Giropay, and ACH.
29
+ - PaymentSheet provides card scanning on iOS and Android, with the Android functionality provided by [Google Payment Card Recognition](https://developers.google.com/pay/payment-card-recognition/debit-credit-card-recognition). To enable card scanning in your app, follow [our guide](https://docs.corp.stripe.com/payments/accept-a-payment?payment-ui=mobile&platform=react-native#react-native-card-scanning).
27
30
 
28
31
  #### Recommended usage
29
32
 
@@ -283,5 +286,5 @@ While installing pods in your iOS project using a Stripe React Native version be
283
286
  UnsupportedModulePropertyParserError: Module NativeStripeSdkModule: TypeScript interfaces extending TurboModule must only contain 'FunctionTypeAnnotation's. Property 'onConfirmHandlerCallback' refers to a 'TSTypeReference'.
284
287
  ```
285
288
 
286
- If possible, update to version 0.52 or above of the Stripe React Native SDK.
289
+ If possible, update to version 0.52 or above of the Stripe React Native SDK.
287
290
  If you are unable to do so, please follow our [guide to apply the fix patch](https://github.com/stripe/stripe-react-native/tree/master/patches).
@@ -3,4 +3,4 @@ StripeSdk_compileSdkVersion=30
3
3
  StripeSdk_targetSdkVersion=28
4
4
  StripeSdk_minSdkVersion=21
5
5
  # Keep StripeSdk_stripeVersion in sync with https://github.com/stripe/stripe-identity-react-native/blob/main/android/gradle.properties
6
- StripeSdk_stripeVersion=22.7.+
6
+ StripeSdk_stripeVersion=22.8.+
@@ -27,8 +27,8 @@
27
27
  </activity>
28
28
 
29
29
  <provider
30
- android:name="androidx.core.content.FileProvider"
31
- android:authorities="${applicationId}.stripe.fileprovider"
30
+ android:name="com.reactnativestripesdk.StripeFileProvider"
31
+ android:authorities="${applicationId}.fileprovider"
32
32
  android:exported="false"
33
33
  android:grantUriPermissions="true">
34
34
  <meta-data
@@ -68,11 +68,6 @@ class FakeOnrampSdkModule(
68
68
  promise?.resolveNotImplemented()
69
69
  }
70
70
 
71
- @ReactMethod
72
- override fun authenticateUser(promise: Promise?) {
73
- promise?.resolveNotImplemented()
74
- }
75
-
76
71
  @ReactMethod
77
72
  override fun verifyIdentity(promise: Promise?) {
78
73
  promise?.resolveNotImplemented()
@@ -2,6 +2,7 @@ package com.reactnativestripesdk
2
2
 
3
3
  import com.facebook.react.bridge.ReadableMap
4
4
  import com.reactnativestripesdk.utils.PaymentSheetException
5
+ import com.reactnativestripesdk.utils.forEachKey
5
6
  import com.reactnativestripesdk.utils.getBooleanOr
6
7
  import com.reactnativestripesdk.utils.getIntOr
7
8
  import com.reactnativestripesdk.utils.getLongOr
@@ -78,12 +79,23 @@ private fun mapStringToLinkDisplay(value: String?): PaymentSheet.LinkConfigurati
78
79
  else -> PaymentSheet.LinkConfiguration.Display.Automatic
79
80
  }
80
81
 
81
- internal fun computeTermsDisplayForUserKey(publishableKey: String): Map<PaymentMethod.Type, PaymentSheet.TermsDisplay> =
82
- if (publishableKey.startsWith("uk_")) {
83
- mapOf(PaymentMethod.Type.Card to PaymentSheet.TermsDisplay.NEVER)
84
- } else {
85
- emptyMap()
82
+ internal fun mapToTermsDisplay(params: ReadableMap?): Map<PaymentMethod.Type, PaymentSheet.TermsDisplay>? {
83
+ val termsDisplayMap = params?.getMap("termsDisplay") ?: return null
84
+ val result = mutableMapOf<PaymentMethod.Type, PaymentSheet.TermsDisplay>()
85
+ termsDisplayMap.forEachKey { code ->
86
+ val paymentMethodType = PaymentMethod.Type.fromCode(code)
87
+ val termsDisplay =
88
+ when (termsDisplayMap.getString(code)) {
89
+ "never" -> PaymentSheet.TermsDisplay.NEVER
90
+ "automatic" -> PaymentSheet.TermsDisplay.AUTOMATIC
91
+ else -> null
92
+ }
93
+ if (paymentMethodType != null && termsDisplay != null) {
94
+ result[paymentMethodType] = termsDisplay
95
+ }
86
96
  }
97
+ return result.ifEmpty { null }
98
+ }
87
99
 
88
100
  private val mapIntToButtonType =
89
101
  mapOf(
@@ -61,7 +61,7 @@ fun parseAppearance(
61
61
  font?.let { appearance.font(font) }
62
62
  val colors = PaymentMethodMessagingElement.Appearance.Colors()
63
63
  textColor?.let { colors.textColor(it) }
64
- linkTextColor?.let { colors.infoIconColor(linkTextColor) }
64
+ linkTextColor?.let { colors.linkTextColor(linkTextColor) }
65
65
  appearance.colors(colors)
66
66
 
67
67
  return appearance
@@ -39,7 +39,6 @@ import com.reactnativestripesdk.utils.mapFromPaymentMethod
39
39
  import com.reactnativestripesdk.utils.mapToPreferredNetworks
40
40
  import com.reactnativestripesdk.utils.parseCustomPaymentMethods
41
41
  import com.stripe.android.ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi
42
- import com.stripe.android.PaymentConfiguration
43
42
  import com.stripe.android.core.reactnative.ReactNativeSdkInternal
44
43
  import com.stripe.android.model.PaymentMethod
45
44
  import com.stripe.android.paymentelement.ConfirmCustomPaymentMethodCallback
@@ -299,13 +298,7 @@ class PaymentSheetManager(
299
298
  mapToPaymentMethodLayout(arguments.getString("paymentMethodLayout")),
300
299
  )
301
300
 
302
- val userKeyTermsDisplay =
303
- computeTermsDisplayForUserKey(
304
- PaymentConfiguration.getInstance(context).publishableKey,
305
- )
306
- if (userKeyTermsDisplay.isNotEmpty()) {
307
- configurationBuilder.termsDisplay(userKeyTermsDisplay)
308
- }
301
+ mapToTermsDisplay(arguments)?.let { configurationBuilder.termsDisplay(it) }
309
302
 
310
303
  paymentSheetConfiguration = configurationBuilder.build()
311
304
 
@@ -0,0 +1,17 @@
1
+ package com.reactnativestripesdk
2
+
3
+ /**
4
+ * A custom FileProvider subclass for the Stripe React Native SDK.
5
+ *
6
+ * This class extends androidx.core.content.FileProvider to provide a unique
7
+ * android:name in the manifest, preventing conflicts with other libraries that
8
+ * also use FileProvider (e.g., react-native-document-viewer, expo-file-system).
9
+ *
10
+ * By using a library-specific class name, the Android manifest merger keeps
11
+ * this provider separate from other FileProviders, avoiding the
12
+ * "Attribute provider@authorities value=... is also present at..." error.
13
+ *
14
+ * No additional implementation is needed - this simply inherits all functionality
15
+ * from the parent FileProvider class.
16
+ */
17
+ class StripeFileProvider : androidx.core.content.FileProvider()
@@ -1518,7 +1518,7 @@ class StripeSdkModule(
1518
1518
  val uri =
1519
1519
  androidx.core.content.FileProvider.getUriForFile(
1520
1520
  reactApplicationContext,
1521
- "${reactApplicationContext.packageName}.stripe.fileprovider",
1521
+ "${reactApplicationContext.packageName}.fileprovider",
1522
1522
  file,
1523
1523
  )
1524
1524
 
@@ -72,10 +72,6 @@ public abstract class NativeOnrampSdkModuleSpec extends ReactContextBaseJavaModu
72
72
  @DoNotStrip
73
73
  public abstract void updatePhoneNumber(String phone, Promise promise);
74
74
 
75
- @ReactMethod
76
- @DoNotStrip
77
- public abstract void authenticateUser(Promise promise);
78
-
79
75
  @ReactMethod
80
76
  @DoNotStrip
81
77
  public abstract void verifyIdentity(Promise promise);
@@ -0,0 +1,103 @@
1
+ package com.reactnativestripesdk
2
+
3
+ import android.annotation.SuppressLint
4
+ import androidx.compose.ui.graphics.Color
5
+ import com.facebook.react.bridge.ReadableMap
6
+ import com.stripe.android.crypto.onramp.model.OnrampConfiguration
7
+ import com.stripe.android.link.LinkAppearance
8
+ import com.stripe.android.link.LinkAppearance.Colors
9
+ import com.stripe.android.link.LinkAppearance.PrimaryButton
10
+ import com.stripe.android.link.LinkAppearance.Style
11
+
12
+ @SuppressLint("RestrictedApi")
13
+ internal fun mapConfig(
14
+ configMap: ReadableMap,
15
+ publishableKey: String,
16
+ ): OnrampConfiguration {
17
+ val appearanceMap = configMap.getMap("appearance")
18
+ val appearance =
19
+ if (appearanceMap != null) {
20
+ mapAppearance(appearanceMap)
21
+ } else {
22
+ LinkAppearance()
23
+ .style(Style.AUTOMATIC)
24
+ }
25
+
26
+ val displayName = configMap.getString("merchantDisplayName") ?: ""
27
+ val cryptoCustomerId = configMap.getString("cryptoCustomerId")
28
+
29
+ return OnrampConfiguration()
30
+ .merchantDisplayName(displayName)
31
+ .publishableKey(publishableKey)
32
+ .appearance(appearance)
33
+ .cryptoCustomerId(cryptoCustomerId)
34
+ }
35
+
36
+ @SuppressLint("RestrictedApi")
37
+ internal fun mapAppearance(appearanceMap: ReadableMap): LinkAppearance {
38
+ val lightColorsMap = appearanceMap.getMap("lightColors")
39
+ val darkColorsMap = appearanceMap.getMap("darkColors")
40
+ val styleStr = appearanceMap.getString("style")
41
+ val primaryButtonMap = appearanceMap.getMap("primaryButton")
42
+
43
+ val lightColors =
44
+ if (lightColorsMap != null) {
45
+ val primaryColorStr = lightColorsMap.getString("primary")
46
+ val contentColorStr = lightColorsMap.getString("contentOnPrimary")
47
+ val borderSelectedColorStr = lightColorsMap.getString("borderSelected")
48
+
49
+ Colors()
50
+ .primary(Color(android.graphics.Color.parseColor(primaryColorStr)))
51
+ .contentOnPrimary(Color(android.graphics.Color.parseColor(contentColorStr)))
52
+ .borderSelected(Color(android.graphics.Color.parseColor(borderSelectedColorStr)))
53
+ } else {
54
+ Colors()
55
+ }
56
+
57
+ val darkColors =
58
+ if (darkColorsMap != null) {
59
+ val primaryColorStr = darkColorsMap.getString("primary")
60
+ val contentColorStr = darkColorsMap.getString("contentOnPrimary")
61
+ val borderSelectedColorStr = darkColorsMap.getString("borderSelected")
62
+
63
+ Colors()
64
+ .primary(Color(android.graphics.Color.parseColor(primaryColorStr)))
65
+ .contentOnPrimary(Color(android.graphics.Color.parseColor(contentColorStr)))
66
+ .borderSelected(Color(android.graphics.Color.parseColor(borderSelectedColorStr)))
67
+ } else {
68
+ Colors()
69
+ }
70
+
71
+ val style =
72
+ when (styleStr) {
73
+ "ALWAYS_LIGHT" -> Style.ALWAYS_LIGHT
74
+ "ALWAYS_DARK" -> Style.ALWAYS_DARK
75
+ else -> Style.AUTOMATIC
76
+ }
77
+
78
+ val primaryButton =
79
+ if (primaryButtonMap != null) {
80
+ PrimaryButton()
81
+ .cornerRadiusDp(
82
+ if (primaryButtonMap.hasKey("cornerRadius")) {
83
+ primaryButtonMap.getDouble("cornerRadius").toFloat()
84
+ } else {
85
+ null
86
+ },
87
+ ).heightDp(
88
+ if (primaryButtonMap.hasKey("height")) {
89
+ primaryButtonMap.getDouble("height").toFloat()
90
+ } else {
91
+ null
92
+ },
93
+ )
94
+ } else {
95
+ PrimaryButton()
96
+ }
97
+
98
+ return LinkAppearance()
99
+ .lightColors(lightColors)
100
+ .darkColors(darkColors)
101
+ .style(style)
102
+ .primaryButton(primaryButton)
103
+ }
@@ -3,8 +3,6 @@ package com.reactnativestripesdk
3
3
  import android.annotation.SuppressLint
4
4
  import android.app.Application
5
5
  import androidx.activity.ComponentActivity
6
- import androidx.compose.ui.graphics.Color
7
- import androidx.core.content.ContextCompat
8
6
  import androidx.fragment.app.FragmentActivity
9
7
  import androidx.lifecycle.SavedStateHandle
10
8
  import com.facebook.react.bridge.Arguments
@@ -30,12 +28,10 @@ import com.stripe.android.crypto.onramp.model.CryptoNetwork
30
28
  import com.stripe.android.crypto.onramp.model.KycInfo
31
29
  import com.stripe.android.crypto.onramp.model.LinkUserInfo
32
30
  import com.stripe.android.crypto.onramp.model.OnrampAttachKycInfoResult
33
- import com.stripe.android.crypto.onramp.model.OnrampAuthenticateResult
34
31
  import com.stripe.android.crypto.onramp.model.OnrampAuthorizeResult
35
32
  import com.stripe.android.crypto.onramp.model.OnrampCallbacks
36
33
  import com.stripe.android.crypto.onramp.model.OnrampCheckoutResult
37
34
  import com.stripe.android.crypto.onramp.model.OnrampCollectPaymentMethodResult
38
- import com.stripe.android.crypto.onramp.model.OnrampConfiguration
39
35
  import com.stripe.android.crypto.onramp.model.OnrampConfigurationResult
40
36
  import com.stripe.android.crypto.onramp.model.OnrampCreateCryptoPaymentTokenResult
41
37
  import com.stripe.android.crypto.onramp.model.OnrampHasLinkAccountResult
@@ -47,10 +43,6 @@ import com.stripe.android.crypto.onramp.model.OnrampUpdatePhoneNumberResult
47
43
  import com.stripe.android.crypto.onramp.model.OnrampVerifyIdentityResult
48
44
  import com.stripe.android.crypto.onramp.model.OnrampVerifyKycInfoResult
49
45
  import com.stripe.android.crypto.onramp.model.PaymentMethodType
50
- import com.stripe.android.link.LinkAppearance
51
- import com.stripe.android.link.LinkAppearance.Colors
52
- import com.stripe.android.link.LinkAppearance.PrimaryButton
53
- import com.stripe.android.link.LinkAppearance.Style
54
46
  import com.stripe.android.link.LinkController.PaymentMethodPreview
55
47
  import com.stripe.android.link.PaymentMethodPreviewDetails
56
48
  import com.stripe.android.model.CardBrand
@@ -59,8 +51,11 @@ import com.stripe.android.paymentsheet.PaymentSheet
59
51
  import kotlinx.coroutines.CompletableDeferred
60
52
  import kotlinx.coroutines.CoroutineScope
61
53
  import kotlinx.coroutines.Dispatchers
54
+ import kotlinx.coroutines.SupervisorJob
55
+ import kotlinx.coroutines.cancel
62
56
  import kotlinx.coroutines.launch
63
57
  import kotlinx.coroutines.withContext
58
+ import kotlinx.coroutines.withTimeout
64
59
 
65
60
  @SuppressLint("RestrictedApi")
66
61
  @ReactModule(name = NativeOnrampSdkModuleSpec.NAME)
@@ -81,6 +76,7 @@ class OnrampSdkModule(
81
76
  private var verifyKycPromise: Promise? = null
82
77
 
83
78
  private var checkoutClientSecretDeferred: CompletableDeferred<String>? = null
79
+ private val rnScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
84
80
 
85
81
  @ReactMethod
86
82
  override fun initialise(
@@ -95,6 +91,11 @@ class OnrampSdkModule(
95
91
  promise.resolve(null)
96
92
  }
97
93
 
94
+ override fun invalidate() {
95
+ super.invalidate()
96
+ rnScope.cancel()
97
+ }
98
+
98
99
  /**
99
100
  * Safely get and cast the current activity as an AppCompatActivity. If that fails, the promise
100
101
  * provided will be resolved with an error message instructing the user to retry the method.
@@ -124,33 +125,37 @@ class OnrampSdkModule(
124
125
  return
125
126
  }
126
127
 
128
+ val onrampCallbacks =
129
+ OnrampCallbacks()
130
+ .verifyIdentityCallback { result ->
131
+ handleOnrampIdentityVerificationResult(result, identityVerificationPromise!!)
132
+ }.collectPaymentCallback { result ->
133
+ handleOnrampCollectPaymentResult(result, collectPaymentPromise!!)
134
+ }.authorizeCallback { result ->
135
+ handleOnrampAuthorizationResult(result, authorizePromise!!)
136
+ }.checkoutCallback { result ->
137
+ handleOnrampCheckoutResult(result, checkoutPromise!!)
138
+ }.verifyKycCallback { result ->
139
+ handleOnrampKycVerificationResult(result, verifyKycPromise!!)
140
+ }.onrampSessionClientSecretProvider { sessionId ->
141
+ checkoutClientSecretDeferred = CompletableDeferred()
142
+
143
+ val params = Arguments.createMap()
144
+ params.putString("onrampSessionId", sessionId)
145
+
146
+ emitOnCheckoutClientSecretRequested(params)
147
+
148
+ checkoutClientSecretDeferred!!.await()
149
+ }
150
+
127
151
  val coordinator =
128
152
  onrampCoordinator ?: OnrampCoordinator
129
153
  .Builder()
130
- .build(application, SavedStateHandle())
154
+ .build(application, SavedStateHandle(), onrampCallbacks)
131
155
  .also { this.onrampCoordinator = it }
132
156
 
133
157
  CoroutineScope(Dispatchers.IO).launch {
134
- val appearanceMap = config.getMap("appearance")
135
- val appearance =
136
- if (appearanceMap != null) {
137
- mapAppearance(appearanceMap)
138
- } else {
139
- LinkAppearance(style = Style.AUTOMATIC)
140
- }
141
-
142
- val displayName = config.getString("merchantDisplayName") ?: ""
143
-
144
- val cryptoCustomerId = config.getString("cryptoCustomerId")
145
-
146
- val configuration =
147
- OnrampConfiguration(
148
- merchantDisplayName = displayName,
149
- publishableKey = publishableKey,
150
- appearance = appearance,
151
- cryptoCustomerId = cryptoCustomerId,
152
- )
153
-
158
+ val configuration = mapConfig(config, publishableKey)
154
159
  val configureResult = coordinator.configure(configuration)
155
160
 
156
161
  CoroutineScope(Dispatchers.Main).launch {
@@ -182,30 +187,8 @@ class OnrampSdkModule(
182
187
  return
183
188
  }
184
189
 
185
- val onrampCallbacks =
186
- OnrampCallbacks(
187
- authenticateUserCallback = { result ->
188
- handleOnrampAuthenticationResult(result, authenticateUserPromise!!)
189
- },
190
- verifyIdentityCallback = { result ->
191
- handleOnrampIdentityVerificationResult(result, identityVerificationPromise!!)
192
- },
193
- collectPaymentCallback = { result ->
194
- handleOnrampCollectPaymentResult(result, collectPaymentPromise!!)
195
- },
196
- authorizeCallback = { result ->
197
- handleOnrampAuthorizationResult(result, authorizePromise!!)
198
- },
199
- checkoutCallback = { result ->
200
- handleOnrampCheckoutResult(result, checkoutPromise!!)
201
- },
202
- verifyKycCallback = { result ->
203
- handleOnrampKycVerificationResult(result, verifyKycPromise!!)
204
- },
205
- )
206
-
207
190
  try {
208
- onrampPresenter = onrampCoordinator!!.createPresenter(activity, onrampCallbacks)
191
+ onrampPresenter = onrampCoordinator!!.createPresenter(activity)
209
192
  promise.resolveVoid()
210
193
  } catch (e: Exception) {
211
194
  promise.resolve(createFailedError(e))
@@ -394,7 +377,7 @@ class OnrampSdkModule(
394
377
  }
395
378
  CoroutineScope(Dispatchers.IO).launch {
396
379
  when (val result = coordinator.updatePhoneNumber(phone)) {
397
- OnrampUpdatePhoneNumberResult.Completed -> {
380
+ is OnrampUpdatePhoneNumberResult.Completed -> {
398
381
  promise.resolveVoid()
399
382
  }
400
383
  is OnrampUpdatePhoneNumberResult.Failed -> {
@@ -404,19 +387,6 @@ class OnrampSdkModule(
404
387
  }
405
388
  }
406
389
 
407
- @ReactMethod
408
- override fun authenticateUser(promise: Promise) {
409
- val presenter =
410
- onrampPresenter ?: run {
411
- promise.resolve(createOnrampNotConfiguredError())
412
- return
413
- }
414
-
415
- authenticateUserPromise = promise
416
-
417
- presenter.authenticateUser()
418
- }
419
-
420
390
  @ReactMethod
421
391
  override fun verifyIdentity(promise: Promise) {
422
392
  val presenter =
@@ -505,20 +475,9 @@ class OnrampSdkModule(
505
475
  return
506
476
  }
507
477
 
508
- val checkoutHandler: suspend () -> String = {
509
- checkoutClientSecretDeferred = CompletableDeferred()
510
-
511
- val params = Arguments.createMap()
512
- params.putString("onrampSessionId", onrampSessionId)
513
-
514
- emitOnCheckoutClientSecretRequested(params)
515
-
516
- checkoutClientSecretDeferred!!.await()
517
- }
518
-
519
478
  checkoutPromise = promise
520
479
 
521
- presenter.performCheckout(onrampSessionId, checkoutHandler)
480
+ presenter.performCheckout(onrampSessionId)
522
481
  }
523
482
 
524
483
  @ReactMethod
@@ -579,6 +538,7 @@ class OnrampSdkModule(
579
538
  null
580
539
  }
581
540
  }
541
+
582
542
  token.hasKey("us_bank_account") -> {
583
543
  val bankMap = token.getMap("us_bank_account")
584
544
  if (bankMap != null) {
@@ -597,6 +557,7 @@ class OnrampSdkModule(
597
557
  null
598
558
  }
599
559
  }
560
+
600
561
  else -> null
601
562
  }
602
563
 
@@ -608,19 +569,35 @@ class OnrampSdkModule(
608
569
  )
609
570
  return
610
571
  }
572
+ rnScope.launch {
573
+ val iconDataUri: String =
574
+ try {
575
+ val base64 =
576
+ withContext(Dispatchers.Default) {
577
+ val drawable =
578
+ withTimeout(5_000L) {
579
+ withContext(Dispatchers.IO) {
580
+ paymentDetails.imageLoader()
581
+ }
582
+ }
583
+
584
+ getBitmapFromDrawable(drawable)?.let { bitmap ->
585
+ getBase64FromBitmap(bitmap)
586
+ } ?: ""
587
+ }
588
+
589
+ if (base64.isNotEmpty()) "data:image/png;base64,$base64" else ""
590
+ } catch (_: Exception) {
591
+ ""
592
+ }
611
593
 
612
- val icon =
613
- reactApplicationContext.currentActivity
614
- ?.let { ContextCompat.getDrawable(it, paymentDetails.iconRes) }
615
- ?.let { "data:image/png;base64," + getBase64FromBitmap(getBitmapFromDrawable(it)) }
616
-
617
- val displayData = Arguments.createMap()
618
-
619
- displayData.putString("icon", icon)
620
- displayData.putString("label", paymentDetails.label)
621
- displayData.putString("sublabel", paymentDetails.sublabel)
594
+ val displayData = Arguments.createMap()
595
+ displayData.putString("icon", iconDataUri)
596
+ displayData.putString("label", paymentDetails.label)
597
+ displayData.putString("sublabel", paymentDetails.sublabel)
622
598
 
623
- promise.resolve(createResult("displayData", displayData))
599
+ promise.resolve(createResult("displayData", displayData))
600
+ }
624
601
  }
625
602
 
626
603
  @ReactMethod
@@ -659,95 +636,6 @@ class OnrampSdkModule(
659
636
  }
660
637
  }
661
638
 
662
- private fun mapAppearance(appearanceMap: ReadableMap): LinkAppearance {
663
- val lightColorsMap = appearanceMap.getMap("lightColors")
664
- val darkColorsMap = appearanceMap.getMap("darkColors")
665
- val styleStr = appearanceMap.getString("style")
666
- val primaryButtonMap = appearanceMap.getMap("primaryButton")
667
-
668
- val lightColors =
669
- if (lightColorsMap != null) {
670
- val primaryColorStr = lightColorsMap.getString("primary")
671
- val contentColorStr = lightColorsMap.getString("contentOnPrimary")
672
- val borderSelectedColorStr = lightColorsMap.getString("borderSelected")
673
-
674
- Colors(
675
- primary = Color(android.graphics.Color.parseColor(primaryColorStr)),
676
- contentOnPrimary = Color(android.graphics.Color.parseColor(contentColorStr)),
677
- borderSelected = Color(android.graphics.Color.parseColor(borderSelectedColorStr)),
678
- )
679
- } else {
680
- null
681
- }
682
-
683
- val darkColors =
684
- if (darkColorsMap != null) {
685
- val primaryColorStr = darkColorsMap.getString("primary")
686
- val contentColorStr = darkColorsMap.getString("contentOnPrimary")
687
- val borderSelectedColorStr = darkColorsMap.getString("borderSelected")
688
-
689
- Colors(
690
- primary = Color(android.graphics.Color.parseColor(primaryColorStr)),
691
- contentOnPrimary = Color(android.graphics.Color.parseColor(contentColorStr)),
692
- borderSelected = Color(android.graphics.Color.parseColor(borderSelectedColorStr)),
693
- )
694
- } else {
695
- null
696
- }
697
-
698
- val style =
699
- when (styleStr) {
700
- "ALWAYS_LIGHT" -> Style.ALWAYS_LIGHT
701
- "ALWAYS_DARK" -> Style.ALWAYS_DARK
702
- else -> Style.AUTOMATIC
703
- }
704
-
705
- val primaryButton =
706
- if (primaryButtonMap != null) {
707
- PrimaryButton(
708
- cornerRadiusDp =
709
- if (primaryButtonMap.hasKey("cornerRadius")) {
710
- primaryButtonMap.getDouble("cornerRadius").toFloat()
711
- } else {
712
- null
713
- },
714
- heightDp =
715
- if (primaryButtonMap.hasKey("height")) {
716
- primaryButtonMap.getDouble("height").toFloat()
717
- } else {
718
- null
719
- },
720
- )
721
- } else {
722
- null
723
- }
724
-
725
- val default = LinkAppearance(style = Style.AUTOMATIC)
726
- return LinkAppearance(
727
- lightColors = lightColors ?: default.lightColors,
728
- darkColors = darkColors ?: default.darkColors,
729
- style = style,
730
- primaryButton = primaryButton ?: default.primaryButton,
731
- )
732
- }
733
-
734
- private fun handleOnrampAuthenticationResult(
735
- result: OnrampAuthenticateResult,
736
- promise: Promise,
737
- ) {
738
- when (result) {
739
- is OnrampAuthenticateResult.Completed -> {
740
- promise.resolveString("customerId", result.customerId)
741
- }
742
- is OnrampAuthenticateResult.Cancelled -> {
743
- promise.resolve(createCanceledError("Authentication was cancelled"))
744
- }
745
- is OnrampAuthenticateResult.Failed -> {
746
- promise.resolve(createFailedError(result.error))
747
- }
748
- }
749
- }
750
-
751
639
  private fun handleOnrampIdentityVerificationResult(
752
640
  result: OnrampVerifyIdentityResult,
753
641
  promise: Promise,
@@ -795,19 +683,41 @@ class OnrampSdkModule(
795
683
  ) {
796
684
  when (result) {
797
685
  is OnrampCollectPaymentMethodResult.Completed -> {
798
- val displayData = Arguments.createMap()
799
- val icon =
800
- reactApplicationContext.currentActivity
801
- ?.let { ContextCompat.getDrawable(it, result.displayData.iconRes) }
802
- ?.let { "data:image/png;base64," + getBase64FromBitmap(getBitmapFromDrawable(it)) }
803
- displayData.putString("icon", icon)
804
- displayData.putString("label", result.displayData.label)
805
- result.displayData.sublabel?.let { displayData.putString("sublabel", it) }
806
- promise.resolve(createResult("displayData", displayData))
686
+ rnScope.launch {
687
+ val iconDataUri =
688
+ try {
689
+ val base64 =
690
+ withContext(Dispatchers.Default) {
691
+ val drawable =
692
+ withTimeout(5_000L) {
693
+ withContext(Dispatchers.IO) {
694
+ result.displayData.imageLoader()
695
+ }
696
+ }
697
+
698
+ getBitmapFromDrawable(drawable)?.let { bitmap ->
699
+ getBase64FromBitmap(bitmap)
700
+ } ?: ""
701
+ }
702
+
703
+ if (base64.isNotEmpty()) "data:image/png;base64,$base64" else ""
704
+ } catch (_: Exception) {
705
+ ""
706
+ }
707
+
708
+ val displayData = Arguments.createMap()
709
+ displayData.putString("icon", iconDataUri)
710
+ displayData.putString("label", result.displayData.label)
711
+ result.displayData.sublabel?.let { displayData.putString("sublabel", it) }
712
+
713
+ promise.resolve(createResult("displayData", displayData))
714
+ }
807
715
  }
716
+
808
717
  is OnrampCollectPaymentMethodResult.Cancelled -> {
809
718
  promise.resolve(createCanceledError("Payment collection was cancelled"))
810
719
  }
720
+
811
721
  is OnrampCollectPaymentMethodResult.Failed -> {
812
722
  promise.resolve(createFailedError(result.error))
813
723
  }