@stripe/stripe-react-native 0.16.0 → 0.18.1

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 (112) hide show
  1. package/.husky/pre-commit +4 -0
  2. package/CHANGELOG.md +40 -0
  3. package/README.md +1 -1
  4. package/android/build.gradle +1 -1
  5. package/android/gradle.properties +2 -2
  6. package/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt +1 -1
  7. package/android/src/main/java/com/reactnativestripesdk/CollectBankAccountLauncherFragment.kt +8 -1
  8. package/android/src/main/java/com/reactnativestripesdk/FinancialConnectionsSheetFragment.kt +278 -0
  9. package/android/src/main/java/com/reactnativestripesdk/GooglePayFragment.kt +4 -0
  10. package/android/src/main/java/com/reactnativestripesdk/GooglePayPaymentMethodLauncherFragment.kt +6 -2
  11. package/android/src/main/java/com/reactnativestripesdk/PaymentLauncherFragment.kt +9 -11
  12. package/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt +2 -2
  13. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt +3 -1
  14. package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +59 -22
  15. package/android/src/main/java/com/reactnativestripesdk/utils/Errors.kt +4 -0
  16. package/android/src/main/java/com/reactnativestripesdk/utils/Extensions.kt +11 -0
  17. package/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt +5 -4
  18. package/ios/Errors.swift +12 -1
  19. package/ios/FinancialConnections.swift +258 -0
  20. package/ios/Mappers.swift +12 -9
  21. package/ios/StripeSdk.m +10 -1
  22. package/ios/StripeSdk.swift +51 -8
  23. package/lib/commonjs/NativeStripeSdk.js.map +1 -1
  24. package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
  25. package/lib/commonjs/components/AuBECSDebitForm.js.map +1 -1
  26. package/lib/commonjs/components/CardForm.js +1 -1
  27. package/lib/commonjs/components/CardForm.js.map +1 -1
  28. package/lib/commonjs/components/StripeProvider.js +1 -1
  29. package/lib/commonjs/components/StripeProvider.js.map +1 -1
  30. package/lib/commonjs/functions.js +1 -1
  31. package/lib/commonjs/functions.js.map +1 -1
  32. package/lib/commonjs/hooks/useApplePay.js +1 -1
  33. package/lib/commonjs/hooks/useApplePay.js.map +1 -1
  34. package/lib/commonjs/hooks/useConfirmPayment.js +1 -1
  35. package/lib/commonjs/hooks/useConfirmPayment.js.map +1 -1
  36. package/lib/commonjs/hooks/useConfirmSetupIntent.js +1 -1
  37. package/lib/commonjs/hooks/useConfirmSetupIntent.js.map +1 -1
  38. package/lib/commonjs/hooks/useFinancialConnectionsSheet.js +2 -0
  39. package/lib/commonjs/hooks/useFinancialConnectionsSheet.js.map +1 -0
  40. package/lib/commonjs/hooks/useGooglePay.js +1 -1
  41. package/lib/commonjs/hooks/useGooglePay.js.map +1 -1
  42. package/lib/commonjs/hooks/usePaymentSheet.js +1 -1
  43. package/lib/commonjs/hooks/usePaymentSheet.js.map +1 -1
  44. package/lib/commonjs/hooks/useStripe.js +1 -1
  45. package/lib/commonjs/hooks/useStripe.js.map +1 -1
  46. package/lib/commonjs/index.js +1 -1
  47. package/lib/commonjs/index.js.map +1 -1
  48. package/lib/commonjs/plugin/withStripe.js +1 -1
  49. package/lib/commonjs/plugin/withStripe.js.map +1 -1
  50. package/lib/commonjs/types/FinancialConnections.js +2 -0
  51. package/lib/commonjs/types/FinancialConnections.js.map +1 -0
  52. package/lib/commonjs/types/PaymentIntent.js.map +1 -1
  53. package/lib/commonjs/types/SetupIntent.js.map +1 -1
  54. package/lib/commonjs/types/index.js +1 -1
  55. package/lib/commonjs/types/index.js.map +1 -1
  56. package/lib/module/NativeStripeSdk.js.map +1 -1
  57. package/lib/module/components/AuBECSDebitForm.js +1 -1
  58. package/lib/module/components/AuBECSDebitForm.js.map +1 -1
  59. package/lib/module/components/CardForm.js +1 -1
  60. package/lib/module/components/CardForm.js.map +1 -1
  61. package/lib/module/components/StripeProvider.js +1 -1
  62. package/lib/module/components/StripeProvider.js.map +1 -1
  63. package/lib/module/functions.js +1 -1
  64. package/lib/module/functions.js.map +1 -1
  65. package/lib/module/hooks/useApplePay.js +1 -1
  66. package/lib/module/hooks/useApplePay.js.map +1 -1
  67. package/lib/module/hooks/useConfirmPayment.js +1 -1
  68. package/lib/module/hooks/useConfirmPayment.js.map +1 -1
  69. package/lib/module/hooks/useConfirmSetupIntent.js +1 -1
  70. package/lib/module/hooks/useConfirmSetupIntent.js.map +1 -1
  71. package/lib/module/hooks/useFinancialConnectionsSheet.js +2 -0
  72. package/lib/module/hooks/useFinancialConnectionsSheet.js.map +1 -0
  73. package/lib/module/hooks/useGooglePay.js +1 -1
  74. package/lib/module/hooks/useGooglePay.js.map +1 -1
  75. package/lib/module/hooks/usePaymentSheet.js +1 -1
  76. package/lib/module/hooks/usePaymentSheet.js.map +1 -1
  77. package/lib/module/hooks/useStripe.js +1 -1
  78. package/lib/module/hooks/useStripe.js.map +1 -1
  79. package/lib/module/index.js +1 -1
  80. package/lib/module/index.js.map +1 -1
  81. package/lib/module/plugin/withStripe.js +1 -1
  82. package/lib/module/plugin/withStripe.js.map +1 -1
  83. package/lib/module/types/FinancialConnections.js +2 -0
  84. package/lib/module/types/FinancialConnections.js.map +1 -0
  85. package/lib/module/types/PaymentIntent.js.map +1 -1
  86. package/lib/module/types/SetupIntent.js.map +1 -1
  87. package/lib/module/types/index.js +1 -1
  88. package/lib/module/types/index.js.map +1 -1
  89. package/lib/typescript/src/NativeStripeSdk.d.ts +4 -2
  90. package/lib/typescript/src/functions.d.ts +24 -2
  91. package/lib/typescript/src/hooks/useConfirmPayment.d.ts +2 -2
  92. package/lib/typescript/src/hooks/useFinancialConnectionsSheet.d.ts +11 -0
  93. package/lib/typescript/src/hooks/useStripe.d.ts +4 -2
  94. package/lib/typescript/src/index.d.ts +1 -0
  95. package/lib/typescript/src/types/FinancialConnections.d.ts +100 -0
  96. package/lib/typescript/src/types/PaymentIntent.d.ts +1 -0
  97. package/lib/typescript/src/types/SetupIntent.d.ts +1 -0
  98. package/lib/typescript/src/types/Token.d.ts +18 -7
  99. package/lib/typescript/src/types/index.d.ts +2 -1
  100. package/package.json +10 -12
  101. package/src/NativeStripeSdk.tsx +10 -2
  102. package/src/functions.ts +67 -1
  103. package/src/hooks/useConfirmPayment.tsx +3 -3
  104. package/src/hooks/useFinancialConnectionsSheet.tsx +34 -0
  105. package/src/hooks/useStripe.tsx +24 -2
  106. package/src/index.tsx +1 -0
  107. package/src/types/FinancialConnections.ts +126 -0
  108. package/src/types/PaymentIntent.ts +1 -0
  109. package/src/types/SetupIntent.ts +1 -0
  110. package/src/types/Token.ts +24 -7
  111. package/src/types/index.ts +2 -0
  112. package/stripe-react-native.podspec +1 -1
