@stripe/stripe-react-native 0.65.1 → 0.67.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 (107) hide show
  1. package/android/detekt/detekt-baseline.xml +0 -10
  2. package/android/gradle.properties +1 -1
  3. package/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt +51 -57
  4. package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorElementEvent.kt +26 -0
  5. package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorElementView.kt +129 -16
  6. package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorElementViewManager.kt +17 -0
  7. package/android/src/main/java/com/reactnativestripesdk/EventEmitterCompat.kt +4 -0
  8. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +8 -19
  9. package/android/src/main/java/com/reactnativestripesdk/StripeAbstractComposeView.kt +1 -0
  10. package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +89 -31
  11. package/android/src/main/java/com/reactnativestripesdk/StripeSdkPackage.kt +1 -0
  12. package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt +2 -16
  13. package/android/src/main/java/com/reactnativestripesdk/utils/CheckoutMappers.kt +59 -33
  14. package/android/src/main/java/com/reactnativestripesdk/utils/DefaultActivityLifecycleCallbacks.kt +41 -0
  15. package/android/src/main/java/com/reactnativestripesdk/utils/Errors.kt +11 -6
  16. package/android/src/oldarch/java/com/reactnativestripesdk/NativeOnrampSdkModuleSpec.java +17 -3
  17. package/android/src/oldarch/java/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java +2 -2
  18. package/android/src/onramp/java/com/reactnativestripesdk/OnrampErrors.kt +145 -0
  19. package/android/src/onramp/java/com/reactnativestripesdk/OnrampMappers.kt +125 -0
  20. package/android/src/onramp/java/com/reactnativestripesdk/OnrampSdkModule.kt +144 -22
  21. package/android/src/test/java/com/reactnativestripesdk/DrawableLoadingTest.kt +6 -2
  22. package/android/src/test/java/com/reactnativestripesdk/OnrampErrorsTest.kt +168 -0
  23. package/android/src/test/java/com/reactnativestripesdk/mappers/OnrampMappersTest.kt +107 -0
  24. package/android/src/test/java/com/reactnativestripesdk/utils/CheckoutMappersTest.kt +39 -30
  25. package/android/src/test/java/com/reactnativestripesdk/utils/CheckoutTestFixtures.java +8 -0
  26. package/ios/CurrencySelectorElementManager.m +7 -0
  27. package/ios/CurrencySelectorElementView.swift +142 -7
  28. package/ios/EmbeddedPaymentElementView.swift +1 -1
  29. package/ios/Mappers+Checkout.swift +251 -107
  30. package/ios/Mappers.swift +97 -7
  31. package/ios/NewArch/StripeCurrencySelectorElementComponentView.mm +14 -0
  32. package/ios/OldArch/StripeSdkEventEmitterCompat.h +1 -0
  33. package/ios/OldArch/StripeSdkEventEmitterCompat.m +6 -0
  34. package/ios/OnrampErrors.swift +132 -0
  35. package/ios/PaymentOptionDisplayData+ReactNative.swift +1 -1
  36. package/ios/PaymentSheetAppearance.swift +1 -1
  37. package/ios/StripeOnrampSdk.mm +19 -0
  38. package/ios/StripeSdk.mm +14 -14
  39. package/ios/StripeSdkEmitter.swift +1 -0
  40. package/ios/StripeSdkImpl+Checkout.swift +66 -31
  41. package/ios/StripeSdkImpl+CustomerSheet.swift +1 -1
  42. package/ios/StripeSdkImpl+Embedded.swift +1 -1
  43. package/ios/StripeSdkImpl+PaymentSheet.swift +1 -1
  44. package/ios/StripeSdkImpl.swift +116 -24
  45. package/lib/commonjs/components/PaymentMethodMessagingElement.js +1 -1
  46. package/lib/commonjs/components/PaymentMethodMessagingElement.js.map +1 -1
  47. package/lib/commonjs/components/StripeProvider.js +1 -1
  48. package/lib/commonjs/components/StripeProvider.js.map +1 -1
  49. package/lib/commonjs/hooks/useCheckout.js +1 -1
  50. package/lib/commonjs/hooks/useCheckout.js.map +1 -1
  51. package/lib/commonjs/hooks/useOnramp.js +1 -1
  52. package/lib/commonjs/hooks/useOnramp.js.map +1 -1
  53. package/lib/commonjs/internal/stripeConfig.js +2 -0
  54. package/lib/commonjs/internal/stripeConfig.js.map +1 -0
  55. package/lib/commonjs/specs/NativeOnrampSdkModule.js.map +1 -1
  56. package/lib/commonjs/specs/NativeStripeSdkModule.js.map +1 -1
  57. package/lib/commonjs/types/Errors.js +1 -1
  58. package/lib/commonjs/types/Errors.js.map +1 -1
  59. package/lib/commonjs/types/Onramp.js +1 -1
  60. package/lib/commonjs/types/Onramp.js.map +1 -1
  61. package/lib/module/components/PaymentMethodMessagingElement.js +1 -1
  62. package/lib/module/components/PaymentMethodMessagingElement.js.map +1 -1
  63. package/lib/module/components/StripeProvider.js +1 -1
  64. package/lib/module/components/StripeProvider.js.map +1 -1
  65. package/lib/module/hooks/useCheckout.js +1 -1
  66. package/lib/module/hooks/useCheckout.js.map +1 -1
  67. package/lib/module/hooks/useOnramp.js +1 -1
  68. package/lib/module/hooks/useOnramp.js.map +1 -1
  69. package/lib/module/internal/stripeConfig.js +2 -0
  70. package/lib/module/internal/stripeConfig.js.map +1 -0
  71. package/lib/module/specs/NativeOnrampSdkModule.js.map +1 -1
  72. package/lib/module/specs/NativeStripeSdkModule.js.map +1 -1
  73. package/lib/module/types/Errors.js +1 -1
  74. package/lib/module/types/Errors.js.map +1 -1
  75. package/lib/module/types/Onramp.js +1 -1
  76. package/lib/module/types/Onramp.js.map +1 -1
  77. package/lib/typescript/src/components/PaymentMethodMessagingElement.d.ts +1 -1
  78. package/lib/typescript/src/components/PaymentMethodMessagingElement.d.ts.map +1 -1
  79. package/lib/typescript/src/components/StripeProvider.d.ts.map +1 -1
  80. package/lib/typescript/src/hooks/useCheckout.d.ts.map +1 -1
  81. package/lib/typescript/src/hooks/useOnramp.d.ts +32 -10
  82. package/lib/typescript/src/hooks/useOnramp.d.ts.map +1 -1
  83. package/lib/typescript/src/internal/stripeConfig.d.ts +3 -0
  84. package/lib/typescript/src/internal/stripeConfig.d.ts.map +1 -0
  85. package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts +3 -0
  86. package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts.map +1 -1
  87. package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts +2 -2
  88. package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts.map +1 -1
  89. package/lib/typescript/src/types/Checkout.d.ts +266 -92
  90. package/lib/typescript/src/types/Checkout.d.ts.map +1 -1
  91. package/lib/typescript/src/types/Errors.d.ts +0 -5
  92. package/lib/typescript/src/types/Errors.d.ts.map +1 -1
  93. package/lib/typescript/src/types/Onramp.d.ts +168 -9
  94. package/lib/typescript/src/types/Onramp.d.ts.map +1 -1
  95. package/package.json +3 -2
  96. package/src/components/PaymentMethodMessagingElement.tsx +54 -52
  97. package/src/components/StripeProvider.tsx +2 -0
  98. package/src/hooks/useCheckout.tsx +20 -13
  99. package/src/hooks/useOnramp.tsx +139 -12
  100. package/src/internal/stripeConfig.ts +9 -0
  101. package/src/specs/NativeOnrampSdkModule.ts +5 -0
  102. package/src/specs/NativeStripeSdkModule.ts +6 -5
  103. package/src/types/Checkout.ts +289 -93
  104. package/src/types/Errors.ts +0 -6
  105. package/src/types/Onramp.ts +192 -9
  106. package/stripe-react-native.podspec +3 -3
  107. package/ios/CurrencySelectorElementManager.swift +0 -12
