@stripe/stripe-react-native 0.69.0 → 0.71.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 (57) hide show
  1. package/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt +8 -0
  2. package/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt +9 -8
  3. package/android/src/oldarch/java/com/reactnativestripesdk/NativeOnrampSdkModuleSpec.java +8 -0
  4. package/android/src/onramp/java/com/reactnativestripesdk/OnrampErrors.kt +22 -8
  5. package/android/src/onramp/java/com/reactnativestripesdk/OnrampMappers.kt +48 -0
  6. package/android/src/onramp/java/com/reactnativestripesdk/OnrampSdkModule.kt +62 -0
  7. package/android/src/test/java/com/reactnativestripesdk/OnrampErrorsTest.kt +103 -26
  8. package/android/src/test/java/com/reactnativestripesdk/mappers/MappersTest.kt +10 -0
  9. package/android/src/test/java/com/reactnativestripesdk/mappers/OnrampMappersTest.kt +37 -0
  10. package/android/src/test/java/com/reactnativestripesdk/mappers/PaymentMethodMappersTest.kt +110 -0
  11. package/ios/Mappers.swift +21 -0
  12. package/ios/OnrampErrors.swift +23 -8
  13. package/ios/PaymentMethodFactory.swift +9 -0
  14. package/ios/StripeOnrampSdk.mm +16 -0
  15. package/ios/StripeSdkImpl.swift +56 -0
  16. package/jest/setup.js +2 -0
  17. package/lib/commonjs/components/StripeProvider.js +1 -1
  18. package/lib/commonjs/components/StripeProvider.js.map +1 -1
  19. package/lib/commonjs/hooks/useOnramp.js +1 -1
  20. package/lib/commonjs/hooks/useOnramp.js.map +1 -1
  21. package/lib/commonjs/specs/NativeOnrampSdkModule.js.map +1 -1
  22. package/lib/commonjs/types/Onramp.js +1 -1
  23. package/lib/commonjs/types/Onramp.js.map +1 -1
  24. package/lib/commonjs/types/PaymentIntent.js.map +1 -1
  25. package/lib/module/components/StripeProvider.js +1 -1
  26. package/lib/module/components/StripeProvider.js.map +1 -1
  27. package/lib/module/hooks/useOnramp.js +1 -1
  28. package/lib/module/hooks/useOnramp.js.map +1 -1
  29. package/lib/module/specs/NativeOnrampSdkModule.js.map +1 -1
  30. package/lib/module/types/Onramp.js +1 -1
  31. package/lib/module/types/Onramp.js.map +1 -1
  32. package/lib/module/types/PaymentIntent.js.map +1 -1
  33. package/lib/typescript/src/components/StripeProvider.d.ts.map +1 -1
  34. package/lib/typescript/src/hooks/useOnramp.d.ts +18 -0
  35. package/lib/typescript/src/hooks/useOnramp.d.ts.map +1 -1
  36. package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts +2 -0
  37. package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts.map +1 -1
  38. package/lib/typescript/src/types/Onramp.d.ts +84 -10
  39. package/lib/typescript/src/types/Onramp.d.ts.map +1 -1
  40. package/lib/typescript/src/types/PaymentIntent.d.ts +9 -1
  41. package/lib/typescript/src/types/PaymentIntent.d.ts.map +1 -1
  42. package/lib/typescript/src/types/PaymentMethod.d.ts +1 -1
  43. package/lib/typescript/src/types/PaymentMethod.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/src/components/StripeProvider.tsx +0 -2
  46. package/src/hooks/useOnramp.tsx +50 -83
  47. package/src/specs/NativeOnrampSdkModule.ts +8 -0
  48. package/src/types/Onramp.ts +100 -9
  49. package/src/types/PaymentIntent.ts +11 -1
  50. package/src/types/PaymentMethod.ts +1 -0
  51. package/lib/commonjs/internal/stripeConfig.js +0 -2
  52. package/lib/commonjs/internal/stripeConfig.js.map +0 -1
  53. package/lib/module/internal/stripeConfig.js +0 -2
  54. package/lib/module/internal/stripeConfig.js.map +0 -1
  55. package/lib/typescript/src/internal/stripeConfig.d.ts +0 -3
  56. package/lib/typescript/src/internal/stripeConfig.d.ts.map +0 -1
  57. package/src/internal/stripeConfig.ts +0 -9
