@stripe/stripe-react-native 0.69.0 → 0.70.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.
Files changed (149) hide show
  1. package/android/.idea/AndroidProjectSystem.xml +6 -0
  2. package/android/.idea/caches/deviceStreaming.xml +835 -0
  3. package/android/.idea/compiler.xml +6 -0
  4. package/android/.idea/gradle.xml +17 -0
  5. package/android/.idea/migrations.xml +10 -0
  6. package/android/.idea/misc.xml +10 -0
  7. package/android/.idea/runConfigurations.xml +17 -0
  8. package/android/.idea/vcs.xml +6 -0
  9. package/android/local.properties +8 -0
  10. package/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt +7 -8
  11. package/android/src/oldarch/java/com/reactnativestripesdk/NativeOnrampSdkModuleSpec.java +8 -0
  12. package/android/src/onramp/java/com/reactnativestripesdk/OnrampErrors.kt +22 -8
  13. package/android/src/onramp/java/com/reactnativestripesdk/OnrampMappers.kt +48 -0
  14. package/android/src/onramp/java/com/reactnativestripesdk/OnrampSdkModule.kt +62 -0
  15. package/android/src/test/java/com/reactnativestripesdk/OnrampErrorsTest.kt +103 -26
  16. package/android/src/test/java/com/reactnativestripesdk/mappers/OnrampMappersTest.kt +37 -0
  17. package/android/src/test/java/com/reactnativestripesdk/mappers/PaymentMethodMappersTest.kt +110 -0
  18. package/ios/Mappers.swift +19 -0
  19. package/ios/OnrampErrors.swift +23 -8
  20. package/ios/StripeOnrampSdk.mm +16 -0
  21. package/ios/StripeSdkImpl.swift +56 -0
  22. package/jest/setup.js +2 -0
  23. package/lib/commonjs/components/AddToWalletButton.js +1 -1
  24. package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
  25. package/lib/commonjs/components/AddressSheet.js +1 -1
  26. package/lib/commonjs/components/AddressSheet.js.map +1 -1
  27. package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
  28. package/lib/commonjs/components/AuBECSDebitForm.js.map +1 -1
  29. package/lib/commonjs/components/CardField.js +1 -1
  30. package/lib/commonjs/components/CardField.js.map +1 -1
  31. package/lib/commonjs/components/CardForm.js +1 -1
  32. package/lib/commonjs/components/CardForm.js.map +1 -1
  33. package/lib/commonjs/components/CurrencySelectorElement.js +1 -1
  34. package/lib/commonjs/components/CurrencySelectorElement.js.map +1 -1
  35. package/lib/commonjs/components/PaymentMethodMessagingElement.js +1 -1
  36. package/lib/commonjs/components/PaymentMethodMessagingElement.js.map +1 -1
  37. package/lib/commonjs/components/PlatformPayButton.js +1 -1
  38. package/lib/commonjs/components/PlatformPayButton.js.map +1 -1
  39. package/lib/commonjs/components/StripeContainer.js +1 -1
  40. package/lib/commonjs/components/StripeContainer.js.map +1 -1
  41. package/lib/commonjs/components/StripeProvider.js +1 -1
  42. package/lib/commonjs/components/StripeProvider.js.map +1 -1
  43. package/lib/commonjs/connect/Components.js +1 -1
  44. package/lib/commonjs/connect/Components.js.map +1 -1
  45. package/lib/commonjs/connect/ConnectComponentsProvider.js +1 -1
  46. package/lib/commonjs/connect/ConnectComponentsProvider.js.map +1 -1
  47. package/lib/commonjs/connect/EmbeddedComponent.js +1 -1
  48. package/lib/commonjs/connect/EmbeddedComponent.js.map +1 -1
  49. package/lib/commonjs/connect/ModalCloseButton.js +1 -1
  50. package/lib/commonjs/connect/ModalCloseButton.js.map +1 -1
  51. package/lib/commonjs/connect/NavigationBar.js +1 -1
  52. package/lib/commonjs/connect/NavigationBar.js.map +1 -1
  53. package/lib/commonjs/helpers.js +1 -1
  54. package/lib/commonjs/hooks/useOnramp.js +1 -1
  55. package/lib/commonjs/hooks/useOnramp.js.map +1 -1
  56. package/lib/commonjs/specs/NativeAddToWalletButton.js +1 -1
  57. package/lib/commonjs/specs/NativeAddressSheet.js +1 -1
  58. package/lib/commonjs/specs/NativeApplePayButton.js +1 -1
  59. package/lib/commonjs/specs/NativeAuBECSDebitForm.js +1 -1
  60. package/lib/commonjs/specs/NativeCardField.js +1 -1
  61. package/lib/commonjs/specs/NativeCardField.js.map +1 -1
  62. package/lib/commonjs/specs/NativeCardForm.js +1 -1
  63. package/lib/commonjs/specs/NativeCardForm.js.map +1 -1
  64. package/lib/commonjs/specs/NativeConnectAccountOnboardingView.js +1 -1
  65. package/lib/commonjs/specs/NativeCurrencySelectorElement.js +1 -1
  66. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js +1 -1
  67. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js.map +1 -1
  68. package/lib/commonjs/specs/NativeGooglePayButton.js +1 -1
  69. package/lib/commonjs/specs/NativeNavigationBar.js +1 -1
  70. package/lib/commonjs/specs/NativeOnrampSdkModule.js.map +1 -1
  71. package/lib/commonjs/specs/NativePaymentMethodMessagingElement.js +1 -1
  72. package/lib/commonjs/specs/NativeStripeContainer.js +1 -1
  73. package/lib/commonjs/types/EmbeddedPaymentElement.js +1 -1
  74. package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
  75. package/lib/commonjs/types/Onramp.js +1 -1
  76. package/lib/commonjs/types/Onramp.js.map +1 -1
  77. package/lib/module/components/AddToWalletButton.js +1 -1
  78. package/lib/module/components/AddToWalletButton.js.map +1 -1
  79. package/lib/module/components/AddressSheet.js +1 -1
  80. package/lib/module/components/AddressSheet.js.map +1 -1
  81. package/lib/module/components/AuBECSDebitForm.js +1 -1
  82. package/lib/module/components/AuBECSDebitForm.js.map +1 -1
  83. package/lib/module/components/CardField.js +1 -1
  84. package/lib/module/components/CardField.js.map +1 -1
  85. package/lib/module/components/CardForm.js +1 -1
  86. package/lib/module/components/CardForm.js.map +1 -1
  87. package/lib/module/components/CurrencySelectorElement.js +1 -1
  88. package/lib/module/components/CurrencySelectorElement.js.map +1 -1
  89. package/lib/module/components/PaymentMethodMessagingElement.js +1 -1
  90. package/lib/module/components/PaymentMethodMessagingElement.js.map +1 -1
  91. package/lib/module/components/PlatformPayButton.js +1 -1
  92. package/lib/module/components/PlatformPayButton.js.map +1 -1
  93. package/lib/module/components/StripeContainer.js +1 -1
  94. package/lib/module/components/StripeContainer.js.map +1 -1
  95. package/lib/module/components/StripeProvider.js +1 -1
  96. package/lib/module/components/StripeProvider.js.map +1 -1
  97. package/lib/module/connect/Components.js +1 -1
  98. package/lib/module/connect/Components.js.map +1 -1
  99. package/lib/module/connect/ConnectComponentsProvider.js +1 -1
  100. package/lib/module/connect/ConnectComponentsProvider.js.map +1 -1
  101. package/lib/module/connect/EmbeddedComponent.js +1 -1
  102. package/lib/module/connect/EmbeddedComponent.js.map +1 -1
  103. package/lib/module/connect/ModalCloseButton.js +1 -1
  104. package/lib/module/connect/ModalCloseButton.js.map +1 -1
  105. package/lib/module/connect/NavigationBar.js +1 -1
  106. package/lib/module/connect/NavigationBar.js.map +1 -1
  107. package/lib/module/helpers.js +1 -1
  108. package/lib/module/hooks/useOnramp.js +1 -1
  109. package/lib/module/hooks/useOnramp.js.map +1 -1
  110. package/lib/module/specs/NativeAddToWalletButton.js +1 -1
  111. package/lib/module/specs/NativeAddressSheet.js +1 -1
  112. package/lib/module/specs/NativeApplePayButton.js +1 -1
  113. package/lib/module/specs/NativeAuBECSDebitForm.js +1 -1
  114. package/lib/module/specs/NativeCardField.js +1 -1
  115. package/lib/module/specs/NativeCardField.js.map +1 -1
  116. package/lib/module/specs/NativeCardForm.js +1 -1
  117. package/lib/module/specs/NativeCardForm.js.map +1 -1
  118. package/lib/module/specs/NativeConnectAccountOnboardingView.js +1 -1
  119. package/lib/module/specs/NativeCurrencySelectorElement.js +1 -1
  120. package/lib/module/specs/NativeEmbeddedPaymentElement.js +1 -1
  121. package/lib/module/specs/NativeEmbeddedPaymentElement.js.map +1 -1
  122. package/lib/module/specs/NativeGooglePayButton.js +1 -1
  123. package/lib/module/specs/NativeNavigationBar.js +1 -1
  124. package/lib/module/specs/NativeOnrampSdkModule.js.map +1 -1
  125. package/lib/module/specs/NativePaymentMethodMessagingElement.js +1 -1
  126. package/lib/module/specs/NativeStripeContainer.js +1 -1
  127. package/lib/module/types/EmbeddedPaymentElement.js +1 -1
  128. package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
  129. package/lib/module/types/Onramp.js +1 -1
  130. package/lib/module/types/Onramp.js.map +1 -1
  131. package/lib/typescript/src/components/StripeProvider.d.ts.map +1 -1
  132. package/lib/typescript/src/hooks/useOnramp.d.ts +18 -0
  133. package/lib/typescript/src/hooks/useOnramp.d.ts.map +1 -1
  134. package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts +2 -0
  135. package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts.map +1 -1
  136. package/lib/typescript/src/types/Onramp.d.ts +84 -10
  137. package/lib/typescript/src/types/Onramp.d.ts.map +1 -1
  138. package/package.json +1 -1
  139. package/src/components/StripeProvider.tsx +0 -2
  140. package/src/hooks/useOnramp.tsx +50 -83
  141. package/src/specs/NativeOnrampSdkModule.ts +8 -0
  142. package/src/types/Onramp.ts +100 -9
  143. package/lib/commonjs/internal/stripeConfig.js +0 -2
  144. package/lib/commonjs/internal/stripeConfig.js.map +0 -1
  145. package/lib/module/internal/stripeConfig.js +0 -2
  146. package/lib/module/internal/stripeConfig.js.map +0 -1
  147. package/lib/typescript/src/internal/stripeConfig.d.ts +0 -3
  148. package/lib/typescript/src/internal/stripeConfig.d.ts.map +0 -1
  149. package/src/internal/stripeConfig.ts +0 -9
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="CompilerConfiguration">
4
+ <bytecodeTargetLevel target="1.6" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="GradleSettings">
4
+ <option name="linkedExternalProjectsSettings">
5
+ <GradleProjectSettings>
6
+ <option name="testRunner" value="CHOOSE_PER_TEST" />
7
+ <option name="externalProjectPath" value="$PROJECT_DIR$" />
8
+ <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
9
+ <option name="modules">
10
+ <set>
11
+ <option value="$PROJECT_DIR$" />
12
+ </set>
13
+ </option>
14
+ </GradleProjectSettings>
15
+ </option>
16
+ </component>
17
+ </project>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectMigrations">
4
+ <option name="MigrateToGradleLocalJavaHome">
5
+ <set>
6
+ <option value="$PROJECT_DIR$" />
7
+ </set>
8
+ </option>
9
+ </component>
10
+ </project>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ExternalStorageConfigurationManager" enabled="true" />
4
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
5
+ <output url="file://$PROJECT_DIR$/build/classes" />
6
+ </component>
7
+ <component name="ProjectType">
8
+ <option name="id" value="Android" />
9
+ </component>
10
+ </project>
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="RunConfigurationProducerService">
4
+ <option name="ignoredProducers">
5
+ <set>
6
+ <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
7
+ <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
8
+ <option value="com.intellij.execution.junit.PatternConfigurationProducer" />
9
+ <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
10
+ <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
11
+ <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
12
+ <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
13
+ <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
14
+ </set>
15
+ </option>
16
+ </component>
17
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,8 @@
1
+ ## This file must *NOT* be checked into Version Control Systems,
2
+ # as it contains information specific to your local configuration.
3
+ #
4
+ # Location of the SDK. This is only used by Gradle.
5
+ # For customization when using a Version Control System, please read the
6
+ # header note.
7
+ #Tue Jul 29 21:50:46 PDT 2025
8
+ sdk.dir=/Users/cttsai/Library/Android/sdk
@@ -360,6 +360,11 @@ internal fun mapFromToken(token: Token): WritableMap {
360
360
  return tokenMap
361
361
  }