@@ -0,0 +1,145 @@
1
+ @file:OptIn(ExperimentalCryptoOnramp::class)
2
+
3
+ package com.reactnativestripesdk
4
+
5
+ import com.facebook.react.bridge.Arguments
6
+ import com.facebook.react.bridge.WritableArray
7
+ import com.facebook.react.bridge.WritableMap
8
+ import com.reactnativestripesdk.utils.ErrorType
9
+ import com.stripe.android.core.exception.StripeException
10
+ import com.stripe.android.crypto.onramp.ExperimentalCryptoOnramp
11
+ import com.stripe.android.crypto.onramp.exception.AppAttestationException
12
+ import com.stripe.android.crypto.onramp.exception.CryptoOnrampApiException
13
+ import com.stripe.android.crypto.onramp.exception.SDKVersion
14
+ import com.stripe.android.crypto.onramp.exception.StripeCryptoOnrampError
15
+
16
+ internal fun createOnrampFailedError(error: Throwable): WritableMap =
17
+ createOnrampError(ErrorType.Failed.toString(), error)
18
+
19
+ internal fun createOnrampNotConfiguredError(): WritableMap =
20
+ createOnrampMessageError(
21
+ ErrorType.Failed.toString(),
22
+ "Onramp is not configured.",
23
+ )
24
+
25
+ private fun createOnrampError(
26
+ code: String,
27
+ error: Throwable,
28
+ ): WritableMap {
29
+ val apiException =
30
+ error as? CryptoOnrampApiException
31
+ ?: return createNonApiOnrampError(code, error)
32
+
33
+ val stripeError = apiException.stripeError
34
+ val onrampErrorType = apiException.toOnrampErrorType()
35
+
36
+ return createOnrampErrorMap(
37
+ code = code,
38
+ message = apiException.userMessage,
39
+ localizedMessage = apiException.localizedMessage,
40
+ declineCode = stripeError?.declineCode,
41
+ type = apiException.context.apiErrorType ?: stripeError?.type,
42
+ stripeErrorCode = apiException.code,
43
+ ) {
44
+ putString("onrampErrorType", onrampErrorType)
45
+ putString("developerMessage", apiException.developerMessage)
46
+ putString("userMessage", apiException.userMessage)
47
+ putString("reason", apiException.context.reason)
48
+ putString("operation", apiException.context.operation)
49
+ putString("appPackageName", apiException.context.appPackageName)
50
+ putString("mode", apiException.context.mode)
51
+ putArray("sdkVersions", mapFromSdkVersions(apiException.sdkVersions))
52
+ putString("requestId", apiException.context.requestId)
53
+ putString("apiErrorCode", apiException.context.apiErrorCode)
54
+ putString("apiErrorType", apiException.context.apiErrorType)
55
+ putString("apiErrorMessage", apiException.context.apiErrorMessage)
56
+ putString("apiUserMessage", apiException.context.apiUserMessage)
57
+ putString("docUrl", apiException.context.docUrl)
58
+ }
59
+ }
60
+
61
+ private fun createNonApiOnrampError(
62
+ code: String,
63
+ error: Throwable,
64
+ ): WritableMap {
65
+ val onrampError =
66
+ error as? StripeCryptoOnrampError
67
+ ?: return createGenericError(code, error)
68
+
69
+ return createOnrampErrorMap(
70
+ code = code,
71
+ message = onrampError.userMessage,
72
+ localizedMessage = error.localizedMessage,
73
+ stripeErrorCode = onrampError.code,
74
+ ) {
75
+ putString("developerMessage", onrampError.developerMessage)
76
+ putString("userMessage", onrampError.userMessage)
77
+ }
78
+ }
79
+
80
+ private fun createOnrampMessageError(
81
+ code: String,
82
+ message: String?,
83
+ ): WritableMap =
84
+ createOnrampErrorMap(code = code, message = message)
85
+
86
+ private fun CryptoOnrampApiException.toOnrampErrorType(): String =
87
+ if (this is AppAttestationException) {
88
+ "AppAttestationError"
89
+ } else {
90
+ "UncategorizedApiError"
91
+ }
92
+
93
+ private fun createGenericError(
94
+ code: String,
95
+ error: Throwable,
96
+ ): WritableMap {
97
+ val stripeError = (error as? StripeException)?.stripeError
98
+
99
+ return createOnrampErrorMap(
100
+ code = code,
101
+ message = error.message,
102
+ localizedMessage = error.localizedMessage,
103
+ declineCode = stripeError?.declineCode,
104
+ type = stripeError?.type,
105
+ stripeErrorCode = stripeError?.code,
106
+ )
107
+ }
108
+
109
+ private fun createOnrampErrorMap(
110
+ code: String,
111
+ message: String?,
112
+ localizedMessage: String? = message,
113
+ declineCode: String? = null,
114
+ type: String? = null,
115
+ stripeErrorCode: String? = null,
116
+ configure: (WritableMap.() -> Unit)? = null,
117
+ ): WritableMap {
118
+ val map: WritableMap = Arguments.createMap()
119
+ val details: WritableMap = Arguments.createMap()
120
+
121
+ details.putString("code", code)
122
+ details.putString("message", message)
123
+ details.putString("localizedMessage", localizedMessage)
124
+ details.putString("declineCode", declineCode)
125
+ details.putString("type", type)
126
+ details.putString("stripeErrorCode", stripeErrorCode)
127
+ configure?.invoke(details)
128
+
129
+ map.putMap("error", details)
130
+ return map
131
+ }
132
+
133
+ private fun mapFromSdkVersions(
134
+ sdkVersions: List<SDKVersion>,
135
+ ): WritableArray =
136
+ Arguments.createArray().apply {
137
+ sdkVersions.forEach { sdkVersion ->
138
+ pushMap(
139
+ Arguments.createMap().apply {
140
+ putString("name", sdkVersion.name)
141
+ putString("version", sdkVersion.version)
142
+ },
143
+ )
144
+ }
145
+ }
@@ -5,11 +5,21 @@ package com.reactnativestripesdk
5
5
  import android.annotation.SuppressLint