@@ -56,6 +56,7 @@ class PaymentMethodCreateParamsFactory(
56
56
  PaymentMethod.Type.Affirm -> createAffirmParams()
57
57
  PaymentMethod.Type.CashAppPay -> createCashAppParams()
58
58
  PaymentMethod.Type.RevolutPay -> createRevolutPayParams()
59
+ PaymentMethod.Type.PayByBank -> createPayByBankParams()
59
60
  else -> {
60
61
  throw Exception("This paymentMethodType is not supported yet")
61
62
  }
@@ -258,6 +259,12 @@ class PaymentMethodCreateParamsFactory(
258
259
  metadata = metadataParams,
259
260
  )
260
261
 
262
+ private fun createPayByBankParams(): PaymentMethodCreateParams =
263
+ PaymentMethodCreateParams.createPayByBank(
264
+ billingDetails = billingDetailsParams,
265
+ metadata = metadataParams,
266
+ )
267
+
261
268
  @Throws(PaymentMethodCreateParamsException::class)
262
269
  fun createParams(
263
270
  clientSecret: String,
@@ -289,6 +296,7 @@ class PaymentMethodCreateParamsFactory(
289
296
  PaymentMethod.Type.PayPal,
290
297
  PaymentMethod.Type.CashAppPay,
291
298
  PaymentMethod.Type.RevolutPay,
299
+ PaymentMethod.Type.PayByBank,
292
300
  -> {
293
301
  val params = createPaymentMethodParams(paymentMethodType)
294
302
 
@@ -151,6 +151,7 @@ internal fun mapPaymentMethodType(type: PaymentMethod.Type?): String =
151
151
  PaymentMethod.Type.Affirm -> "Affirm"
152
152
  PaymentMethod.Type.CashAppPay -> "CashApp"
153
153
  PaymentMethod.Type.RevolutPay -> "RevolutPay"
154
+ PaymentMethod.Type.PayByBank -> "PayByBank"
154
155
  PaymentMethod.Type.Link -> "Link"
155
156
  else -> "Unknown"
156
157
  }
@@ -182,6 +183,7 @@ internal fun mapToPaymentMethodType(type: String?): PaymentMethod.Type? =
182
183
  "Affirm" -> PaymentMethod.Type.Affirm
183
184
  "CashApp" -> PaymentMethod.Type.CashAppPay
184
185
  "RevolutPay" -> PaymentMethod.Type.RevolutPay
186
+ "PayByBank" -> PaymentMethod.Type.PayByBank
185
187
  "Link" -> PaymentMethod.Type.Link
186
188
  else -> null
187
189
  }
@@ -360,6 +362,11 @@ internal fun mapFromToken(token: Token): WritableMap {
360
362
  return tokenMap
361
363
  }
362
364
 
365
+ private fun mapFromStringList(list: Collection<String>?): WritableArray? =
366
+ list?.let { networks ->
367
+ Arguments.createArray().also { arr -> networks.forEach { arr.pushString(it) } }
368
+ }
369
+
363
370
  internal fun mapFromPaymentMethod(paymentMethod: PaymentMethod): WritableMap {
364
371
  val pm: WritableMap = Arguments.createMap()
365
372
 
@@ -379,13 +386,7 @@ internal fun mapFromPaymentMethod(paymentMethod: PaymentMethod): WritableMap {
379
386
  it.putString("last4", paymentMethod.card?.last4)
380
387
  it.putString("fingerprint", paymentMethod.card?.fingerprint)
381
388
  it.putString("preferredNetwork", paymentMethod.card?.networks?.preferred)
382
- it.putArray(
383
- "availableNetworks",
384
- paymentMethod.card
385
- ?.networks
386
- ?.available
387
- ?.toList() as? ReadableArray,
388
- )
389
+ it.putArray("availableNetworks", mapFromStringList(paymentMethod.card?.networks?.available))
389
390
  it.putMap(
390
391
  "threeDSecureUsage",
391
392
  Arguments.createMap().also { threeDSecureUsageMap ->
@@ -455,7 +456,7 @@ internal fun mapFromPaymentMethod(paymentMethod: PaymentMethod): WritableMap {
455
456
  it.putString("preferredNetworks", paymentMethod.usBankAccount?.networks?.preferred)
456
457
  it.putArray(
457
458
  "supportedNetworks",
458
- paymentMethod.usBankAccount?.networks?.supported as? ReadableArray,
459
+ mapFromStringList(paymentMethod.usBankAccount?.networks?.supported),
459
460
  )
460
461
  },
461
462
  )
@@ -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
  }
@@ -96,6 +96,16 @@ class MappersTest {
96
96
  assertEquals(PaymentMethod.Type.Multibanco, mapToPaymentMethodType("Multibanco"))
97
97
  }
98
98
 
99
+ @Test
100
+ fun mapPaymentMethodType_PayByBank_ReturnsString() {
101
+ assertEquals("PayByBank", mapPaymentMethodType(PaymentMethod.Type.PayByBank))
102
+ }
103
+
104
+ @Test
105
+ fun mapToPaymentMethodType_PayByBank_ReturnsType() {
106
+ assertEquals(PaymentMethod.Type.PayByBank, mapToPaymentMethodType("PayByBank"))
107
+ }
108
+
99
109
  @Test
100
110
  fun mapNextAction_DisplayMultibancoDetails_ReturnsVoucherUrl() {
101
111
  val result =
@@ -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 =
@@ -0,0 +1,110 @@
1
+ package com.reactnativestripesdk.mappers
2
+
3
+ import android.annotation.SuppressLint
4
+ import com.reactnativestripesdk.utils.mapFromPaymentMethod
5
+ import com.stripe.android.model.CardBrand
6
+ import com.stripe.android.model.PaymentMethod
7
+ import org.junit.Assert.assertEquals
8
+ import org.junit.Assert.assertNotNull
9
+ import org.junit.Assert.assertNull
10
+ import org.junit.Test
11
+ import org.junit.runner.RunWith
12
+ import org.robolectric.RobolectricTestRunner
13
+
14
+ @SuppressLint("RestrictedApi")
15
+ @RunWith(RobolectricTestRunner::class)
16
+ class PaymentMethodMappersTest {
17
+ @Test
18
+ fun mapFromPaymentMethod_CardWithNetworks_MapsAvailableNetworks() {
19
+ val paymentMethod =
20
+ PaymentMethod
21
+ .Builder()
22
+ .setId("pm_test123")
23
+ .setType(PaymentMethod.Type.Card)
24
+ .setCard(
25
+ PaymentMethod.Card(
26
+ brand = CardBrand.Discover,
27
+ networks = PaymentMethod.Card.Networks(available = setOf("discover")),
28
+ ),
29
+ ).build()
30
+
31
+ val result = mapFromPaymentMethod(paymentMethod)
32
+ val availableNetworks = result.getMap("Card")?.getArray("availableNetworks")
33
+
34
+ assertNotNull(availableNetworks)
35
+ assertEquals(1, availableNetworks!!.size())
36
+ assertEquals("discover", availableNetworks.getString(0))
37
+ }
38
+
39
+ @Test
40
+ fun mapFromPaymentMethod_CardWithoutNetworks_AvailableNetworksIsNull() {
41
+ val paymentMethod =
42
+ PaymentMethod
43
+ .Builder()
44
+ .setId("pm_test123")
45
+ .setType(PaymentMethod.Type.Card)
46
+ .setCard(PaymentMethod.Card(brand = CardBrand.Visa))
47
+ .build()
48
+
49
+ val result = mapFromPaymentMethod(paymentMethod)
50
+
51
+ assertNull(result.getMap("Card")?.getArray("availableNetworks"))
52
+ }
53
+
54
+ @Test
55
+ fun mapFromPaymentMethod_USBankAccountWithNetworks_MapsSupportedNetworks() {
56
+ val paymentMethod =
57
+ PaymentMethod
58
+ .Builder()
59
+ .setId("pm_test123")
60
+ .setType(PaymentMethod.Type.USBankAccount)
61
+ .setUSBankAccount(
62
+ PaymentMethod.USBankAccount(
63
+ accountHolderType = PaymentMethod.USBankAccount.USBankAccountHolderType.INDIVIDUAL,
64
+ accountType = PaymentMethod.USBankAccount.USBankAccountType.CHECKING,
65
+ bankName = null,
66
+ fingerprint = null,
67
+ last4 = null,
68
+ financialConnectionsAccount = null,
69
+ networks =
70
+ PaymentMethod.USBankAccount.USBankNetworks(
71
+ preferred = "ach",
72
+ supported = listOf("ach"),
73
+ ),
74
+ routingNumber = null,
75
+ ),
76
+ ).build()
77
+
78
+ val result = mapFromPaymentMethod(paymentMethod)
79
+ val supportedNetworks = result.getMap("USBankAccount")?.getArray("supportedNetworks")
80
+
81
+ assertNotNull(supportedNetworks)
82
+ assertEquals(1, supportedNetworks!!.size())
83
+ assertEquals("ach", supportedNetworks.getString(0))
84
+ }
85
+
86
+ @Test
87
+ fun mapFromPaymentMethod_USBankAccountWithoutNetworks_SupportedNetworksIsNull() {
88
+ val paymentMethod =
89
+ PaymentMethod
90
+ .Builder()
91
+ .setId("pm_test123")
92
+ .setType(PaymentMethod.Type.USBankAccount)
93
+ .setUSBankAccount(
94
+ PaymentMethod.USBankAccount(
95
+ accountHolderType = PaymentMethod.USBankAccount.USBankAccountHolderType.INDIVIDUAL,
96
+ accountType = PaymentMethod.USBankAccount.USBankAccountType.CHECKING,
97
+ bankName = null,
98
+ fingerprint = null,
99
+ last4 = null,
100
+ financialConnectionsAccount = null,
101
+ networks = null,
102
+ routingNumber = null,
103
+ ),
104
+ ).build()
105
+
106
+ val result = mapFromPaymentMethod(paymentMethod)
107
+
108
+ assertNull(result.getMap("USBankAccount")?.getArray("supportedNetworks"))
109
+ }
110
+ }