@@ -5,6 +5,7 @@ import android.content.Intent
5
5
  import android.os.Parcelable
6
6
  import android.util.Log
7
7
  import androidx.appcompat.app.AppCompatActivity
8
+ import androidx.fragment.app.Fragment
8
9
  import com.facebook.react.bridge.*
9
10
  import com.facebook.react.module.annotations.ReactModule
10
11
  import com.reactnativestripesdk.pushprovisioning.PushProvisioningProxy
@@ -36,21 +37,27 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
36
37
  private var stripeAccountId: String? = null
37
38
  private var urlScheme: String? = null
38
39
 
40
+ private var confirmPromise: Promise? = null
41
+ private var confirmPaymentClientSecret: String? = null
42
+
39
43
  private var paymentSheetFragment: PaymentSheetFragment? = null
40
44
  private var googlePayFragment: GooglePayFragment? = null
41
45
  private var paymentLauncherFragment: PaymentLauncherFragment? = null
42
-
43
- private var confirmPromise: Promise? = null
44
- private var confirmPaymentClientSecret: String? = null
46
+ private var collectBankAccountLauncherFragment: CollectBankAccountLauncherFragment? = null
47
+ private var financialConnectionsSheetFragment: FinancialConnectionsSheetFragment? = null
48
+ private val allFragments: List<Fragment?>
49
+ get() = listOf(
50
+ paymentSheetFragment,
51
+ googlePayFragment,
52
+ paymentLauncherFragment,
53
+ collectBankAccountLauncherFragment,
54
+ financialConnectionsSheetFragment
55
+ )
45
56
 