6
6
  import androidx.compose.ui.graphics.Color
7
7
  import com.facebook.react.bridge.Arguments
8
+ import com.facebook.react.bridge.ReadableArray
8
9
  import com.facebook.react.bridge.ReadableMap
10
+ import com.facebook.react.bridge.WritableArray
11
+ import com.stripe.android.core.model.CountryCode
9
12
  import com.stripe.android.crypto.onramp.ExperimentalCryptoOnramp
13
+ import com.stripe.android.crypto.onramp.exception.SDKVersion
10
14
  import com.stripe.android.crypto.onramp.model.KycInfo
11
15
  import com.stripe.android.crypto.onramp.model.OnrampConfiguration
12
16
  import com.stripe.android.crypto.onramp.model.PaymentMethodDisplayData
17
+ import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifier
18
+ import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifierAlternativeGroup
19
+ import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifierRequirement
20
+ import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifierRequirements
21
+ import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifierType
22
+ import com.stripe.android.crypto.onramp.model.compliance.SubmitIdentifiersResult
13
23
  import com.stripe.android.googlepaylauncher.GooglePayEnvironment
14
24
  import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher
15
25
  import com.stripe.android.link.LinkAppearance
@@ -23,6 +33,7 @@ import com.stripe.android.paymentsheet.PaymentSheet
23
33
  internal fun mapConfig(
24
34
  configMap: ReadableMap,
25
35
  publishableKey: String,
36
+ additionalSdkVersions: List<SDKVersion> = emptyList(),
26
37
  ): OnrampConfiguration {
27
38
  val appearanceMap = configMap.getMap("appearance")
28
39
  val appearance =
@@ -43,6 +54,11 @@ internal fun mapConfig(
43
54
  .appearance(appearance)
44
55
  .cryptoCustomerId(cryptoCustomerId)
45
56
  .apply { googlePayConfig?.let { googlePayConfig(it) } }
57
+ .apply {
58
+ if (additionalSdkVersions.isNotEmpty()) {
59
+ this.additionalSdkVersions(additionalSdkVersions)
60
+ }
61
+ }
46
62
  }
47
63
 
48
64
  @SuppressLint("RestrictedApi")
@@ -178,10 +194,112 @@ internal fun mapFromKycInfo(kycInfo: KycInfo): ReadableMap {
178
194
  kycInfo.idNumber?.let { result.putString("idNumber", it) }
179
195
  kycInfo.address?.let { result.putMap("address", mapFromKycAddress(it)) }
180
196
  kycInfo.dateOfBirth?.let { result.putMap("dateOfBirth", mapFromDateOfBirth(it)) }
197
+ kycInfo.birthCountry?.let { result.putString("birthCountry", it.value) }
198
+ kycInfo.birthCity?.let { result.putString("birthCity", it) }
199
+ kycInfo.nationalities?.let {
200
+ result.putArray("nationalities", mapFromCountryCodes(it))
201
+ }
181
202
 
182
203
  return result
183
204
  }
184
205
 
206
+ internal fun mapToComplianceIdentifiers(identifiers: ReadableArray): List<ComplianceIdentifier> {
207
+ val complianceIdentifiers = mutableListOf<ComplianceIdentifier>()
208
+
209
+ for (index in 0 until identifiers.size()) {
210
+ val identifierMap =
211
+ runCatching { identifiers.getMap(index) }
212
+ .getOrNull()
213
+ ?: throw InvalidIdentifiersArrayException()
214
+ val type = identifierMap.getRequiredNormalizedString("type")
215
+ val value = identifierMap.getRequiredNormalizedString("value")
216
+
217
+ complianceIdentifiers.add(
218
+ ComplianceIdentifier()
219
+ .type(ComplianceIdentifierType(type))
220
+ .value(value),
221
+ )
222
+ }
223
+
224
+ return complianceIdentifiers
225
+ }
226
+
227
+ internal fun mapFromComplianceIdentifierRequirements(
228
+ requirements: ComplianceIdentifierRequirements,
229
+ ) = Arguments.createMap().apply {
230
+ putArray("identifiers", mapFromComplianceIdentifierRequirementsList(requirements.identifiers))
231
+ putArray("alternatives", mapFromComplianceIdentifierAlternativeGroups(requirements.alternatives))
232
+ putBoolean("carfTinRequired", requirements.carfTinRequired)
233
+ }
234
+
235
+ internal fun mapFromSubmitIdentifiersResult(
236
+ result: SubmitIdentifiersResult,
237
+ ) = Arguments.createMap().apply {
238
+ putBoolean("completed", result.completed)
239
+ putArray("identifiers", mapFromComplianceIdentifierRequirementsList(result.identifiers))
240
+ putArray("alternatives", mapFromComplianceIdentifierAlternativeGroups(result.alternatives))
241
+ putBoolean("carfTinRequired", result.carfTinRequired)
242
+ putArray("invalidIdentifiers", mapFromComplianceIdentifierTypes(result.invalidIdentifiers))
243
+ }
244
+
245
+ private fun mapFromComplianceIdentifierRequirementsList(
246
+ requirements: List<ComplianceIdentifierRequirement>,
247
+ ): WritableArray =
248
+ Arguments.createArray().apply {
249
+ requirements.forEach { requirement ->
250
+ pushMap(
251
+ Arguments.createMap().apply {
252
+ putString("type", requirement.type.value)
253
+ putString("regulation", requirement.regulation.value)
254
+ },
255
+ )
256
+ }
257
+ }
258
+
259
+ private fun mapFromComplianceIdentifierAlternativeGroups(
260
+ groups: List<ComplianceIdentifierAlternativeGroup>,
261
+ ): WritableArray =
262
+ Arguments.createArray().apply {
263
+ groups.forEach { group ->
264
+ pushMap(
265
+ Arguments.createMap().apply {
266
+ putArray(
267
+ "originalMissingIdentifiers",
268
+ mapFromComplianceIdentifierTypes(group.originalMissingIdentifiers),
269
+ )
270
+ putArray(
271
+ "alternativeMissingIdentifiers",
272
+ mapFromComplianceIdentifierTypes(group.alternativeMissingIdentifiers),
273
+ )
274
+ },
275
+ )
276
+ }
277
+ }
278
+
279
+ private fun mapFromComplianceIdentifierTypes(
280
+ identifierTypes: List<ComplianceIdentifierType>,
281
+ ): WritableArray =
282
+ Arguments.createArray().apply {
283
+ identifierTypes.forEach { identifierType ->
284
+ pushString(identifierType.value)
285
+ }
286
+ }
287
+
288
+ private fun mapFromCountryCodes(
289
+ countryCodes: List<CountryCode>,
290
+ ): WritableArray =
291
+ Arguments.createArray().apply {
292
+ countryCodes.forEach { countryCode ->
293
+ pushString(countryCode.value)
294
+ }
295
+ }
296
+
297
+ private fun ReadableMap.getRequiredNormalizedString(key: String): String =
298
+ getString(key)
299
+ ?.trim()
300
+ ?.takeIf { it.isNotEmpty() }
301
+ ?: throw ComplianceIdentifierFieldException(key)
302
+
185
303
  private fun mapFromKycAddress(address: PaymentSheet.Address): ReadableMap {
186
304
  val result = Arguments.createMap()
187
305
 
@@ -202,3 +320,10 @@ private fun mapFromDateOfBirth(dateOfBirth: DateOfBirth): ReadableMap {
202
320
  result.putInt("year", dateOfBirth.year)
203
321
  return result
204
322
  }
323
+
324
+ internal class ComplianceIdentifierFieldException(
325
+ field: String,
326
+ ) : IllegalArgumentException("Invalid format for field: $field")
327
+
328
+ internal class InvalidIdentifiersArrayException :
329
+ IllegalArgumentException("Unexpected format of identifiers array. Expected dictionaries with String keys.")
@@ -7,10 +7,12 @@ import android.app.Application
7
7
  import androidx.activity.ComponentActivity
8
8
  import androidx.fragment.app.FragmentActivity
9
9
  import androidx.lifecycle.SavedStateHandle
10
+ import com.stripe.android.core.model.CountryCode
10
11
  import com.facebook.react.bridge.Arguments
11
12
  import com.facebook.react.bridge.Promise
12
13
  import com.facebook.react.bridge.ReactApplicationContext
13
14
  import com.facebook.react.bridge.ReactMethod
15
+ import com.facebook.react.bridge.ReadableArray
14
16
  import com.facebook.react.bridge.ReadableMap
15
17
  import com.facebook.react.bridge.WritableNativeMap
16
18
  import com.facebook.react.module.annotations.ReactModule
@@ -18,15 +20,15 @@ import com.reactnativestripesdk.utils.ErrorType
18
20
  import com.reactnativestripesdk.utils.createCanceledError
19
21
  import com.reactnativestripesdk.utils.createEmptyResult
20
22
  import com.reactnativestripesdk.utils.createError
21
- import com.reactnativestripesdk.utils.createFailedError
22
23
  import com.reactnativestripesdk.utils.createMissingActivityError
23
24
  import com.reactnativestripesdk.utils.createMissingInitError
24
- import com.reactnativestripesdk.utils.createOnrampNotConfiguredError
25
25
  import com.reactnativestripesdk.utils.createResult
26
+ import com.reactnativestripesdk.utils.getStringList
26
27
  import com.reactnativestripesdk.utils.getValOr
27
28
  import com.reactnativestripesdk.utils.mapToPaymentSheetAddress
28
29
  import com.stripe.android.crypto.onramp.ExperimentalCryptoOnramp
29
30
  import com.stripe.android.crypto.onramp.OnrampCoordinator
31
+ import com.stripe.android.crypto.onramp.exception.SDKVersion
30
32
  import com.stripe.android.crypto.onramp.model.CryptoNetwork
31
33
  import com.stripe.android.crypto.onramp.model.KycInfo
32
34
  import com.stripe.android.crypto.onramp.model.LinkUserInfo
@@ -37,10 +39,13 @@ import com.stripe.android.crypto.onramp.model.OnrampCheckoutResult
37
39
  import com.stripe.android.crypto.onramp.model.OnrampCollectPaymentMethodResult
38
40
  import com.stripe.android.crypto.onramp.model.OnrampConfigurationResult
39
41
  import com.stripe.android.crypto.onramp.model.OnrampCreateCryptoPaymentTokenResult
42
+ import com.stripe.android.crypto.onramp.model.OnrampCrsCarfDeclarationResult
40
43
  import com.stripe.android.crypto.onramp.model.OnrampHasLinkAccountResult
41
44
  import com.stripe.android.crypto.onramp.model.OnrampLogOutResult
42
45
  import com.stripe.android.crypto.onramp.model.OnrampRegisterLinkUserResult
43
46
  import com.stripe.android.crypto.onramp.model.OnrampRegisterWalletAddressResult
47
+ import com.stripe.android.crypto.onramp.model.OnrampRetrieveMissingIdentifiersResult
48
+ import com.stripe.android.crypto.onramp.model.OnrampSubmitIdentifiersResult
44
49
  import com.stripe.android.crypto.onramp.model.OnrampTokenAuthenticationResult
45
50
  import com.stripe.android.crypto.onramp.model.OnrampUpdatePhoneNumberResult
46
51
  import com.stripe.android.crypto.onramp.model.OnrampVerifyIdentityResult
@@ -66,6 +71,7 @@ class OnrampSdkModule(
66
71
  reactContext: ReactApplicationContext,
67
72
  ) : NativeOnrampSdkModuleSpec(reactContext) {
68
73
  private val eventEmitterCompat = EventEmitterCompat(reactContext)
74
+ private var reactNativeSdkVersion: String? = null
69
75
  private lateinit var publishableKey: String
70
76
  private var stripeAccountId: String? = null
71
77
 
@@ -78,6 +84,7 @@ class OnrampSdkModule(
78
84
  private var authorizePromise: Promise? = null
79
85
  private var checkoutPromise: Promise? = null
80
86
  private var verifyKycPromise: Promise? = null
87
+ private var userAttestationPromise: Promise? = null
81
88
 
82
89
  private var checkoutClientSecretDeferred: CompletableDeferred<String>? = null
83
90
  private val rnScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
@@ -89,6 +96,10 @@ class OnrampSdkModule(
89
96
  ) {
90
97
  // Note: This method depends on `StripeSdkModule#initialise()` being called as well.
91
98
  val publishableKey = getValOr(params, "publishableKey", null) as String
99
+ reactNativeSdkVersion =
100
+ params.getMap("appInfo")
101
+ ?.getString("version")
102
+ ?.takeIf { it.isNotBlank() }
92
103
  this.stripeAccountId = getValOr(params, "stripeAccountId", null)
93
104
  this.publishableKey = publishableKey
94
105
 
@@ -141,6 +152,10 @@ class OnrampSdkModule(
141
152
  handleOnrampCheckoutResult(result, checkoutPromise!!)
142
153
  }.verifyKycCallback { result ->
143
154
  handleOnrampKycVerificationResult(result, verifyKycPromise!!)
155
+ }.crsCarfDeclarationCallback { result ->
156
+ userAttestationPromise?.let {
157
+ handleUserAttestationResult(result, it)
158
+ }
144
159
  }.onrampSessionClientSecretProvider { sessionId ->
145
160
  checkoutClientSecretDeferred = CompletableDeferred()
146
161
 
@@ -159,7 +174,7 @@ class OnrampSdkModule(
159
174
  .also { this.onrampCoordinator = it }
160
175
 
161
176
  CoroutineScope(Dispatchers.IO).launch {
162
- val configuration = mapConfig(config, publishableKey)
177
+ val configuration = mapConfig(config, publishableKey, onrampAdditionalSdkVersions())
163
178
  val configureResult = coordinator.configure(configuration)
164
179
 
165
180
  CoroutineScope(Dispatchers.Main).launch {
@@ -168,7 +183,7 @@ class OnrampSdkModule(
168
183
  createOnrampPresenter(promise)
169
184
  }
170
185
  is OnrampConfigurationResult.Failed -> {
171
- promise.resolve(createError(ErrorType.Failed.toString(), configureResult.error))
186
+ promise.resolve(createOnrampFailedError(configureResult.error))
172
187
  }
173
188
  }
174
189
  }
@@ -195,7 +210,7 @@ class OnrampSdkModule(
195
210
  onrampPresenter = onrampCoordinator!!.createPresenter(activity)
196
211
  promise.resolveVoid()
197
212
  } catch (e: Exception) {
198
- promise.resolve(createFailedError(e))
213
+ promise.resolve(createOnrampFailedError(e))
199
214
  }
200
215
  }
201
216
 
@@ -215,7 +230,7 @@ class OnrampSdkModule(
215
230
  promise.resolveBoolean("hasLinkAccount", result.hasLinkAccount)
216
231
  }
217
232
  is OnrampHasLinkAccountResult.Failed -> {
218
- promise.resolve(createFailedError(result.error))
233
+ promise.resolve(createOnrampFailedError(result.error))
219
234
  }
220
235
  }
221
236
  }
@@ -246,7 +261,7 @@ class OnrampSdkModule(
246
261
  promise.resolveString("customerId", result.customerId)
247
262
  }
248
263
  is OnrampRegisterLinkUserResult.Failed -> {
249
- promise.resolve(createFailedError(result.error))
264
+ promise.resolve(createOnrampFailedError(result.error))
250
265
  }
251
266
  }
252
267
  }
@@ -275,7 +290,7 @@ class OnrampSdkModule(
275
290
  promise.resolveVoid()
276
291
  }
277
292
  is OnrampRegisterWalletAddressResult.Failed -> {
278
- promise.resolve(createFailedError(result.error))
293
+ promise.resolve(createOnrampFailedError(result.error))
279
294
  }
280
295
  }
281
296
  }
