@stripe/stripe-react-native 0.65.1 → 0.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) 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 +95 -26
  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/oldarch/java/com/reactnativestripesdk/NativeOnrampSdkModuleSpec.java +17 -3
  16. package/android/src/oldarch/java/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java +5 -1
  17. package/android/src/onramp/java/com/reactnativestripesdk/OnrampMappers.kt +116 -0
  18. package/android/src/onramp/java/com/reactnativestripesdk/OnrampSdkModule.kt +109 -1
  19. package/android/src/test/java/com/reactnativestripesdk/DrawableLoadingTest.kt +6 -2
  20. package/android/src/test/java/com/reactnativestripesdk/mappers/OnrampMappersTest.kt +107 -0
  21. package/android/src/test/java/com/reactnativestripesdk/utils/CheckoutMappersTest.kt +39 -30
  22. package/android/src/test/java/com/reactnativestripesdk/utils/CheckoutTestFixtures.java +8 -0
  23. package/ios/CurrencySelectorElementManager.m +7 -0
  24. package/ios/CurrencySelectorElementView.swift +142 -7
  25. package/ios/EmbeddedPaymentElementView.swift +1 -1
  26. package/ios/Mappers+Checkout.swift +251 -107
  27. package/ios/Mappers.swift +90 -2
  28. package/ios/NewArch/StripeCurrencySelectorElementComponentView.mm +14 -0
  29. package/ios/OldArch/StripeSdkEventEmitterCompat.h +1 -0
  30. package/ios/OldArch/StripeSdkEventEmitterCompat.m +6 -0
  31. package/ios/PaymentOptionDisplayData+ReactNative.swift +1 -1
  32. package/ios/PaymentSheetAppearance.swift +1 -1
  33. package/ios/StripeOnrampSdk.mm +19 -0
  34. package/ios/StripeSdk.mm +17 -4
  35. package/ios/StripeSdkEmitter.swift +1 -0
  36. package/ios/StripeSdkImpl+Checkout.swift +75 -23
  37. package/ios/StripeSdkImpl+CustomerSheet.swift +1 -1
  38. package/ios/StripeSdkImpl+Embedded.swift +1 -1
  39. package/ios/StripeSdkImpl+PaymentSheet.swift +1 -1
  40. package/ios/StripeSdkImpl.swift +88 -8
  41. package/lib/commonjs/components/PaymentMethodMessagingElement.js +1 -1
  42. package/lib/commonjs/components/PaymentMethodMessagingElement.js.map +1 -1
  43. package/lib/commonjs/hooks/useCheckout.js +1 -1
  44. package/lib/commonjs/hooks/useCheckout.js.map +1 -1
  45. package/lib/commonjs/hooks/useOnramp.js +1 -1
  46. package/lib/commonjs/hooks/useOnramp.js.map +1 -1
  47. package/lib/commonjs/specs/NativeOnrampSdkModule.js.map +1 -1
  48. package/lib/commonjs/specs/NativeStripeSdkModule.js.map +1 -1
  49. package/lib/module/components/PaymentMethodMessagingElement.js +1 -1
  50. package/lib/module/components/PaymentMethodMessagingElement.js.map +1 -1
  51. package/lib/module/hooks/useCheckout.js +1 -1
  52. package/lib/module/hooks/useCheckout.js.map +1 -1
  53. package/lib/module/hooks/useOnramp.js +1 -1
  54. package/lib/module/hooks/useOnramp.js.map +1 -1
  55. package/lib/module/specs/NativeOnrampSdkModule.js.map +1 -1
  56. package/lib/module/specs/NativeStripeSdkModule.js.map +1 -1
  57. package/lib/typescript/src/components/PaymentMethodMessagingElement.d.ts +1 -1
  58. package/lib/typescript/src/components/PaymentMethodMessagingElement.d.ts.map +1 -1
  59. package/lib/typescript/src/hooks/useCheckout.d.ts.map +1 -1
  60. package/lib/typescript/src/hooks/useOnramp.d.ts +22 -0
  61. package/lib/typescript/src/hooks/useOnramp.d.ts.map +1 -1
  62. package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts +3 -0
  63. package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts.map +1 -1
  64. package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts +2 -1
  65. package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts.map +1 -1
  66. package/lib/typescript/src/types/Checkout.d.ts +266 -84
  67. package/lib/typescript/src/types/Checkout.d.ts.map +1 -1
  68. package/lib/typescript/src/types/Onramp.d.ts +94 -0
  69. package/lib/typescript/src/types/Onramp.d.ts.map +1 -1
  70. package/package.json +3 -2
  71. package/src/components/PaymentMethodMessagingElement.tsx +54 -52
  72. package/src/hooks/useCheckout.tsx +20 -4
  73. package/src/hooks/useOnramp.tsx +44 -0
  74. package/src/specs/NativeOnrampSdkModule.ts +5 -0
  75. package/src/specs/NativeStripeSdkModule.ts +8 -1
  76. package/src/types/Checkout.ts +289 -84
  77. package/src/types/Onramp.ts +109 -0
  78. package/stripe-react-native.podspec +1 -1
  79. package/ios/CurrencySelectorElementManager.swift +0 -12