46
57
  private val mActivityEventListener = object : BaseActivityEventListener() {
47
58
  override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
48
59
  if (::stripe.isInitialized) {
49
- // BEGIN - Necessary on older versions of React Native (~0.64 and below)
50
- paymentSheetFragment?.activity?.activityResultRegistry?.dispatchResult(requestCode, resultCode, data)
51
- googlePayFragment?.activity?.activityResultRegistry?.dispatchResult(requestCode, resultCode, data)
52
- paymentLauncherFragment?.activity?.activityResultRegistry?.dispatchResult(requestCode, resultCode, data)
53
- // END
60
+ dispatchActivityResultsToFragments(requestCode, resultCode, data)
54
61
  try {
55
62
  val result = AddPaymentMethodActivityStarter.Result.fromIntent(data)
56
63
  if (data?.getParcelableExtra<Parcelable>("extra_activity_result") != null) {
@@ -67,6 +74,13 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
67
74
  reactContext.addActivityEventListener(mActivityEventListener)
68
75
  }
69
76
 
77
+ // Necessary on older versions of React Native (~0.65 and below)
78
+ private fun dispatchActivityResultsToFragments(requestCode: Int, resultCode: Int, data: Intent?) {
79
+ for (fragment in allFragments) {
80
+ fragment?.activity?.activityResultRegistry?.dispatchResult(requestCode, resultCode, data)
81
+ }
82
+ }
83
+
70
84
  private fun configure3dSecure(params: ReadableMap) {
71
85
  val stripe3dsConfigBuilder = PaymentAuthConfig.Stripe3ds2Config.Builder()
72
86
  if (params.hasKey("timeout")) stripe3dsConfigBuilder.setTimeout(params.getInt("timeout"))
@@ -121,17 +135,14 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
121
135
  @ReactMethod
122
136
  fun initPaymentSheet(params: ReadableMap, promise: Promise) {
123
137
  getCurrentActivityOrResolveWithError(promise)?.let { activity ->
124
- paymentSheetFragment?.let {
125
- // If a payment sheet was already initialized, we want to remove its fragment first
126
- activity.supportFragmentManager.beginTransaction().remove(it).commitAllowingStateLoss()
127
- }
138
+ paymentSheetFragment?.removeFragment(reactApplicationContext)
128
139
  paymentSheetFragment = PaymentSheetFragment(reactApplicationContext, promise).also {
129
140
  val bundle = toBundleObject(params)
130
141
  it.arguments = bundle
131
142
  }
132
143
  try {
133
144
  activity.supportFragmentManager.beginTransaction()
134
- .add(paymentSheetFragment!!, "payment_sheet_launch_fragment")
145
+ .add(paymentSheetFragment!!, PaymentSheetFragment.TAG)
135
146
  .commit()
136
147
  } catch (error: IllegalStateException) {
137
148
  promise.resolve(createError(ErrorType.Failed.toString(), error.message))
@@ -368,12 +379,15 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
368
379
  // }
369
380
 
370
381
  @ReactMethod
371
- fun confirmPayment(paymentIntentClientSecret: String, params: ReadableMap, options: ReadableMap, promise: Promise) {
382
+ fun confirmPayment(paymentIntentClientSecret: String, params: ReadableMap?, options: ReadableMap, promise: Promise) {
372
383
  val paymentMethodData = getMapOrNull(params, "paymentMethodData")
373
- val paymentMethodType = getValOr(params, "paymentMethodType")?.let { mapToPaymentMethodType(it) } ?: run {
374
- promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), "You must provide paymentMethodType"))
375
- return
376
- }
384
+ val paymentMethodType = if (params != null)
385
+ mapToPaymentMethodType(params.getString("paymentMethodType")) ?: run {
386
+ promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), "You must provide paymentMethodType"))
387
+ return
388
+ }
389
+ else
390
+ null // Expect that payment method was attached on the server
377
391
 
378
392
  val testOfflineBank = getBooleanOrFalse(params, "testOfflineBank")
379
393
 
@@ -480,7 +494,7 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
480
494
  getCurrentActivityOrResolveWithError(promise)?.let {
481
495
  try {
482
496
  it.supportFragmentManager.beginTransaction()
483
- .add(fragment, "google_pay_support_fragment")
497
+ .add(fragment, GooglePayPaymentMethodLauncherFragment.TAG)
484
498
  .commit()
485
499
  } catch (error: IllegalStateException) {
486
500
  promise.resolve(createError(ErrorType.Failed.toString(), error.message))
@@ -498,7 +512,7 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
498
512
  getCurrentActivityOrResolveWithError(promise)?.let {
499
513
  try {
500
514
  it.supportFragmentManager.beginTransaction()
501
- .add(googlePayFragment!!, "google_pay_launch_fragment")
515
+ .add(googlePayFragment!!, GooglePayFragment.TAG)
502
516
  .commit()
503
517
  } catch (error: IllegalStateException) {
504
518
  promise.resolve(createError(ErrorType.Failed.toString(), error.message))
@@ -603,9 +617,10 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
603
617
  billingDetails.getString("email")
604
618
  )
605
619
 
606
- val fragment = CollectBankAccountLauncherFragment(
620
+ collectBankAccountLauncherFragment = CollectBankAccountLauncherFragment(
607
621
  reactApplicationContext,
608
622
  publishableKey,
623
+ stripeAccountId,
609
624
  clientSecret,
610
625
  isPaymentIntent,
611
626
  collectParams,
@@ -614,7 +629,7 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
614
629
  getCurrentActivityOrResolveWithError(promise)?.let {
615
630
  try {
616
631
  it.supportFragmentManager.beginTransaction()
617
- .add(fragment, "collect_bank_account_launcher_fragment")
632
+ .add(collectBankAccountLauncherFragment!!, "collect_bank_account_launcher_fragment")
618
633
  .commit()
619
634
  } catch (error: IllegalStateException) {
620
635
  promise.resolve(createError(ErrorType.Failed.toString(), error.message))
@@ -690,6 +705,28 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
690
705
  }
691
706
  }
692
707
 
708
+ @ReactMethod
709
+ fun collectBankAccountToken(clientSecret: String, promise: Promise) {
710
+ if (!::stripe.isInitialized) {
711
+ promise.resolve(createMissingInitError())
712
+ return
713
+ }
714
+ financialConnectionsSheetFragment = FinancialConnectionsSheetFragment().also {
715
+ it.presentFinancialConnectionsSheet(clientSecret, FinancialConnectionsSheetFragment.Mode.ForToken, publishableKey, stripeAccountId, promise, reactApplicationContext)
716
+ }
717
+ }
718
+
719
+ @ReactMethod
720
+ fun collectFinancialConnectionsAccounts(clientSecret: String, promise: Promise) {
721
+ if (!::stripe.isInitialized) {
722
+ promise.resolve(createMissingInitError())
723
+ return
724
+ }
725
+ financialConnectionsSheetFragment = FinancialConnectionsSheetFragment().also {
726
+ it.presentFinancialConnectionsSheet(clientSecret, FinancialConnectionsSheetFragment.Mode.ForSession, publishableKey, stripeAccountId, promise, reactApplicationContext)
727
+ }
728
+ }
729
+
693
730
  /**
694
731
  * Safely get and cast the current activity as an AppCompatActivity. If that fails, the promise
695
732
  * provided will be resolved with an error message instructing the user to retry the method.
@@ -111,3 +111,7 @@ internal fun createError(code: String, error: Throwable): WritableMap {
111
111
  null,
112
112
  null)
113
113
  }
114
+
115
+ internal fun createMissingInitError(): WritableMap {
116
+ return createError(ErrorType.Failed.toString(), "Stripe has not been initialized. Initialize Stripe in your app with the StripeProvider component or the initStripe method.")
117
+ }
@@ -3,6 +3,9 @@ package com.reactnativestripesdk.utils
3
3
  import android.content.Context
4
4
  import android.view.View
5
5
  import android.view.inputmethod.InputMethodManager
6
+ import androidx.appcompat.app.AppCompatActivity
7
+ import androidx.fragment.app.Fragment
8
+ import com.facebook.react.bridge.ReactApplicationContext
6
9
 
7
10
  fun View.showSoftKeyboard() {
8
11
  post {
@@ -19,3 +22,11 @@ fun View.hideSoftKeyboard() {
19
22
  imm?.hideSoftInputFromWindow(windowToken, 0)
20
23
  }
21
24
  }
25
+
26
+ fun Fragment.removeFragment(context: ReactApplicationContext) {
27
+ (context.currentActivity as? AppCompatActivity)?.supportFragmentManager?.let {
28
+ if (it.findFragmentByTag(this.tag) != null) {
29
+ it.beginTransaction().remove(this).commitAllowingStateLoss()
30
+ }
31
+ }
32
+ }
@@ -217,12 +217,11 @@ internal fun mapFromBankAccountStatus(status: BankAccount.Status?): String {
217
217
  }
218
218
 
219
219
  internal fun mapFromBankAccount(bankAccount: BankAccount?): WritableMap? {
220
- val bankAccountMap: WritableMap = WritableNativeMap()
221
-
222
220
  if (bankAccount == null) {
223
221
  return null
224
222
  }
225
223
 
224
+ val bankAccountMap: WritableMap = WritableNativeMap()
226
225
  bankAccountMap.putString("id", bankAccount.id)
227
226
  bankAccountMap.putString("bankName", bankAccount.bankName)
228
227
  bankAccountMap.putString("accountHolderName", bankAccount.accountHolderName)
@@ -231,6 +230,8 @@ internal fun mapFromBankAccount(bankAccount: BankAccount?): WritableMap? {
231
230
  bankAccountMap.putString("country", bankAccount.countryCode)
232
231
  bankAccountMap.putString("routingNumber", bankAccount.routingNumber)
233
232
  bankAccountMap.putString("status", mapFromBankAccountStatus(bankAccount.status))
233
+ bankAccountMap.putString("fingerprint", bankAccount.fingerprint)
234
+ bankAccountMap.putString("last4", bankAccount.last4)
234
235
 
235
236
  return bankAccountMap
236
237
  }
@@ -312,13 +313,13 @@ internal fun mapFromCard(card: Card?): WritableMap? {
312
313
 
313
314
  internal fun mapFromToken(token: Token): WritableMap {
314
315
  val tokenMap: WritableMap = WritableNativeMap()
315
-
316
316
  tokenMap.putString("id", token.id)
317
- tokenMap.putString("created", token.created.time.toString())
317
+ tokenMap.putDouble("created", token.created.time.toDouble())
318
318
  tokenMap.putString("type", mapTokenType(token.type))
319
319
  tokenMap.putBoolean("livemode", token.livemode)
320
320
  tokenMap.putMap("bankAccount", mapFromBankAccount(token.bankAccount))
321
321
  tokenMap.putMap("card", mapFromCard(token.card))
322
+ tokenMap.putBoolean("used", token.used)
322
323
 
323
324
  return tokenMap
324
325
  }
package/ios/Errors.swift CHANGED
@@ -1,4 +1,5 @@
1
1
  import Stripe
2
+ @_spi(STP) import StripeCore
2
3
 
3
4
  enum ErrorType {
4
5
  static let Failed = "Failed"
@@ -50,7 +51,7 @@ class Errors {
50
51
  class func createError (_ code: String, _ error: NSError?) -> NSDictionary {
51
52
  let value: NSDictionary = [
52
53
  "code": code,
53
- "message": error?.userInfo[STPError.errorMessageKey] ?? NSNull(),
54
+ "message": error?.userInfo[STPError.errorMessageKey] ?? error?.localizedDescription ?? NSNull(),
54
55
  "localizedMessage": error?.localizedDescription ?? NSNull(),
55
56
  "declineCode": error?.userInfo[STPError.stripeDeclineCodeKey] ?? NSNull(),
56
57
  "stripeErrorCode": error?.userInfo[STPError.stripeErrorCodeKey] ?? NSNull(),
@@ -84,5 +85,15 @@ class Errors {
84
85
 
85
86
  return ["error": value]
86
87
  }
88
+
89
+ class func createError(_ code: String, _ error: Error) -> NSDictionary {
90
+ if let stripeError = error as? StripeError {
91
+ return createError(code, NSError.stp_error(from: stripeError))
92
+ }
93
+
94
+ return createError(code, error as NSError)
95
+ }
96
+
97
+ static let MISSING_INIT_ERROR = Errors.createError(ErrorType.Failed, "Stripe has not been initialized. Initialize Stripe in your app with the StripeProvider component or the initStripe method.")
87
98
  }
88
99
 
@@ -0,0 +1,258 @@
1
+ //
2
+ // FinancialConnections.swift
3
+ // stripe-react-native
4
+ //
5
+ // Created by Charles Cruzan on 7/12/22.
6
+ //
7
+
8
+ import Foundation
9
+ import StripeFinancialConnections
10
+ import Stripe
11
+
12
+ class FinancialConnections {
13
+
14
+ internal static func present(
15
+ withClientSecret: String,
16
+ resolve: @escaping RCTPromiseResolveBlock
17
+ ) -> Void {
18
+ DispatchQueue.main.async {
19
+ FinancialConnectionsSheet(financialConnectionsSessionClientSecret: withClientSecret).present(
20
+ from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController()),
21
+ completion: { result in
22
+ switch result {
23
+ case .completed(session: let session):
24
+ resolve([ "session": mapFromSessionResult(session) ])
25
+ case .canceled:
26
+ resolve(Errors.createError(ErrorType.Canceled, "The flow has been canceled."))
27
+ case .failed(let error):
28
+ resolve(Errors.createError(ErrorType.Failed, error))
29
+ }
30
+ })
31
+ }
32
+ }
33
+
34
+ internal static func presentForToken(
35
+ withClientSecret: String,
36
+ resolve: @escaping RCTPromiseResolveBlock
37
+ ) -> Void {
38
+ DispatchQueue.main.async {
39
+ FinancialConnectionsSheet(financialConnectionsSessionClientSecret: withClientSecret).presentForToken(
40
+ from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController()),
41
+ completion: { result in
42
+ switch result {
43
+ case .completed(result: let result):
44
+ resolve(
45
+ [
46
+ "session": mapFromSessionResult(result.session),
47
+ "token" : mapFromTokenResult(result.token)
48
+ ]
49
+ )
50
+ case .canceled:
51
+ resolve(Errors.createError(ErrorType.Canceled, "The flow has been canceled."))
52
+ case .failed(let error):
53
+ resolve(Errors.createError(ErrorType.Failed, error))
54
+ }
55
+ })
56
+ }
57
+ }
58
+
59
+ internal static func mapFromSessionResult(
60
+ _ session: StripeAPI.FinancialConnectionsSession
61
+ ) -> NSDictionary {
62
+ return [
63
+ "id": session.id,
64
+ "clientSecret": session.clientSecret,
65
+ "livemode": session.livemode,
66
+ "accounts": mapFromAccountsList(accounts: session.accounts)
67
+ ]
68
+ }
69
+
70
+ internal static func mapFromTokenResult(
71
+ _ token: StripeAPI.BankAccountToken?
72
+ ) -> NSDictionary {
73
+ return [
74
+ "bankAccount": mapFromBankAccount(bankAccount: token?.bankAccount) ?? NSNull(),
75
+ "livemode": token?.livemode ?? false,
76
+ "id": token?.id ?? NSNull(),
77
+ "used": token?.used ?? false,
78
+ "type": Mappers.mapFromTokenType(STPTokenType.bankAccount) ?? NSNull(),
79
+ "created": NSNull(), // Doesn't exist on StripeAPI.BankAccountToken
80
+ ]
81
+ }
82
+
83
+ internal static func mapFromBankAccount(
84
+ bankAccount: StripeAPI.BankAccountToken.BankAccount?
85
+ ) -> NSDictionary? {
86
+ guard let bankAccount = bankAccount else {
87
+ return nil
88
+ }
89
+ // return Mappers.mapFromBankAccount(bankAccount) Cannot use this since it expects an STPBankAccount
90
+ return [
91
+ "id": bankAccount.id,
92
+ "bankName": bankAccount.bankName ?? NSNull(),
93
+ "accountHolderName": bankAccount.accountHolderName ?? NSNull(),
94
+ "accountHolderType": NSNull(), // Doesn't exist on StripeAPI.BankAccountToken
95
+ "currency": bankAccount.currency,
96
+ "country": bankAccount.country,
97
+ "routingNumber": bankAccount.routingNumber ?? NSNull(),
98
+ "fingerprint": bankAccount.fingerprint ?? NSNull(),
99
+ "last4": bankAccount.last4,
100
+ "status": bankAccount.status.prefix(1).uppercased() + bankAccount.status.lowercased().dropFirst(), // stripe-ios returns a string, not STPBankAccountStatus
101
+ ]
102
+ }
103
+
104
+ internal static func mapFromAccountsList(
105
+ accounts: StripeAPI.FinancialConnectionsSession.AccountList
106
+ ) -> [[String: Any]] {
107
+ var result = [[String: Any]]()
108
+
109
+ for account in accounts.data {
110
+ result.append([
111
+ "id": account.id,
112
+ "livemode": account.livemode,
113
+ "displayName": account.displayName ?? NSNull(),
114
+ "status": mapFromStatus(account.status),
115
+ "institutionName": account.institutionName,
116
+ "last4": account.last4 ?? NSNull(),
117
+ "created": account.created * 1000,
118
+ "balance": mapFromAccountBalance(balance: account.balance) ?? NSNull(),
119
+ "balanceRefresh": mapFromAccountBalanceRefresh(balanceRefresh: account.balanceRefresh) ?? NSNull(),
120
+ "category": mapFromCategory(account.category),
121
+ "subcategory": mapFromSubcategory(account.subcategory),
122
+ "permissions": account.permissions?.map { mapFromPermission($0) } ?? NSNull(),
123
+ "supportedPaymentMethodTypes": account.supportedPaymentMethodTypes.map { mapFromSupportedPaymentMethodTypes($0) },
124
+ ])
125
+ }
126
+
127
+ return result
128
+ }
129
+
130
+ internal static func mapFromAccountBalance(
131
+ balance: StripeAPI.FinancialConnectionsAccount.Balance?
132
+ ) -> NSDictionary? {
133
+ guard let balance = balance else {
134
+ return nil
135
+ }
136
+
137
+ return [
138
+ "asOf": balance.asOf * 1000,
139
+ "type": mapFromBalanceType(balance.type),
140
+ // TODO: Protected by internal on iOS only. PR is out to fix
141
+ "cash": ["available": NSNull()], // balance.cash?.available
142
+ "credit": ["used": NSNull()], // balance.credit?.used
143
+ "current": balance.current,
144
+ ]
145
+ }
146
+
147
+ internal static func mapFromAccountBalanceRefresh(
148
+ balanceRefresh: StripeAPI.FinancialConnectionsAccount.BalanceRefresh?
149
+ ) -> NSDictionary? {
150
+ guard let balanceRefresh = balanceRefresh else {
151
+ return nil
152
+ }
153
+
154
+ return [
155
+ "status": mapFromBalanceRefreshStatus(balanceRefresh.status),
156
+ "lastAttemptedAt": balanceRefresh.lastAttemptedAt * 1000,
157
+ ]
158
+ }
159
+
160
+ internal static func mapFromStatus( _ status: StripeAPI.FinancialConnectionsAccount.Status) -> String {
161
+ switch status {
162
+ case .active:
163
+ return "active"
164
+ case .inactive:
165
+ return "inactive"
166
+ case .disconnected:
167
+ return "disconnected"
168
+ case .unparsable:
169
+ return "unparsable"
170
+ }
171
+ }
172
+
173
+ internal static func mapFromCategory( _ category: StripeAPI.FinancialConnectionsAccount.Category) -> String {
174
+ switch category {
175
+ case .cash:
176
+ return "cash"
177
+ case .credit:
178
+ return "credit"
179
+ case .investment:
180
+ return "investment"
181
+ case .other:
182
+ return "other"
183
+ case .unparsable:
184
+ return "unparsable"
185
+ }
186
+ }
187
+
188
+ internal static func mapFromSubcategory( _ subcategory: StripeAPI.FinancialConnectionsAccount.Subcategory) -> String {
189
+ switch subcategory {
190
+ case .savings:
191
+ return "savings"
192
+ case .mortgage:
193
+ return "mortgage"
194
+ case .checking:
195
+ return "checking"
196
+ case .creditCard:
197
+ return "creditCard"
198
+ case .lineOfCredit:
199
+ return "lineOfCredit"
200
+ case .other:
201
+ return "other"
202
+ case .unparsable:
203
+ return "unparsable"
204
+ }
205
+ }
206
+
207
+ internal static func mapFromPermission( _ permission: StripeAPI.FinancialConnectionsAccount.Permissions) -> String {
208
+ switch permission {
209
+ case .transactions:
210
+ return "transactions"
211
+ case .ownership:
212
+ return "ownership"
213
+ case .paymentMethod:
214
+ return "paymentMethod"
215
+ case .accountNumbers:
216
+ return "accountNumbers"
217
+ case .balances:
218
+ return "balances"
219
+ case .unparsable:
220
+ return "unparsable"
221
+ }
222
+ }
223
+
224
+ internal static func mapFromSupportedPaymentMethodTypes( _ type: StripeAPI.FinancialConnectionsAccount.SupportedPaymentMethodTypes) -> String {
225
+ switch type {
226
+ case .usBankAccount:
227
+ return "usBankAccount"
228
+ case .link:
229
+ return "link"
230
+ case .unparsable:
231
+ return "unparsable"
232
+ }
233
+ }
234
+
235
+ internal static func mapFromBalanceType( _ type: StripeAPI.FinancialConnectionsAccount.Balance.ModelType) -> String {
236
+ switch type {
237
+ case .cash:
238
+ return "cash"
239
+ case .credit:
240
+ return "credit"
241
+ case .unparsable:
242
+ return "unparsable"
243
+ }
244
+ }
245
+
246
+ internal static func mapFromBalanceRefreshStatus( _ status: StripeAPI.FinancialConnectionsAccount.BalanceRefresh.Status) -> String {
247
+ switch status {
248
+ case .succeeded:
249
+ return "succeeded"
250
+ case .pending:
251
+ return "pending"
252
+ case .failed:
253
+ return "failed"
254
+ case .unparsable:
255
+ return "unparsable"
256
+ }
257
+ }
258
+ }
package/ios/Mappers.swift CHANGED
@@ -50,18 +50,21 @@ class Mappers {
50
50
  }
51
51
 
52
52
  class func mapFromBankAccount(_ bankAccount: STPBankAccount?) -> NSDictionary? {
53
- if (bankAccount == nil) {
53
+ guard let bankAccount = bankAccount else {
54
54
  return nil
55
55
  }
56
+
56
57
  let result: NSDictionary = [
57
- "id": bankAccount?.stripeID ?? NSNull(),
58
- "bankName": bankAccount?.bankName ?? NSNull(),
59
- "accountHolderName": bankAccount?.accountHolderName ?? NSNull(),
60
- "accountHolderType": mapFromBankAccountHolderType(bankAccount?.accountHolderType) ?? NSNull(),
61
- "country": bankAccount?.country ?? NSNull(),
62
- "currency": bankAccount?.currency ?? NSNull(),
63
- "routingNumber": bankAccount?.routingNumber ?? NSNull(),
64
- "status": mapFromBankAccountStatus(bankAccount?.status) ?? NSNull(),
58
+ "id": bankAccount.stripeID,
59
+ "bankName": bankAccount.bankName ?? NSNull(),
60
+ "accountHolderName": bankAccount.accountHolderName ?? NSNull(),
61
+ "accountHolderType": mapFromBankAccountHolderType(bankAccount.accountHolderType) ?? NSNull(),
62
+ "country": bankAccount.country ?? NSNull(),
63
+ "currency": bankAccount.currency ?? NSNull(),
64
+ "routingNumber": bankAccount.routingNumber ?? NSNull(),
65
+ "status": mapFromBankAccountStatus(bankAccount.status) ?? NSNull(),
66
+ "fingerprint": bankAccount.fingerprint ?? NSNull(),
67
+ "last4": bankAccount.last4 ?? NSNull()
65
68
  ]
66
69
  return result
67
70
  }
package/ios/StripeSdk.m CHANGED
@@ -131,5 +131,14 @@ RCT_EXTERN_METHOD(
131
131
  resolver: (RCTPromiseResolveBlock)resolve
132
132
  rejecter: (RCTPromiseRejectBlock)reject
133
133
  )
134
-
134
+ RCT_EXTERN_METHOD(
135
+ collectBankAccountToken:(NSString *)clientSecret
136
+ resolver: (RCTPromiseResolveBlock)resolve
137
+ rejecter: (RCTPromiseRejectBlock)reject
138
+ )
139
+ RCT_EXTERN_METHOD(
140
+ collectFinancialConnectionsAccounts:(NSString *)clientSecret
141
+ resolver: (RCTPromiseResolveBlock)resolve
142
+ rejecter: (RCTPromiseRejectBlock)reject
143
+ )
135
144
  @end
@@ -1,5 +1,6 @@
1
1
  import PassKit
2
2
  import Stripe
3
+ import StripeFinancialConnections
3
4
 
4
5
  @objc(StripeSdk)
5
6
  class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionViewControllerDelegate, UIAdaptivePresentationControllerDelegate {
@@ -800,7 +801,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi
800
801
  @objc(confirmPayment:data:options:resolver:rejecter:)
801
802
  func confirmPayment(
802
803
  paymentIntentClientSecret: String,
803
- params: NSDictionary,
804
+ params: NSDictionary?,
804
805
  options: NSDictionary,
805
806
  resolver resolve: @escaping RCTPromiseResolveBlock,
806
807
  rejecter reject: @escaping RCTPromiseRejectBlock
@@ -808,13 +809,13 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi
808
809
  self.confirmPaymentResolver = resolve
809
810
  self.confirmPaymentClientSecret = paymentIntentClientSecret
810
811
 
811
- let paymentMethodData = params["paymentMethodData"] as? NSDictionary
812
- let type = Mappers.mapToPaymentMethodType(type: params["paymentMethodType"] as? String)
813
- guard let paymentMethodType = type else {
814
- resolve(Errors.createError(ErrorType.Failed, "You must provide paymentMethodType"))
812
+ let paymentMethodData = params?["paymentMethodData"] as? NSDictionary
813
+ let (missingPaymentMethodError, paymentMethodType) = getPaymentMethodType(params: params)
814
+ if (missingPaymentMethodError != nil) {
815
+ resolve(missingPaymentMethodError)
815
816
  return
816
817
  }
817
-
818
+
818
819
  if (paymentMethodType == .FPX) {
819
820
  let testOfflineBank = paymentMethodData?["testOfflineBank"] as? Bool
820
821
  if (testOfflineBank == false || testOfflineBank == nil) {
@@ -831,10 +832,24 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi
831
832
  STPPaymentHandler.shared().confirmPayment(paymentIntentParams, with: self, completion: onCompleteConfirmPayment)
832
833
  }
833
834
  }
835
+
836
+ func getPaymentMethodType(
837
+ params: NSDictionary?
838
+ ) -> (NSDictionary?, STPPaymentMethodType?) {
839
+ if let params = params {
840
+ guard let paymentMethodType = Mappers.mapToPaymentMethodType(type: params["paymentMethodType"] as? String) else {
841
+ return (Errors.createError(ErrorType.Failed, "You must provide paymentMethodType"), nil)
842
+ }
843
+ return (nil, paymentMethodType)
844
+ } else {
845
+ // If params aren't provided, it means we expect that the payment method was attached on the server side
846
+ return (nil, nil)
847
+ }
848
+ }
834
849
 
835
850
  func createPaymentIntentParams(
836
851
  paymentIntentClientSecret: String,
837
- paymentMethodType: STPPaymentMethodType,
852
+ paymentMethodType: STPPaymentMethodType?,
838
853
  paymentMethodData: NSDictionary?,
839
854
  options: NSDictionary
840
855
  ) -> (NSDictionary?, STPPaymentIntentParams) {
@@ -846,6 +861,8 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi
846
861
  if (paymentMethodType == .USBankAccount && paymentMethodData == nil) {
847
862
  return STPPaymentIntentParams(clientSecret: paymentIntentClientSecret, paymentMethodType: .USBankAccount)
848
863
  } else {
864
+ guard let paymentMethodType = paymentMethodType else { return STPPaymentIntentParams(clientSecret: paymentIntentClientSecret) }
865
+
849
866
  let paymentMethodId = paymentMethodData?["paymentMethodId"] as? String
850
867
  let parameters = STPPaymentIntentParams(clientSecret: paymentIntentClientSecret)
851
868
 
@@ -1023,7 +1040,33 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi
1023
1040
  }
1024
1041
  resolve(["isInWallet": PushProvisioningUtils.passExistsWith(last4: last4)])
1025
1042
  }
1026
-
1043
+
1044
+ @objc(collectBankAccountToken:resolver:rejecter:)
1045
+ func collectBankAccountToken(
1046
+ clientSecret: String,
1047
+ resolver resolve: @escaping RCTPromiseResolveBlock,
1048
+ rejecter reject: @escaping RCTPromiseRejectBlock
1049
+ ) -> Void {
1050
+ if (STPAPIClient.shared.publishableKey == nil) {
1051
+ resolve(Errors.MISSING_INIT_ERROR)
1052
+ return
1053
+ }
1054
+ FinancialConnections.presentForToken(withClientSecret: clientSecret, resolve: resolve)
1055
+ }
1056
+
1057
+ @objc(collectFinancialConnectionsAccounts:resolver:rejecter:)
1058
+ func collectFinancialConnectionsAccounts(
1059
+ clientSecret: String,
1060
+ resolver resolve: @escaping RCTPromiseResolveBlock,
1061
+ rejecter reject: @escaping RCTPromiseRejectBlock
1062
+ ) -> Void {
1063
+ if (STPAPIClient.shared.publishableKey == nil) {
1064
+ resolve(Errors.MISSING_INIT_ERROR)
1065
+ return
1066
+ }
1067
+ FinancialConnections.present(withClientSecret: clientSecret, resolve: resolve)
1068
+ }
1069
+
1027
1070
  func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
1028
1071
  confirmPaymentResolver?(Errors.createError(ErrorType.Canceled, "FPX Payment has been canceled"))
1029
1072
  }