@@ -311,10 +326,16 @@ class OnrampSdkModule(
311
326
  )
312
327
  } else {
313
328
  null
314
- }
329
+ }
315
330
 
316
331
  val addressMap = kycInfo.getMap("address")
317
332
  val addressObj = mapToPaymentSheetAddress(addressMap)
333
+ val birthCountry = kycInfo.getString("birthCountry")?.let(CountryCode::create)
334
+ val birthCity = kycInfo.getString("birthCity")
335
+ val nationalities =
336
+ kycInfo.getStringList("nationalities")
337
+ ?.map(CountryCode::create)
338
+ ?.takeIf { it.isNotEmpty() }
318
339
 
319
340
  val kycInfoObj =
320
341
  KycInfo(
@@ -323,6 +344,9 @@ class OnrampSdkModule(
323
344
  idNumber = idNumber,
324
345
  dateOfBirth = dob,
325
346
  address = addressObj,
347
+ birthCountry = birthCountry,
348
+ birthCity = birthCity,
349
+ nationalities = nationalities,
326
350
  )
327
351
 
328
352
  when (val result = coordinator.attachKycInfo(kycInfoObj)) {
@@ -330,12 +354,81 @@ class OnrampSdkModule(
330
354
  promise.resolveVoid()
331
355
  }
332
356
  is OnrampAttachKycInfoResult.Failed -> {
333
- promise.resolve(createFailedError(result.error))
357
+ promise.resolve(createOnrampFailedError(result.error))
358
+ }
359
+ }
360
+ }
361
+ }
362
+
363
+ @ReactMethod
364
+ override fun retrieveMissingIdentifiers(promise: Promise) {
365
+ val coordinator =
366
+ onrampCoordinator ?: run {
367
+ promise.resolve(createOnrampNotConfiguredError())
368
+ return
369
+ }
370
+
371
+ CoroutineScope(Dispatchers.IO).launch {
372
+ when (val result = coordinator.retrieveMissingIdentifiers()) {
373
+ is OnrampRetrieveMissingIdentifiersResult.Completed -> {
374
+ promise.resolve(mapFromComplianceIdentifierRequirements(result.requirements))
375
+ }
376
+ is OnrampRetrieveMissingIdentifiersResult.Failed -> {
377
+ promise.resolve(createOnrampFailedError(result.error))
378
+ }
379
+ }
380
+ }
381
+ }
382
+
383
+ @ReactMethod
384
+ override fun submitIdentifiers(
385
+ identifiers: ReadableArray,
386
+ promise: Promise,
387
+ ) {
388
+ val coordinator =
389
+ onrampCoordinator ?: run {
390
+ promise.resolve(createOnrampNotConfiguredError())
391
+ return
392
+ }
393
+
394
+ val complianceIdentifiers =
395
+ try {
396
+ mapToComplianceIdentifiers(identifiers)
397
+ } catch (error: IllegalArgumentException) {
398
+ val errorType =
399
+ if (error is ComplianceIdentifierFieldException) {
400
+ ErrorType.Unknown
401
+ } else {
402
+ ErrorType.Failed
403
+ }
404
+ promise.resolve(createError(errorType.toString(), error.message ?: "Invalid identifiers"))
405
+ return
406
+ }
407
+
408
+ CoroutineScope(Dispatchers.IO).launch {
409
+ when (val result = coordinator.submitIdentifiers(complianceIdentifiers)) {
410
+ is OnrampSubmitIdentifiersResult.Completed -> {
411
+ promise.resolve(mapFromSubmitIdentifiersResult(result.result))
412
+ }
413
+ is OnrampSubmitIdentifiersResult.Failed -> {
414
+ promise.resolve(createOnrampFailedError(result.error))
334
415
  }
335
416
  }
336
417
  }
337
418
  }