@@ -62,6 +62,7 @@ class StripeSdkPackage : BaseReactPackage() {
62
62
  EmbeddedPaymentElementViewManager(),
63
63
  NavigationBarManager(),
64
64
  PaymentMethodMessagingElementViewManager(),
65
+ CurrencySelectorElementViewManager(),
65
66
  )
66
67
 
67
68
  private fun getOnrampModuleClass(): Class<out NativeModule?>? {
@@ -1,7 +1,6 @@
1
1
  package com.reactnativestripesdk.customersheet
2
2
 
3
3
  import android.app.Activity
4
- import android.app.Application
5
4
  import android.graphics.drawable.Drawable
6
5
  import android.os.Bundle
7
6
  import android.os.Handler
@@ -20,6 +19,7 @@ import com.reactnativestripesdk.buildPaymentSheetAppearance
20
19
  import com.reactnativestripesdk.convertDrawableToBase64
21
20
  import com.reactnativestripesdk.mapToCardBrandAcceptance
22
21
  import com.reactnativestripesdk.utils.CreateTokenErrorType
22
+ import com.reactnativestripesdk.utils.DefaultActivityLifecycleCallbacks
23
23
  import com.reactnativestripesdk.utils.ErrorType
24
24
  import com.reactnativestripesdk.utils.KeepJsAwakeTask
25
25
  import com.reactnativestripesdk.utils.PaymentSheetAppearanceException
@@ -224,7 +224,7 @@ class CustomerSheetManager(
224
224
  private fun presentWithTimeout(timeout: Long) {
225
225
  var activities: MutableList<Activity> = mutableListOf()
226
226
  val activityLifecycleCallbacks =
227
- object : Application.ActivityLifecycleCallbacks {
227
+ object : DefaultActivityLifecycleCallbacks() {
228
228
  override fun onActivityCreated(
229
229
  activity: Activity,
230
230
  savedInstanceState: Bundle?,
@@ -232,20 +232,6 @@ class CustomerSheetManager(
232
232
  activities.add(activity)
233
233
  }
234
234
 
235
- override fun onActivityStarted(activity: Activity) {}
236
-
237
- override fun onActivityResumed(activity: Activity) {}
238
-
239
- override fun onActivityPaused(activity: Activity) {}
240
-
241
- override fun onActivityStopped(activity: Activity) {}
242
-
243
- override fun onActivitySaveInstanceState(
244
- activity: Activity,
245
- outState: Bundle,
246
- ) {
247
- }
248
-
249
235
  override fun onActivityDestroyed(activity: Activity) {
250
236
  activities = mutableListOf()
251
237
  context.currentActivity?.application?.unregisterActivityLifecycleCallbacks(this)
@@ -25,51 +25,70 @@ internal fun mapFromCheckoutState(
25
25
  }
26
26
 
27
27
  @OptIn(CheckoutSessionPreview::class)
28
- private fun mapFromCheckoutSession(
29
- session: CheckoutSession,
30
- ): WritableMap =
28
+ private fun mapFromCheckoutSession(session: CheckoutSession): WritableMap =
31
29
  Arguments.createMap().apply {
32
30
  putString("id", session.id)
33
- // TODO(CheckoutSessionsPreview): Populate from public Android checkout fields once
34
- // stripe-android exposes status, paymentStatus, and livemode on CheckoutSession.
35
- putString("status", "unknown")
36
- putString("paymentStatus", "unknown")
37
31
  putBoolean("livemode", false)
38
32
 
39
33
  if (session.currency.isNotBlank()) {
40
34
  putString("currency", session.currency)
41
35
  }
42
36
 
43
- session.totalSummary?.let { putMap("totals", mapFromCheckoutTotals(it)) }
44
- putArray("lineItems", mapFromCheckoutLineItems(session))
45
- putArray("shippingOptions", mapFromCheckoutShippingOptions(session))
37
+ putMap("status", mapFromStatus(session.status))
38
+ putMap("tax", mapFromTax(session.tax))
39
+ session.totalSummary?.let { putMap("total", mapFromTotal(it)) }
40
+
41
+ putArray("lineItems", mapFromLineItems(session))
42
+ putArray("shippingOptions", mapFromShippingOptions(session))
43
+ putArray("discountAmounts", Arguments.createArray())
44
+ putArray("currencyOptions", Arguments.createArray())
45
+ }
46
46
 
47
- // TODO(CheckoutSessionsPreview): Populate top-level discounts once stripe-android
48
- // exposes them directly instead of only exposing TotalSummary.discountAmounts.
49
- putArray("discounts", Arguments.createArray())
47
+ @OptIn(CheckoutSessionPreview::class)
48
+ private fun mapFromStatus(status: CheckoutSession.Status): WritableMap =
49
+ Arguments.createMap().apply {
50
+ putString(
51
+ "type",
52
+ when (status) {
53
+ CheckoutSession.Status.Open -> "open"
54
+ CheckoutSession.Status.Complete -> "complete"
55
+ CheckoutSession.Status.Expired -> "expired"
56
+ else -> "unknown"
57
+ }
58
+ )
59
+ }
50
60
 
51
- // TODO(CheckoutSessionsPreview): Populate customerId, customerEmail, billingAddress,
52
- // and shippingAddress once stripe-android exposes them on public checkout models.
61
+ @OptIn(CheckoutSessionPreview::class)
62
+ private fun mapFromTax(tax: CheckoutSession.Tax): WritableMap =
63
+ Arguments.createMap().apply {
64
+ putString(
65
+ "status",
66
+ when (tax.status) {
67
+ CheckoutSession.Tax.Status.Ready -> "ready"
68
+ CheckoutSession.Tax.Status.RequiresShippingAddress -> "requiresShippingAddress"
69
+ CheckoutSession.Tax.Status.RequiresBillingAddress -> "requiresBillingAddress"
70
+ else -> "unknown"
71
+ }
72
+ )
53
73
  }
54
74
 
55
75
  @OptIn(CheckoutSessionPreview::class)
56
- private fun mapFromCheckoutTotals(totals: CheckoutSession.TotalSummary): WritableMap {
57
- val taxTotal = totals.taxAmounts.sumOf { it.amount }
76
+ private fun mapFromTotal(totals: CheckoutSession.TotalSummary): WritableMap =
77
+ Arguments.createMap().apply {
78
+ val taxTotal = totals.taxAmounts.sumOf { it.amount }
58
79
 
59
- return Arguments.createMap().apply {
60
- putDouble("subtotal", totals.subtotal.toDouble())
61
- putDouble("total", totals.totalAmountDue.toDouble())
62
- putDouble("due", totals.totalDueToday.toDouble())
63
- // TODO(CheckoutSessionsPreview): Populate the top-level discount total once the
64
- // Android TotalSummary model exposes it directly.
65
- putDouble("discount", 0.0)
66
- putDouble("shipping", (totals.shippingRate?.amount ?: 0L).toDouble())
67
- putDouble("tax", taxTotal.toDouble())
80
+ putMap("subtotal", makeAmount(totals.subtotal))
81
+ putMap("taxExclusive", makeAmount(taxTotal))
82
+ putMap("taxInclusive", makeAmount(0L))
83
+ putMap("shippingRate", makeAmount(totals.shippingRate?.amount ?: 0L))
84
+ putMap("discount", makeAmount(0L))
85
+ putMap("total", makeAmount(totals.totalAmountDue))
86
+ putMap("appliedBalance", makeAmount(0L))
87
+ putBoolean("balanceAppliedToNextInvoice", false)
68
88
  }
69
- }
70
89
 
71
90
  @OptIn(CheckoutSessionPreview::class)
72
- private fun mapFromCheckoutLineItems(session: CheckoutSession): WritableArray =
91
+ private fun mapFromLineItems(session: CheckoutSession): WritableArray =
73
92
  Arguments.createArray().apply {
74
93
  session.lineItems.forEach { item ->
75
94
  pushMap(
@@ -77,25 +96,32 @@ private fun mapFromCheckoutLineItems(session: CheckoutSession): WritableArray =
77
96
  putString("id", item.id)
78
97
  putString("name", item.name)
79
98
  putInt("quantity", item.quantity)
80
- putDouble("unitAmount", (item.unitAmount ?: 0L).toDouble())
81
- putString("currency", session.currency)
99
+ putArray("images", Arguments.createArray())
100
+ putArray("discountAmounts", Arguments.createArray())
101
+ putArray("taxAmounts", Arguments.createArray())
102
+ item.unitAmount?.let { putMap("unitAmount", makeAmount(it)) }
82
103
  },
83
104
  )
84
105
  }
85
106
  }
86
107
 
87
108
  @OptIn(CheckoutSessionPreview::class)
88
- private fun mapFromCheckoutShippingOptions(session: CheckoutSession): WritableArray =
109
+ private fun mapFromShippingOptions(session: CheckoutSession): WritableArray =
89
110
  Arguments.createArray().apply {
90
111
  session.shippingOptions.forEach { option ->
91
112
  pushMap(
92
113
  Arguments.createMap().apply {
93
114
  putString("id", option.id)
94
115
  putString("displayName", option.displayName)
95
- putDouble("amount", option.amount.toDouble())
116
+ putMap("amount", makeAmount(option.amount))
96
117
  putString("currency", session.currency)
97
- option.deliveryEstimate?.let { putString("deliveryEstimate", it) }
98
118
  },
99
119
  )
100
120
  }
101
121
  }
122
+
123
+ private fun makeAmount(minorUnits: Long): WritableMap =
124
+ Arguments.createMap().apply {
125
+ putString("amount", "")
126
+ putDouble("minorUnitsAmount", minorUnits.toDouble())
127
+ }
@@ -0,0 +1,41 @@
1
+ package com.reactnativestripesdk.utils
2
+
3
+ import android.app.Activity
4
+ import android.app.Application
5
+ import android.os.Bundle
6
+
7
+ internal open class DefaultActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {
8
+ override fun onActivityCreated(
9
+ activity: Activity,
10
+ savedInstanceState: Bundle?,
11
+ ) {
12
+ // NO-OP
13
+ }
14
+
15
+ override fun onActivityStarted(activity: Activity) {
16
+ // NO-OP
17
+ }
18
+
19
+ override fun onActivityResumed(activity: Activity) {
20
+ // NO-OP
21
+ }
22
+
23
+ override fun onActivityPaused(activity: Activity) {
24
+ // NO-OP
25
+ }
26
+
27
+ override fun onActivityStopped(activity: Activity) {
28
+ // NO-OP
29
+ }
30
+
31
+ override fun onActivitySaveInstanceState(
32
+ activity: Activity,
33
+ outState: Bundle,
34
+ ) {
35
+ // NO-OP
36
+ }
37
+
38
+ override fun onActivityDestroyed(activity: Activity) {
39
+ // NO-OP
40
+ }
41
+ }
@@ -17,9 +17,11 @@ import com.facebook.react.bridge.Promise;
17
17
  import com.facebook.react.bridge.ReactApplicationContext;
18
18
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
19
19
  import com.facebook.react.bridge.ReactMethod;
20
+ import com.facebook.react.bridge.ReadableArray;
20
21
  import com.facebook.react.bridge.ReadableMap;
21
22
  import com.facebook.react.turbomodule.core.interfaces.TurboModule;
22
23
  import javax.annotation.Nonnull;
24
+ import javax.annotation.Nullable;
23
25
 
24
26
  public abstract class NativeOnrampSdkModuleSpec extends ReactContextBaseJavaModule implements TurboModule {
25
27
  public static final String NAME = "OnrampSdk";
@@ -57,6 +59,18 @@ public abstract class NativeOnrampSdkModuleSpec extends ReactContextBaseJavaModu
57
59
  @DoNotStrip
58
60
  public abstract void attachKycInfo(ReadableMap kycInfo, Promise promise);
59
61
 
62
+ @ReactMethod
63
+ @DoNotStrip
64
+ public abstract void retrieveMissingIdentifiers(Promise promise);
65
+
66
+ @ReactMethod
67
+ @DoNotStrip
68
+ public abstract void submitIdentifiers(ReadableArray identifiers, Promise promise);
69
+
70
+ @ReactMethod
71
+ @DoNotStrip
72
+ public abstract void presentCRSCARFDeclaration(Promise promise);
73
+
60
74
  @ReactMethod
61
75
  @DoNotStrip
62
76
  public abstract void updatePhoneNumber(String phone, Promise promise);
@@ -71,7 +85,7 @@ public abstract class NativeOnrampSdkModuleSpec extends ReactContextBaseJavaModu
71
85
 
72
86
  @ReactMethod
73
87
  @DoNotStrip
74
- public abstract void provideCheckoutClientSecret(String clientSecret);
88
+ public abstract void provideCheckoutClientSecret(@Nullable String clientSecret);
75
89
 
76
90
  @ReactMethod
77
91
  @DoNotStrip
@@ -91,11 +105,11 @@ public abstract class NativeOnrampSdkModuleSpec extends ReactContextBaseJavaModu
91
105
 
92
106
  @ReactMethod
93
107
  @DoNotStrip
94
- public abstract void authenticateUserWithToken(String token, Promise promise);
108
+ public abstract void authenticateUserWithToken(String linkAuthTokenClientSecret, Promise promise);
95
109
 
96
110
  @ReactMethod
97
111
  @DoNotStrip
98
- public abstract void presentKycInfoVerification(ReadableMap updatedAddress, Promise promise);
112
+ public abstract void presentKycInfoVerification(@Nullable ReadableMap updatedAddress, Promise promise);
99
113
 
100
114
  @ReactMethod
101
115
  @DoNotStrip
@@ -335,7 +335,11 @@ public abstract class NativeStripeSdkModuleSpec extends ReactContextBaseJavaModu
335
335
 
336
336
  @ReactMethod
337
337
  @DoNotStrip
338
- public abstract void checkoutRefresh(String sessionKey, Promise promise);
338
+ public abstract void checkoutRunServerUpdateStart(String sessionKey, Promise promise);
339
+
340
+ @ReactMethod
341
+ @DoNotStrip
342
+ public abstract void checkoutRunServerUpdateComplete(String sessionKey, @Nullable String error, Promise promise);
339
343
 
340
344
  @ReactMethod
341
345
  @DoNotStrip
@@ -5,11 +5,20 @@ 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
10
13
  import com.stripe.android.crypto.onramp.model.KycInfo
11
14
  import com.stripe.android.crypto.onramp.model.OnrampConfiguration
12
15
  import com.stripe.android.crypto.onramp.model.PaymentMethodDisplayData
16
+ import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifier
17
+ import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifierAlternativeGroup
18
+ import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifierRequirement
19
+ import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifierRequirements
20
+ import com.stripe.android.crypto.onramp.model.compliance.ComplianceIdentifierType
21
+ import com.stripe.android.crypto.onramp.model.compliance.SubmitIdentifiersResult
13
22
  import com.stripe.android.googlepaylauncher.GooglePayEnvironment
14
23
  import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher
15
24
  import com.stripe.android.link.LinkAppearance
@@ -178,10 +187,110 @@ internal fun mapFromKycInfo(kycInfo: KycInfo): ReadableMap {
178
187
  kycInfo.idNumber?.let { result.putString("idNumber", it) }
179
188
  kycInfo.address?.let { result.putMap("address", mapFromKycAddress(it)) }
180
189
  kycInfo.dateOfBirth?.let { result.putMap("dateOfBirth", mapFromDateOfBirth(it)) }
190
+ kycInfo.birthCountry?.let { result.putString("birthCountry", it.value) }
191
+ kycInfo.birthCity?.let { result.putString("birthCity", it) }
192
+ kycInfo.nationalities?.let {
193
+ result.putArray("nationalities", mapFromCountryCodes(it))
194
+ }
181
195
 
182
196
  return result
183
197
  }
184
198
 
199
+ internal fun mapToComplianceIdentifiers(identifiers: ReadableArray): List<ComplianceIdentifier> {
200
+ val complianceIdentifiers = mutableListOf<ComplianceIdentifier>()
201
+
202
+ for (index in 0 until identifiers.size()) {
203
+ val identifierMap =
204
+ runCatching { identifiers.getMap(index) }
205
+ .getOrNull()
206
+ ?: throw InvalidIdentifiersArrayException()
207
+ val type = identifierMap.getRequiredNormalizedString("type")
208
+ val value = identifierMap.getRequiredNormalizedString("value")
209
+
210
+ complianceIdentifiers.add(
211
+ ComplianceIdentifier()
212
+ .type(ComplianceIdentifierType(type))
213
+ .value(value),
214
+ )
215
+ }
216
+
217
+ return complianceIdentifiers
218
+ }
219
+
220
+ internal fun mapFromComplianceIdentifierRequirements(
221
+ requirements: ComplianceIdentifierRequirements,
222
+ ) = Arguments.createMap().apply {
223
+ putArray("identifiers", mapFromComplianceIdentifierRequirementsList(requirements.identifiers))
224
+ putArray("alternatives", mapFromComplianceIdentifierAlternativeGroups(requirements.alternatives))
225
+ }
226
+
227
+ internal fun mapFromSubmitIdentifiersResult(
228
+ result: SubmitIdentifiersResult,
229
+ ) = Arguments.createMap().apply {
230
+ putBoolean("valid", result.valid)
231
+ putArray("identifiers", mapFromComplianceIdentifierRequirementsList(result.identifiers))
232
+ putArray("alternatives", mapFromComplianceIdentifierAlternativeGroups(result.alternatives))
233
+ putArray("invalidIdentifiers", mapFromComplianceIdentifierTypes(result.invalidIdentifiers))
234
+ }
235
+
236
+ private fun mapFromComplianceIdentifierRequirementsList(
237
+ requirements: List<ComplianceIdentifierRequirement>,
238
+ ): WritableArray =
239
+ Arguments.createArray().apply {
240
+ requirements.forEach { requirement ->
241
+ pushMap(
242
+ Arguments.createMap().apply {
243
+ putString("type", requirement.type.value)
244
+ putString("regulation", requirement.regulation.value)
245
+ },
246
+ )
247
+ }
248
+ }
249
+
250
+ private fun mapFromComplianceIdentifierAlternativeGroups(
251
+ groups: List<ComplianceIdentifierAlternativeGroup>,
252
+ ): WritableArray =
253
+ Arguments.createArray().apply {
254
+ groups.forEach { group ->
255
+ pushMap(
256
+ Arguments.createMap().apply {
257
+ putArray(
258
+ "originalMissingIdentifiers",
259
+ mapFromComplianceIdentifierTypes(group.originalMissingIdentifiers),
260
+ )
261
+ putArray(
262
+ "alternativeMissingIdentifiers",
263
+ mapFromComplianceIdentifierTypes(group.alternativeMissingIdentifiers),
264
+ )
265
+ },
266
+ )
267
+ }
268
+ }
269
+
270
+ private fun mapFromComplianceIdentifierTypes(
271
+ identifierTypes: List<ComplianceIdentifierType>,
272
+ ): WritableArray =
273
+ Arguments.createArray().apply {
274
+ identifierTypes.forEach { identifierType ->
275
+ pushString(identifierType.value)
276
+ }
277
+ }
278
+
279
+ private fun mapFromCountryCodes(
280
+ countryCodes: List<CountryCode>,
281
+ ): WritableArray =
282
+ Arguments.createArray().apply {
283
+ countryCodes.forEach { countryCode ->
284
+ pushString(countryCode.value)
285
+ }
286
+ }
287
+
288
+ private fun ReadableMap.getRequiredNormalizedString(key: String): String =
289
+ getString(key)
290
+ ?.trim()
291
+ ?.takeIf { it.isNotEmpty() }
292
+ ?: throw ComplianceIdentifierFieldException(key)
293
+
185
294
  private fun mapFromKycAddress(address: PaymentSheet.Address): ReadableMap {
186
295
  val result = Arguments.createMap()
187
296
 
@@ -202,3 +311,10 @@ private fun mapFromDateOfBirth(dateOfBirth: DateOfBirth): ReadableMap {
202
311
  result.putInt("year", dateOfBirth.year)
203
312
  return result
204
313
  }
314
+
315
+ internal class ComplianceIdentifierFieldException(
316
+ field: String,
317
+ ) : IllegalArgumentException("Invalid format for field: $field")
318
+
319
+ internal class InvalidIdentifiersArrayException :
320
+ 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
@@ -23,6 +25,7 @@ import com.reactnativestripesdk.utils.createMissingActivityError
23
25
  import com.reactnativestripesdk.utils.createMissingInitError
24
26
  import com.reactnativestripesdk.utils.createOnrampNotConfiguredError
25
27
  import com.reactnativestripesdk.utils.createResult
28
+ import com.reactnativestripesdk.utils.getStringList
26
29
  import com.reactnativestripesdk.utils.getValOr
27
30
  import com.reactnativestripesdk.utils.mapToPaymentSheetAddress
28
31
  import com.stripe.android.crypto.onramp.ExperimentalCryptoOnramp
@@ -37,10 +40,13 @@ import com.stripe.android.crypto.onramp.model.OnrampCheckoutResult
37
40
  import com.stripe.android.crypto.onramp.model.OnrampCollectPaymentMethodResult
38
41
  import com.stripe.android.crypto.onramp.model.OnrampConfigurationResult
39
42
  import com.stripe.android.crypto.onramp.model.OnrampCreateCryptoPaymentTokenResult
43
+ import com.stripe.android.crypto.onramp.model.OnrampCrsCarfDeclarationResult
40
44
  import com.stripe.android.crypto.onramp.model.OnrampHasLinkAccountResult
41
45
  import com.stripe.android.crypto.onramp.model.OnrampLogOutResult
42
46
  import com.stripe.android.crypto.onramp.model.OnrampRegisterLinkUserResult
43
47
  import com.stripe.android.crypto.onramp.model.OnrampRegisterWalletAddressResult
48
+ import com.stripe.android.crypto.onramp.model.OnrampRetrieveMissingIdentifiersResult
49
+ import com.stripe.android.crypto.onramp.model.OnrampSubmitIdentifiersResult
44
50
  import com.stripe.android.crypto.onramp.model.OnrampTokenAuthenticationResult
45
51
  import com.stripe.android.crypto.onramp.model.OnrampUpdatePhoneNumberResult
46
52
  import com.stripe.android.crypto.onramp.model.OnrampVerifyIdentityResult
@@ -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 crsCarfDeclarationPromise: Promise? = null
81
88
 
82
89
  private var checkoutClientSecretDeferred: CompletableDeferred<String>? = null
83
90
  private val rnScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
@@ -141,6 +148,10 @@ class OnrampSdkModule(
141
148
  handleOnrampCheckoutResult(result, checkoutPromise!!)
142
149
  }.verifyKycCallback { result ->
143
150
  handleOnrampKycVerificationResult(result, verifyKycPromise!!)
151
+ }.crsCarfDeclarationCallback { result ->
152
+ crsCarfDeclarationPromise?.let {
153
+ handleOnrampCrsCarfDeclarationResult(result, it)
154
+ }
144
155
  }.onrampSessionClientSecretProvider { sessionId ->
145
156
  checkoutClientSecretDeferred = CompletableDeferred()
146
157
 
@@ -311,10 +322,16 @@ class OnrampSdkModule(
311
322
  )
312
323
  } else {
313
324
  null
314
- }
325
+ }
315
326
 
316
327
  val addressMap = kycInfo.getMap("address")
317
328
  val addressObj = mapToPaymentSheetAddress(addressMap)
329
+ val birthCountry = kycInfo.getString("birthCountry")?.let(CountryCode::create)
330
+ val birthCity = kycInfo.getString("birthCity")
331
+ val nationalities =
332
+ kycInfo.getStringList("nationalities")
333
+ ?.map(CountryCode::create)
334
+ ?.takeIf { it.isNotEmpty() }
318
335
 
319
336
  val kycInfoObj =
320
337
  KycInfo(
@@ -323,6 +340,9 @@ class OnrampSdkModule(
323
340
  idNumber = idNumber,
324
341
  dateOfBirth = dob,
325
342
  address = addressObj,
343
+ birthCountry = birthCountry,
344
+ birthCity = birthCity,
345
+ nationalities = nationalities,
326
346
  )
327
347
 
328
348
  when (val result = coordinator.attachKycInfo(kycInfoObj)) {
@@ -336,6 +356,75 @@ class OnrampSdkModule(
336
356
  }
337
357
  }
338
358
 
359
+ @ReactMethod
360
+ override fun retrieveMissingIdentifiers(promise: Promise) {
361
+ val coordinator =
362
+ onrampCoordinator ?: run {
363
+ promise.resolve(createOnrampNotConfiguredError())
364
+ return
365
+ }
366
+
367
+ CoroutineScope(Dispatchers.IO).launch {
368
+ when (val result = coordinator.retrieveMissingIdentifiers()) {
369
+ is OnrampRetrieveMissingIdentifiersResult.Completed -> {
370
+ promise.resolve(mapFromComplianceIdentifierRequirements(result.requirements))
371
+ }
372
+ is OnrampRetrieveMissingIdentifiersResult.Failed -> {
373
+ promise.resolve(createFailedError(result.error))
374
+ }
375
+ }
376
+ }
377
+ }
378
+
379
+ @ReactMethod
380
+ override fun submitIdentifiers(
381
+ identifiers: ReadableArray,
382
+ promise: Promise,
383
+ ) {
384
+ val coordinator =
385
+ onrampCoordinator ?: run {
386
+ promise.resolve(createOnrampNotConfiguredError())
387
+ return
388
+ }
389
+
390
+ val complianceIdentifiers =
391
+ try {
392
+ mapToComplianceIdentifiers(identifiers)
393
+ } catch (error: IllegalArgumentException) {
394
+ val errorType =
395
+ if (error is ComplianceIdentifierFieldException) {
396
+ ErrorType.Unknown
397
+ } else {
398
+ ErrorType.Failed
399
+ }
400
+ promise.resolve(createError(errorType.toString(), error.message ?: "Invalid identifiers"))
401
+ return
402
+ }
403
+
404
+ CoroutineScope(Dispatchers.IO).launch {
405
+ when (val result = coordinator.submitIdentifiers(complianceIdentifiers)) {
406
+ is OnrampSubmitIdentifiersResult.Completed -> {
407
+ promise.resolve(mapFromSubmitIdentifiersResult(result.result))
408
+ }
409
+ is OnrampSubmitIdentifiersResult.Failed -> {
410
+ promise.resolve(createFailedError(result.error))
411
+ }
412
+ }
413
+ }
414
+ }
415
+
416
+ @ReactMethod
417
+ override fun presentCRSCARFDeclaration(promise: Promise) {
418
+ val presenter =
419
+ onrampPresenter ?: run {
420
+ promise.resolve(createOnrampNotConfiguredError())
421
+ return
422
+ }
423
+
424
+ crsCarfDeclarationPromise = promise
425
+ presenter.presentCrsCarfDeclaration()
426
+ }
427
+
339
428
  @ReactMethod
340
429
  override fun updatePhoneNumber(
341
430
  phone: String,
@@ -689,6 +778,25 @@ class OnrampSdkModule(
689
778
  }
690
779
  }
691
780
 
781
+ private fun handleOnrampCrsCarfDeclarationResult(
782
+ result: OnrampCrsCarfDeclarationResult,
783
+ promise: Promise,
784
+ ) {
785
+ when (result) {
786
+ is OnrampCrsCarfDeclarationResult.Confirmed -> {
787
+ promise.resolve(
788
+ WritableNativeMap().apply { putString("status", "Confirmed") },
789
+ )
790
+ }
791
+ is OnrampCrsCarfDeclarationResult.Cancelled -> {
792
+ promise.resolve(createCanceledError("CRS/CARF declaration was cancelled"))
793
+ }
794
+ is OnrampCrsCarfDeclarationResult.Failed -> {
795
+ promise.resolve(createFailedError(result.error))
796
+ }
797
+ }
798
+ }
799
+
692
800
  private fun handleOnrampCollectPaymentResult(
693
801
  result: OnrampCollectPaymentMethodResult,
694
802
  promise: Promise,
@@ -41,9 +41,13 @@ class DrawableLoadingTest {
41
41
  canvas.drawColor(Color.RED)
42
42
  }
43
43
 
44
- override fun setAlpha(alpha: Int) {}
44
+ override fun setAlpha(alpha: Int) {
45
+ // NO-OP
46
+ }
45
47
 
46
- override fun setColorFilter(colorFilter: ColorFilter?) {}
48
+ override fun setColorFilter(colorFilter: ColorFilter?) {
49
+ // NO-OP
50
+ }
47
51
 
48
52
  override fun getOpacity(): Int = PixelFormat.OPAQUE
49
53
  }