362
362
 
363
+ private fun mapFromStringList(list: Collection<String>?): WritableArray? =
364
+ list?.let { networks ->
365
+ Arguments.createArray().also { arr -> networks.forEach { arr.pushString(it) } }
366
+ }
367
+
363
368
  internal fun mapFromPaymentMethod(paymentMethod: PaymentMethod): WritableMap {
364
369
  val pm: WritableMap = Arguments.createMap()
365
370
 
@@ -379,13 +384,7 @@ internal fun mapFromPaymentMethod(paymentMethod: PaymentMethod): WritableMap {
379
384
  it.putString("last4", paymentMethod.card?.last4)
380
385
  it.putString("fingerprint", paymentMethod.card?.fingerprint)
381
386
  it.putString("preferredNetwork", paymentMethod.card?.networks?.preferred)
382
- it.putArray(
383
- "availableNetworks",
384
- paymentMethod.card
385
- ?.networks
386
- ?.available
387
- ?.toList() as? ReadableArray,
388
- )
387
+ it.putArray("availableNetworks", mapFromStringList(paymentMethod.card?.networks?.available))
389
388
  it.putMap(
390
389
  "threeDSecureUsage",
391
390
  Arguments.createMap().also { threeDSecureUsageMap ->
@@ -455,7 +454,7 @@ internal fun mapFromPaymentMethod(paymentMethod: PaymentMethod): WritableMap {
455
454
  it.putString("preferredNetworks", paymentMethod.usBankAccount?.networks?.preferred)
456
455
  it.putArray(
457
456
  "supportedNetworks",
458
- paymentMethod.usBankAccount?.networks?.supported as? ReadableArray,
457
+ mapFromStringList(paymentMethod.usBankAccount?.networks?.supported),
459
458
  )
460
459
  },
461
460
  )
@@ -55,6 +55,14 @@ public abstract class NativeOnrampSdkModuleSpec extends ReactContextBaseJavaModu
55
55
  @DoNotStrip
56
56
  public abstract void registerWalletAddress(String walletAddress, String network, Promise promise);
57
57
 
58
+ @ReactMethod
59
+ @DoNotStrip
60
+ public abstract void getWalletOwnershipChallenge(String walletAddress, String network, Promise promise);
61
+
62
+ @ReactMethod
63
+ @DoNotStrip
64
+ public abstract void submitWalletOwnershipSignature(String challengeId, String signature, Promise promise);
65
+
58
66
  @ReactMethod
59
67
  @DoNotStrip
60
68
  public abstract void attachKycInfo(ReadableMap kycInfo, Promise promise);
@@ -7,7 +7,6 @@ import com.facebook.react.bridge.WritableMap
7
7
  import com.reactnativestripesdk.utils.ErrorType
8
8
  import com.stripe.android.core.exception.StripeException
9
9
  import com.stripe.android.crypto.onramp.ExperimentalCryptoOnramp
10
- import com.stripe.android.crypto.onramp.exception.AppAttestationException
11
10
  import com.stripe.android.crypto.onramp.exception.CryptoOnrampApiException
12
11
  import com.stripe.android.crypto.onramp.exception.StripeCryptoOnrampError
13
12
 
@@ -40,16 +39,13 @@ private fun createOnrampError(
40
39
  type = apiErrorContext.apiErrorType ?: stripeError?.type,
41
40
  stripeErrorCode = apiException.code,
42
41
  ) {
43
- putString("onrampErrorType", onrampErrorType)
44
- putString("developerMessage", apiException.developerMessage)
45
- putString("userMessage", apiException.userMessage)
42
+ putCommonOnrampFields(apiException, onrampErrorType)
46
43
  putString("reason", apiErrorContext.reason)
47
44
  putString("requestId", apiErrorContext.requestId)
48
45
  putString("apiErrorCode", apiErrorContext.apiErrorCode)
49
46
  putString("apiErrorType", apiErrorContext.apiErrorType)
50
47
  putString("apiErrorMessage", apiErrorContext.apiErrorMessage)
51
48
  putString("apiUserMessage", apiErrorContext.apiUserMessage)
52
- putString("docUrl", apiErrorContext.docUrl)
53
49
  }
54
50
  }
55
51
 
@@ -67,11 +63,22 @@ private fun createNonApiOnrampError(
67
63
  localizedMessage = error.localizedMessage,
68
64
  stripeErrorCode = onrampError.code,
69
65
  ) {
70
- putString("developerMessage", onrampError.developerMessage)
71
- putString("userMessage", onrampError.userMessage)
66
+ putCommonOnrampFields(onrampError, onrampError.toOnrampErrorType())
72
67
  }
73
68
  }
74
69
 
70
+ private fun WritableMap.putCommonOnrampFields(
71
+ error: StripeCryptoOnrampError,
72
+ onrampErrorType: String? = null,
73
+ ) {
74
+ onrampErrorType?.let {
75
+ putString("onrampErrorType", it)
76
+ }
77
+ putString("developerMessage", error.developerMessage)
78
+ putString("userMessage", error.userMessage)
79
+ putString("docUrl", error.docUrl)
80
+ }
81
+
75
82
  private fun createOnrampMessageError(
76
83
  code: String,
77
84
  message: String?,
@@ -79,12 +86,19 @@ private fun createOnrampMessageError(
79
86
  createOnrampErrorMap(code = code, message = message)
80
87
 
81
88
  private fun CryptoOnrampApiException.toOnrampErrorType(): String =
82
- if (this is AppAttestationException) {
89
+ if (code == "link_failed_to_attest_request") {
83
90
  "AppAttestationError"
84
91
  } else {
85
92
  "UncategorizedApiError"
86
93
  }
87
94
 
95
+ private fun StripeCryptoOnrampError.toOnrampErrorType(): String? =
96
+ if (code == "app_attestation_unavailable") {
97
+ "AppAttestationUnavailableError"
98
+ } else {
99
+ null
100
+ }
101
+
88
102
  private fun createGenericError(
89
103
  code: String,
90
104
  error: Throwable,
@@ -10,10 +10,13 @@ import com.facebook.react.bridge.ReadableMap
10
10
  import com.facebook.react.bridge.WritableArray
11
11
  import com.stripe.android.core.model.CountryCode
12
12
  import com.stripe.android.crypto.onramp.ExperimentalCryptoOnramp
13
+ import com.stripe.android.crypto.onramp.model.CryptoConsumerWallet
14
+ import com.stripe.android.crypto.onramp.model.CryptoNetwork
13
15
  import com.stripe.android.crypto.onramp.exception.SDKVersion
14
16
  import com.stripe.android.crypto.onramp.model.KycInfo
15
17
  import com.stripe.android.crypto.onramp.model.OnrampConfiguration
16
18
  import com.stripe.android.crypto.onramp.model.PaymentMethodDisplayData
19
+ import com.stripe.android.crypto.onramp.model.WalletOwnershipChallenge
17
20
  import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifier
18
21
  import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifierAlternativeGroup
19
22
  import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifierRequirement
@@ -203,6 +206,51 @@ internal fun mapFromKycInfo(kycInfo: KycInfo): ReadableMap {
203
206
  return result
204
207
  }
205
208
 
209
+ internal fun mapFromWalletOwnershipChallenge(challenge: WalletOwnershipChallenge) =
210
+ mapFromWalletOwnershipChallenge(
211
+ challengeId = challenge.challengeId,
212
+ walletAddress = challenge.walletAddress,
213
+ network = challenge.network,
214
+ message = challenge.message,
215
+ expiresAt = challenge.expiresAt,
216
+ )
217
+
218
+ internal fun mapFromWalletOwnershipChallenge(
219
+ challengeId: String,
220
+ walletAddress: String,
221
+ network: CryptoNetwork,
222
+ message: String,
223
+ expiresAt: String,
224
+ ) =
225
+ Arguments.createMap().apply {
226
+ putString("challengeId", challengeId)
227
+ putString("walletAddress", walletAddress)
228
+ putString("network", network.value)
229
+ putString("message", message)
230
+ putString("expiresAt", expiresAt)
231
+ }
232
+
233
+ internal fun mapFromCryptoConsumerWallet(wallet: CryptoConsumerWallet) =
234
+ mapFromCryptoConsumerWallet(
235
+ id = wallet.id,
236
+ network = wallet.network,
237
+ walletAddress = wallet.walletAddress,
238
+ verifiedOwnership = wallet.verifiedOwnership,
239
+ )
240
+
241
+ internal fun mapFromCryptoConsumerWallet(
242
+ id: String,
243
+ network: CryptoNetwork,
244
+ walletAddress: String,
245
+ verifiedOwnership: Boolean,
246
+ ) =
247
+ Arguments.createMap().apply {
248
+ putString("id", id)
249
+ putString("walletAddress", walletAddress)
250
+ putString("network", network.value)
251
+ putBoolean("verifiedOwnership", verifiedOwnership)
252
+ }
253
+
206
254
  internal fun mapToComplianceIdentifiers(identifiers: ReadableArray): List<ComplianceIdentifier> {
207
255
  val complianceIdentifiers = mutableListOf<ComplianceIdentifier>()
208
256
 
@@ -39,6 +39,7 @@ import com.stripe.android.crypto.onramp.model.OnrampCheckoutResult
39
39
  import com.stripe.android.crypto.onramp.model.OnrampCollectPaymentMethodResult
40
40
  import com.stripe.android.crypto.onramp.model.OnrampConfigurationResult
41
41
  import com.stripe.android.crypto.onramp.model.OnrampCreateCryptoPaymentTokenResult
42
+ import com.stripe.android.crypto.onramp.model.OnrampGetWalletOwnershipChallengeResult
42
43
  import com.stripe.android.crypto.onramp.model.OnrampUserAttestationResult
43
44
  import com.stripe.android.crypto.onramp.model.OnrampHasLinkAccountResult
44
45
  import com.stripe.android.crypto.onramp.model.OnrampLogOutResult
@@ -46,6 +47,7 @@ import com.stripe.android.crypto.onramp.model.OnrampRegisterLinkUserResult
46
47
  import com.stripe.android.crypto.onramp.model.OnrampRegisterWalletAddressResult
47
48
  import com.stripe.android.crypto.onramp.model.OnrampRetrieveMissingIdentifiersResult
48
49
  import com.stripe.android.crypto.onramp.model.OnrampSubmitIdentifiersResult
50
+ import com.stripe.android.crypto.onramp.model.OnrampSubmitWalletOwnershipSignatureResult
49
51
  import com.stripe.android.crypto.onramp.model.OnrampTokenAuthenticationResult
50
52
  import com.stripe.android.crypto.onramp.model.OnrampUpdatePhoneNumberResult
51
53
  import com.stripe.android.crypto.onramp.model.OnrampVerifyIdentityResult
@@ -297,6 +299,66 @@ class OnrampSdkModule(
297
299
  }
298
300
  }
299
301
 
302
+ @ReactMethod
303
+ override fun getWalletOwnershipChallenge(
304
+ walletAddress: String,
305
+ network: String,
306
+ promise: Promise,
307
+ ) {
308
+ val coordinator =
309
+ onrampCoordinator ?: run {
310
+ promise.resolve(createOnrampNotConfiguredError())
311
+ return
312
+ }
313
+ CoroutineScope(Dispatchers.IO).launch {
314
+ val cryptoNetwork = enumValues<CryptoNetwork>().firstOrNull { it.value == network }
315
+ if (cryptoNetwork == null) {
316
+ promise.resolve(createError(ErrorType.Unknown.toString(), "Invalid network: $network"))
317
+ return@launch
318
+ }
319
+
320
+ when (val result = coordinator.getWalletOwnershipChallenge(walletAddress, cryptoNetwork)) {
321
+ is OnrampGetWalletOwnershipChallengeResult.Completed -> {
322
+ promise.resolve(
323
+ WritableNativeMap().apply {
324
+ putMap("challenge", mapFromWalletOwnershipChallenge(result.challenge))
325
+ },
326
+ )
327
+ }
328
+ is OnrampGetWalletOwnershipChallengeResult.Failed -> {
329
+ promise.resolve(createOnrampFailedError(result.error))
330
+ }
331
+ }
332
+ }
333
+ }
334
+
335
+ @ReactMethod
336
+ override fun submitWalletOwnershipSignature(
337
+ challengeId: String,
338
+ signature: String,
339
+ promise: Promise,
340
+ ) {
341
+ val coordinator =
342
+ onrampCoordinator ?: run {
343
+ promise.resolve(createOnrampNotConfiguredError())
344
+ return
345
+ }
346
+ CoroutineScope(Dispatchers.IO).launch {
347
+ when (val result = coordinator.submitWalletOwnershipSignature(challengeId, signature)) {
348
+ is OnrampSubmitWalletOwnershipSignatureResult.Completed -> {
349
+ promise.resolve(
350
+ WritableNativeMap().apply {
351
+ putMap("consumerWallet", mapFromCryptoConsumerWallet(result.consumerWallet))
352
+ },
353
+ )
354
+ }
355
+ is OnrampSubmitWalletOwnershipSignatureResult.Failed -> {
356
+ promise.resolve(createOnrampFailedError(result.error))
357
+ }
358
+ }
359
+ }
360
+ }
361
+
300
362
  @ReactMethod
301
363
  override fun attachKycInfo(
302
364
  kycInfo: ReadableMap,
@@ -17,9 +17,9 @@ import org.robolectric.RobolectricTestRunner
17
17
  @RunWith(RobolectricTestRunner::class)
18
18
  class OnrampErrorsTest {
19
19
  @Test
20
- fun createOnrampFailedError_withAppAttestationException_preservesOnrampDiagnostics() {
20
+ fun createOnrampFailedError_withAppAttestationException_preservesApiFields() {
21
21
  val error =
22
- createOnrampException(
22
+ createApiOnrampException(
23
23
  className = "com.stripe.android.crypto.onramp.exception.AppAttestationException",
24
24
  reason = "android_package_name_mismatch",
25
25
  operation = "configure",
@@ -67,9 +67,9 @@ class OnrampErrorsTest {
67
67
  }
68
68
 
69
69
  @Test
70
- fun createOnrampFailedError_withUncategorizedApiError_preservesStripeCodes() {
70
+ fun createOnrampFailedError_withUncategorizedApiError_preservesApiFields() {
71
71
  val error =
72
- createOnrampException(
72
+ createApiOnrampException(
73
73
  className = "com.stripe.android.crypto.onramp.exception.UncategorizedException",
74
74
  reason = "consumer_session_expired",
75
75
  operation = "authorize",
@@ -93,11 +93,50 @@ class OnrampErrorsTest {
93
93
  assertEquals("authentication_error", details.getString("type"))
94
94
  assertEquals("Session expired. Please sign in again.", details.getString("message"))
95
95
  assertEquals("Session expired. Please sign in again.", details.getString("userMessage"))
96
+ assertEquals("consumer_session_expired", details.getString("reason"))
96
97
  assertEquals("req_auth", details.getString("requestId"))
97
98
  assertTrue(details.getString("developerMessage")!!.contains("Code: consumer_session_expired"))
98
99
  }
99
100
 
100
- private fun createOnrampException(
101
+ @Test
102
+ fun createOnrampFailedError_withAppAttestationUnavailableException_preservesSdkErrorFields() {
103
+ val underlyingError = IllegalStateException("Native Link is not available")
104
+ val error =
105
+ createAppAttestationUnavailableException(
106
+ underlyingError = underlyingError,
107
+ operation = "configure",
108
+ appPackageName = "com.example.app",
109
+ mode = "test",
110
+ sdkVersions =
111
+ listOf(
112
+ SDKVersion(name = "stripe-android", version = "23.10.0"),
113
+ SDKVersion(name = "stripe-react-native", version = "0.67.0"),
114
+ ),
115
+ userMessage = "This app couldn't be verified. Contact the app developer for help.",
116
+ )
117
+
118
+ val details = createOnrampFailedError(error).getMap("error")
119
+
120
+ assertNotNull(details)
121
+ assertEquals("Failed", details!!.getString("code"))
122
+ assertEquals("AppAttestationUnavailableError", details.getString("onrampErrorType"))
123
+ assertEquals(
124
+ "This app couldn't be verified. Contact the app developer for help.",
125
+ details.getString("message"),
126
+ )
127
+ assertEquals(
128
+ "This app couldn't be verified. Contact the app developer for help.",
129
+ details.getString("localizedMessage"),
130
+ )
131
+ assertEquals(
132
+ "This app couldn't be verified. Contact the app developer for help.",
133
+ details.getString("userMessage"),
134
+ )
135
+ assertEquals("app_attestation_unavailable", details.getString("stripeErrorCode"))
136
+ assertTrue(details.getString("developerMessage")!!.contains("operation: configure"))
137
+ }
138
+
139
+ private fun createApiOnrampException(
101
140
  className: String,
102
141
  reason: String,
103
142
  operation: String,
@@ -120,7 +159,7 @@ class OnrampErrorsTest {
120
159
  docUrl = docUrl,
121
160
  )
122
161
  val cause = APIException(stripeError = stripeError, requestId = requestId)
123
- val context =
162
+ val apiErrorContext =
124
163
  APIErrorContext(
125
164
  reason = reason,
126
165
  apiErrorCode = apiErrorCode,
@@ -130,41 +169,79 @@ class OnrampErrorsTest {
130
169
  docUrl = docUrl,
131
170
  underlyingError = cause,
132
171
  )
133
- val diagnosticContextClass =
134
- Class.forName("com.stripe.android.crypto.onramp.exception.DiagnosticContext")
135
172
  val diagnosticContext =
136
- diagnosticContextClass
137
- .getDeclaredConstructor(
138
- List::class.java,
139
- String::class.java,
140
- String::class.java,
141
- String::class.java,
142
- ).apply {
143
- isAccessible = true
144
- }.newInstance(
145
- sdkVersions,
146
- operation,
147
- appPackageName,
148
- mode,
149
- )
173
+ createDiagnosticContext(sdkVersions, operation, appPackageName, mode)
150
174
 
151
- // The exception classes in the `com.stripe.android.crypto.onramp.exception` package
152
- // don't have public constructors, so we use reflection to create instances for testing.
153
175
  val constructor =
154
176
  Class
155
177
  .forName(className)
156
178
  .getDeclaredConstructor(
157
179
  APIErrorContext::class.java,
158
- diagnosticContextClass,
180
+ diagnosticContext.javaClass,
159
181
  String::class.java,
160
182
  ).apply {
161
183
  isAccessible = true
162
184
  }
163
185
 
164
186
  return constructor.newInstance(
165
- context,
187
+ apiErrorContext,
166
188
  diagnosticContext,
167
189
  userMessage,
168
190
  ) as Exception
169
191
  }
192
+
193
+ private fun createAppAttestationUnavailableException(
194
+ underlyingError: Throwable,
195
+ operation: String,
196
+ appPackageName: String,
197
+ mode: String,
198
+ sdkVersions: List<SDKVersion>,
199
+ userMessage: String,
200
+ ): Exception {
201
+ val diagnosticContext =
202
+ createDiagnosticContext(sdkVersions, operation, appPackageName, mode)
203
+ val exceptionClass =
204
+ Class.forName(
205
+ "com.stripe.android.crypto.onramp.exception.AppAttestationUnavailableException",
206
+ )
207
+
208
+ val constructor =
209
+ exceptionClass
210
+ .getDeclaredConstructor(
211
+ Throwable::class.java,
212
+ diagnosticContext.javaClass,
213
+ String::class.java,
214
+ )
215
+ .apply {
216
+ isAccessible = true
217
+ }
218
+
219
+ return constructor.newInstance(
220
+ underlyingError,
221
+ diagnosticContext,
222
+ userMessage,
223
+ ) as Exception
224
+ }
225
+
226
+ private fun createDiagnosticContext(
227
+ sdkVersions: List<SDKVersion>,
228
+ operation: String,
229
+ appPackageName: String,
230
+ mode: String,
231
+ ): Any {
232
+ val diagnosticContextClass =
233
+ Class.forName("com.stripe.android.crypto.onramp.exception.DiagnosticContext")
234
+ val constructor =
235
+ diagnosticContextClass
236
+ .getDeclaredConstructor(
237
+ List::class.java,
238
+ String::class.java,
239
+ String::class.java,
240
+ String::class.java,
241
+ ).apply {
242
+ isAccessible = true
243
+ }
244
+
245
+ return constructor.newInstance(sdkVersions, operation, appPackageName, mode)
246
+ }
170
247
  }
@@ -8,7 +8,9 @@ import com.reactnativestripesdk.ComplianceIdentifierFieldException
8
8
  import com.reactnativestripesdk.InvalidIdentifiersArrayException
9
9
  import com.reactnativestripesdk.mapAppearance
10
10
  import com.reactnativestripesdk.mapConfig
11
+ import com.reactnativestripesdk.mapFromCryptoConsumerWallet
11
12
  import com.reactnativestripesdk.mapFromKycInfo
13
+ import com.reactnativestripesdk.mapFromWalletOwnershipChallenge
12
14
  import com.reactnativestripesdk.mapGooglePayConfig
13
15
  import com.reactnativestripesdk.mapPaymentDetailsType
14
16
  import com.reactnativestripesdk.mapToComplianceIdentifiers
@@ -16,6 +18,7 @@ import com.reactnativestripesdk.utils.readableArrayOf
16
18
  import com.reactnativestripesdk.utils.readableMapOf
17
19
  import com.stripe.android.core.model.CountryCode
18
20
  import com.stripe.android.crypto.onramp.ExperimentalCryptoOnramp
21
+ import com.stripe.android.crypto.onramp.model.CryptoNetwork
19
22
  import com.stripe.android.crypto.onramp.model.KycInfo
20
23
  import com.stripe.android.crypto.onramp.model.PaymentMethodDisplayData
21
24
  import com.stripe.android.googlepaylauncher.GooglePayEnvironment
@@ -593,6 +596,40 @@ class OnrampMappersTest {
593
596
  assertFalse(address.hasKey("state"))
594
597
  }
595
598
 
599
+ @Test
600
+ fun mapFromWalletOwnershipChallenge_MapsAllFields() {
601
+ val result =
602
+ mapFromWalletOwnershipChallenge(
603
+ challengeId = "cwoc_123",
604
+ walletAddress = "0x1234",
605
+ network = CryptoNetwork.Solana,
606
+ message = "Sign this exact message",
607
+ expiresAt = "2026-07-15T16:00:00Z",
608
+ )
609
+
610
+ assertEquals("cwoc_123", result.getString("challengeId"))
611
+ assertEquals("0x1234", result.getString("walletAddress"))
612
+ assertEquals("solana", result.getString("network"))
613
+ assertEquals("Sign this exact message", result.getString("message"))
614
+ assertEquals("2026-07-15T16:00:00Z", result.getString("expiresAt"))
615
+ }
616
+
617
+ @Test
618
+ fun mapFromCryptoConsumerWallet_MapsAllFields() {
619
+ val result =
620
+ mapFromCryptoConsumerWallet(
621
+ id = "ccw_123",
622
+ network = CryptoNetwork.Ethereum,
623
+ walletAddress = "0xabcd",
624
+ verifiedOwnership = true,
625
+ )
626
+
627
+ assertEquals("ccw_123", result.getString("id"))
628
+ assertEquals("0xabcd", result.getString("walletAddress"))
629
+ assertEquals("ethereum", result.getString("network"))
630
+ assertTrue(result.getBoolean("verifiedOwnership"))
631
+ }
632
+
596
633
  @Test
597
634
  fun mapToComplianceIdentifiers_ValidIdentifiers_ReturnsIdentifiers() {
598
635
  val identifiers =