338
419
 
420
+ @ReactMethod
421
+ override fun presentUserAttestation(promise: Promise) {
422
+ val presenter =
423
+ onrampPresenter ?: run {
424
+ promise.resolve(createOnrampNotConfiguredError())
425
+ return
426
+ }
427
+
428
+ userAttestationPromise = promise
429
+ presenter.presentCrsCarfDeclaration()
430
+ }
431
+
339
432
  @ReactMethod
340
433
  override fun updatePhoneNumber(
341
434
  phone: String,
@@ -352,7 +445,7 @@ class OnrampSdkModule(
352
445
  promise.resolveVoid()
353
446
  }
354
447
  is OnrampUpdatePhoneNumberResult.Failed -> {
355
- promise.resolve(createFailedError(result.error))
448
+ promise.resolve(createOnrampFailedError(result.error))
356
449
  }
357
450
  }
358
451
  }
@@ -410,7 +503,7 @@ class OnrampSdkModule(
410
503
  platformPayParams.getMap("googlePay")
411
504
  ?: run {
412
505
  promise.resolve(
413
- createFailedError(
506
+ createOnrampFailedError(
414
507
  IllegalArgumentException("Missing googlePay params in platformPayParams"),
415
508
  ),
416
509
  )
@@ -431,7 +524,7 @@ class OnrampSdkModule(
431
524
  }
432
525
  else -> {
433
526
  promise.resolve(
434
- createFailedError(
527
+ createOnrampFailedError(
435
528
  IllegalArgumentException("Unsupported payment method: $paymentMethod"),
436
529
  ),
437
530
  )
@@ -559,7 +652,7 @@ class OnrampSdkModule(
559
652
 
560
653
  if (paymentDetails == null) {
561
654
  promise.resolve(
562
- createFailedError(
655
+ createOnrampFailedError(
563
656
  IllegalArgumentException("Unsupported payment method"),
564
657
  ),
565
658
  )
@@ -660,7 +753,7 @@ class OnrampSdkModule(
660
753
  promise.resolve(createCanceledError("Identity verification was cancelled"))
661
754
  }
662
755
  is OnrampVerifyIdentityResult.Failed -> {
663
- promise.resolve(createFailedError(result.error))
756
+ promise.resolve(createOnrampFailedError(result.error))
664
757
  }
665
758
  }
666
759
  }
@@ -684,7 +777,26 @@ class OnrampSdkModule(
684
777
  promise.resolve(createCanceledError("KYC verification was cancelled"))
685
778
  }
686
779
  is OnrampVerifyKycInfoResult.Failed -> {
687
- promise.resolve(createFailedError(result.error))
780
+ promise.resolve(createOnrampFailedError(result.error))
781
+ }
782
+ }
783
+ }
784
+
785
+ private fun handleUserAttestationResult(
786
+ result: OnrampCrsCarfDeclarationResult,
787
+ promise: Promise,
788
+ ) {
789
+ when (result) {
790
+ is OnrampCrsCarfDeclarationResult.Confirmed -> {
791
+ promise.resolve(
792
+ WritableNativeMap().apply { putString("status", "Confirmed") },
793
+ )
794
+ }
795
+ is OnrampCrsCarfDeclarationResult.Cancelled -> {
796
+ promise.resolve(createCanceledError("User attestation was canceled"))
797
+ }
798
+ is OnrampCrsCarfDeclarationResult.Failed -> {
799
+ promise.resolve(createOnrampFailedError(result.error))
688
800
  }
689
801
  }
690
802
  }
@@ -735,7 +847,7 @@ class OnrampSdkModule(
735
847
  }
736
848
 
737
849
  is OnrampCollectPaymentMethodResult.Failed -> {
738
- promise.resolve(createFailedError(result.error))
850
+ promise.resolve(createOnrampFailedError(result.error))
739
851
  }
740
852
  }
741
853
  }
@@ -764,7 +876,7 @@ class OnrampSdkModule(
764
876
  promise.resolve(createCanceledError("Authorization was cancelled"))
765
877
  }
766
878
  is OnrampAuthorizeResult.Failed -> {
767
- promise.resolve(createFailedError(result.error))
879
+ promise.resolve(createOnrampFailedError(result.error))
768
880
  }
769
881
  }
770
882
  }
@@ -781,7 +893,7 @@ class OnrampSdkModule(
781
893
  promise.resolve(createCanceledError("Checkout was cancelled"))
782
894
  }
783
895
  is OnrampCheckoutResult.Failed -> {
784
- promise.resolve(createFailedError(result.error))
896
+ promise.resolve(createOnrampFailedError(result.error))
785
897
  }
786
898
  }
787
899
  }
@@ -795,7 +907,7 @@ class OnrampSdkModule(
795
907
  promise.resolveString("cryptoPaymentToken", result.cryptoPaymentToken)
796
908
  }
797
909
  is OnrampCreateCryptoPaymentTokenResult.Failed -> {
798
- promise.resolve(createFailedError(result.error))
910
+ promise.resolve(createOnrampFailedError(result.error))
799
911
  }
800
912
  }
801
913
  }
@@ -809,7 +921,7 @@ class OnrampSdkModule(
809
921
  promise.resolveVoid()
810
922
  }
811
923
  is OnrampLogOutResult.Failed -> {
812
- promise.resolve(createFailedError(result.error))
924
+ promise.resolve(createOnrampFailedError(result.error))
813
925
  }
814
926
  }
815
927
  }
@@ -823,7 +935,7 @@ class OnrampSdkModule(
823
935
  promise.resolveVoid()
824
936
  }
825
937
  is OnrampTokenAuthenticationResult.Failed -> {
826
- promise.resolve(createFailedError(result.error))
938
+ promise.resolve(createOnrampFailedError(result.error))
827
939
  }
828
940
  }
829
941
  }
@@ -845,4 +957,14 @@ class OnrampSdkModule(
845
957
  ) {
846
958
  resolve(WritableNativeMap().apply { putBoolean(key, value) })
847
959
  }
960
+
961
+ private fun onrampAdditionalSdkVersions(): List<SDKVersion> =
962
+ reactNativeSdkVersion?.let {
963
+ listOf(
964
+ SDKVersion(
965
+ name = "stripe-react-native",
966
+ version = it,
967
+ ),
968
+ )
969
+ } ?: emptyList()
848
970
  }