@stripe/stripe-react-native 0.9.0 → 0.12.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.12.0
6
+
7
+ ### Breaking changes
8
+
9
+ - Renamed `appearance.shapes.shadow.borderRadius` to `appearance.shapes.shadow.blurRadius`, and `appearance.primaryButton.shapes.shadow.borderRadius` to `appearance.primaryButton.shapes.shadow.blurRadius`. [#962](https://github.com/stripe/stripe-react-native/pull/962)
10
+
11
+ ### New features
12
+
13
+ ### Fixes
14
+
15
+ - Fixed cases where Android apps would crash with the error: `Unable to instantiate fragment com.reactnativestripesdk.PaymentLauncherFragment`. [#965](https://github.com/stripe/stripe-react-native/pull/965)
16
+ - Fixed `appearance.shapes.shadow.offset` and `appearance.primaryButton.shapes.shadow.offset` not applying the y-coordinate in the correct direction. [#962](https://github.com/stripe/stripe-react-native/pull/962)
17
+ - Fixed a bug where `handleNextAction` wouldn't resolve on Android when using 3DS2. [#966](https://github.com/stripe/stripe-react-native/pull/966)
18
+ - Fixed a bug where the wrong CVC icon was show in the `CardForm` component on Android. [#966](https://github.com/stripe/stripe-react-native/pull/966)
19
+ - The card brand tint color is now correctly set in the `CardField` component on Android via the `cardStyle.textColor` prop. [#851](https://github.com/stripe/stripe-react-native/pull/851)
20
+
21
+ ## 0.11.0
22
+
23
+ ### Breaking changes
24
+
25
+ - Removed support for `primaryButtonColor` field on `initPaymentSheet()`. Please use the new `appearance.primaryButton.colors.background` field instead. [#940](https://github.com/stripe/stripe-react-native/pull/940)
26
+
27
+ ### New features
28
+
29
+ - You can now customize the appearance of your Payment Sheet via the `appearance` field on `initPaymentSheet()`. [#940](https://github.com/stripe/stripe-react-native/pull/940)
30
+ - Added Affirm and AU BECS Direct Debit support to Payment Sheet. [#940](https://github.com/stripe/stripe-react-native/pull/940)
31
+
32
+ ### Fixes
33
+
34
+ - Improved error messages on Android for failed `confirmPayment` and `confirmSetupIntent` calls, and any Google Pay related methods. [#957](https://github.com/stripe/stripe-react-native/pull/957)
35
+ - Made Android card validation state consistent with iOS in the `CardField` `onCardChange` callback. [#958](https://github.com/stripe/stripe-react-native/pull/958)
36
+
37
+ ## 0.10.0
38
+
39
+ ### Breaking changes
40
+
41
+ ### New features
42
+
43
+ - Card scanning is available in payment sheet on Android. [#944](https://github.com/stripe/stripe-react-native/pull/944)
44
+ - To enable this, you will need to add `implementation 'com.stripe:stripecardscan:20.3.+'` to your `dependencies {}` block in `android/app/build.gradle`.
45
+ - `us_bank_account` payment method is now available in the payment sheet on Android. [#944](https://github.com/stripe/stripe-react-native/pull/944)
46
+
47
+ ### Fixes
48
+
3
49
  ## 0.9.0
4
50
 
5
51
  - [#913](https://github.com/stripe/stripe-react-native/pull/913) BREAKING CHANGE: Changed props for the `<AddToWalletButton />` component. Instead of passing `cardHolderName`, `cardLastFour`, `cardDescription`, and `cardBrand` directly as props, you will instead pass a `cardDetails` prop, which is an object containing the following fields:
package/README.md CHANGED
@@ -96,7 +96,7 @@ implementation 'com.google.android.material:material:<version>'
96
96
 
97
97
  #### iOS
98
98
 
99
- Compatible with apps targeting iOS 12 or above.
99
+ The Stripe React Native SDK requires Xcode 13.2.1 or later and is compatible with apps targeting iOS 12 or above. For iOS 11 support, please use [`@stripe/stripe-react-native@0.5.0`](https://github.com/stripe/stripe-react-native/releases/tag/v0.5.0).
100
100
 
101
101
  The SDK uses TypeScript features available in Babel version `7.9.0` and above.
102
102
  Alternatively use the `plugin-transform-typescript` plugin in your project.
@@ -135,7 +135,7 @@ dependencies {
135
135
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
136
136
  implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
137
137
  implementation "com.stripe:stripe-android:$stripe_version"
138
- implementation "com.stripe:connections:$stripe_version"
138
+ implementation "com.stripe:financial-connections:$stripe_version"
139
139
  implementation 'com.google.android.material:material:1.3.0'
140
140
  implementation 'androidx.appcompat:appcompat:1.4.1'
141
141
  implementation 'androidx.legacy:legacy-support-v4:1.0.0'
@@ -1,2 +1,2 @@
1
- StripeSdk_kotlinVersion=1.5.10
2
- StripeSdk_stripeVersion=20.1.+
1
+ StripeSdk_kotlinVersion=1.6.10
2
+ StripeSdk_stripeVersion=20.5.+
@@ -6,6 +6,7 @@ import android.graphics.Typeface
6
6
  import android.os.Build
7
7
  import android.text.Editable
8
8
  import android.text.TextWatcher
9
+ import android.util.Log
9
10
  import android.widget.FrameLayout
10
11
  import com.facebook.react.bridge.ReadableMap
11
12
  import com.facebook.react.uimanager.ThemedReactContext
@@ -20,6 +21,8 @@ import com.stripe.android.model.PaymentMethodCreateParams
20
21
  import com.stripe.android.view.CardInputListener
21
22
  import com.stripe.android.view.CardInputWidget
22
23
  import com.stripe.android.view.CardValidCallback
24
+ import com.stripe.android.view.StripeEditText
25
+ import java.lang.Exception
23
26
 
24
27
  class CardFieldView(context: ThemedReactContext) : FrameLayout(context) {
25
28
  private var mCardWidget: CardInputWidget = CardInputWidget(context)
@@ -89,7 +92,8 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) {
89
92
  cardInputWidgetBinding.cardNumberEditText,
90
93
  cardInputWidgetBinding.cvcEditText,
91
94
  cardInputWidgetBinding.expiryDateEditText,
92
- cardInputWidgetBinding.postalCodeEditText)
95
+ cardInputWidgetBinding.postalCodeEditText
96
+ )
93
97
 
94
98
  textColor?.let {
95
99
  for (editTextBinding in bindings) {
@@ -105,6 +109,7 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) {
105
109
  for (editTextBinding in bindings) {
106
110
  editTextBinding.setHintTextColor(Color.parseColor(it))
107
111
  }
112
+ setCardBrandTint(Color.parseColor(it))
108
113
  }
109
114
  fontSize?.let {
110
115
  for (editTextBinding in bindings) {
@@ -151,6 +156,19 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) {
151
156
  }
152
157
  }
153
158
 
159
+ private fun setCardBrandTint(color: Int) {
160
+ try {
161
+ cardInputWidgetBinding.cardBrandView::class.java.getDeclaredField("tintColorInt").let { internalTintColor ->
162
+ internalTintColor.isAccessible = true
163
+ internalTintColor.set(cardInputWidgetBinding.cardBrandView, color)
164
+ }
165
+ } catch (e: Exception) {
166
+ Log.e(
167
+ "StripeReactNative",
168
+ "Unable to set card brand tint color: " + e.message)
169
+ }
170
+ }
171
+
154
172
  fun setPlaceHolders(value: ReadableMap) {
155
173
  val numberPlaceholder = getValOr(value, "number", null)
156
174
  val expirationPlaceholder = getValOr(value, "expiration", null)
@@ -228,9 +246,18 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) {
228
246
  }
229
247
 
230
248
  mCardWidget.setCardValidCallback { isValid, invalidFields ->
231
- cardDetails["validNumber"] = if (invalidFields.contains(CardValidCallback.Fields.Number)) "Invalid" else "Valid"
232
- cardDetails["validCVC"] = if (invalidFields.contains(CardValidCallback.Fields.Cvc)) "Invalid" else "Valid"
233
- cardDetails["validExpiryDate"] = if (invalidFields.contains(CardValidCallback.Fields.Expiry)) "Invalid" else "Valid"
249
+ fun getCardValidationState(field: CardValidCallback.Fields, editTextField: StripeEditText): String {
250
+ if (invalidFields.contains(field)) {
251
+ return if (editTextField.shouldShowError) "Invalid"
252
+ else "Incomplete"
253
+ }
254
+ return "Valid"
255
+ }
256
+
257
+ cardDetails["validNumber"] = getCardValidationState(CardValidCallback.Fields.Number, cardInputWidgetBinding.cardNumberEditText)
258
+ cardDetails["validCVC"] = getCardValidationState(CardValidCallback.Fields.Cvc, cardInputWidgetBinding.cvcEditText)
259
+ cardDetails["validExpiryDate"] = getCardValidationState(CardValidCallback.Fields.Expiry, cardInputWidgetBinding.expiryDateEditText)
260
+
234
261
  if (isValid) {
235
262
  onValidCardChange()
236
263
  } else {
@@ -38,7 +38,7 @@ class CardFormViewManager : SimpleViewManager<CardFormView>() {
38
38
 
39
39
  @ReactProp(name = "placeholders")
40
40
  fun setPlaceHolders(view: CardFormView, placeholders: ReadableMap) {
41
- view.setPlaceHolders(placeholders);
41
+ view.setPlaceHolders(placeholders)
42
42
  }
43
43
 
44
44
  @ReactProp(name = "autofocus")
@@ -41,6 +41,8 @@ enum class GooglePayErrorType {
41
41
  Failed, Canceled
42
42
  }
43
43
 
44
+ class PaymentSheetAppearanceException(message: String) : Exception(message)
45
+
44
46
  internal fun mapError(code: String, message: String?, localizedMessage: String?, declineCode: String?, type: String?, stripeErrorCode: String?): WritableMap {
45
47
  val map: WritableMap = WritableNativeMap()
46
48
  val details: WritableMap = WritableNativeMap()
@@ -96,5 +98,16 @@ internal fun createError(code: String, error: Exception): WritableMap {
96
98
  }
97
99
 
98
100
  internal fun createError(code: String, error: Throwable): WritableMap {
99
- return mapError(code, error.message, error.localizedMessage, null, null, null)
101
+ (error as? Exception)?.let {
102
+ return createError(
103
+ code,
104
+ it)
105
+ }
106
+ return mapError(
107
+ code,
108
+ error.message,
109
+ error.localizedMessage,
110
+ null,
111
+ null,
112
+ null)
100
113
  }
@@ -1,44 +1,30 @@
1
1
  package com.reactnativestripesdk
2
2
 
3
- import android.content.Intent
4
3
  import android.os.Bundle
5
4
  import android.view.LayoutInflater
6
5
  import android.view.View
7
6
  import android.view.ViewGroup
8
7
  import android.widget.FrameLayout
9
8
  import androidx.fragment.app.Fragment
10
- import androidx.localbroadcastmanager.content.LocalBroadcastManager
9
+ import com.facebook.react.bridge.Promise
10
+ import com.facebook.react.bridge.WritableNativeMap
11
11
  import com.stripe.android.googlepaylauncher.GooglePayEnvironment
12
12
  import com.stripe.android.googlepaylauncher.GooglePayLauncher
13
13
  import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher
14
14
 
15
- class GooglePayFragment : Fragment() {
15
+ class GooglePayFragment(private val initPromise: Promise) : Fragment() {
16
16
  private var googlePayLauncher: GooglePayLauncher? = null
17
17
  private var googlePayMethodLauncher: GooglePayPaymentMethodLauncher? = null
18
18
  private var isGooglePayMethodLauncherReady: Boolean = false
19
19
  private var isGooglePayLauncherReady: Boolean = false
20
- private lateinit var localBroadcastManager: LocalBroadcastManager
21
-
22
- private fun onGooglePayMethodLauncherReady(isReady: Boolean) {
23
- isGooglePayMethodLauncherReady = true
24
- if (isGooglePayLauncherReady) {
25
- onGooglePayReady(isReady)
26
- }
27
- }
28
-
29
- private fun onGooglePayLauncherReady(isReady: Boolean) {
30
- isGooglePayLauncherReady = true
31
- if (isGooglePayMethodLauncherReady) {
32
- onGooglePayReady(isReady)
33
- }
34
- }
20
+ private var presentPromise: Promise? = null
21
+ private var createPaymentMethodPromise: Promise? = null
35
22
 
36
23
  override fun onCreateView(
37
24
  inflater: LayoutInflater,
38
25
  container: ViewGroup?,
39
26
  savedInstanceState: Bundle?
40
27
  ): View {
41
- localBroadcastManager = LocalBroadcastManager.getInstance(requireContext())
42
28
  return FrameLayout(requireActivity()).also {
43
29
  it.visibility = View.GONE
44
30
  }
@@ -89,74 +75,104 @@ class GooglePayFragment : Fragment() {
89
75
  )
90
76
  }
91
77
 
92
- fun presentForPaymentIntent(clientSecret: String) {
78
+ private fun onGooglePayMethodLauncherReady(isReady: Boolean) {
79
+ isGooglePayMethodLauncherReady = true
80
+ if (isGooglePayLauncherReady) {
81
+ onGooglePayReady(isReady)
82
+ }
83
+ }
84
+
85
+ private fun onGooglePayLauncherReady(isReady: Boolean) {
86
+ isGooglePayLauncherReady = true
87
+ if (isGooglePayMethodLauncherReady) {
88
+ onGooglePayReady(isReady)
89
+ }
90
+ }
91
+
92
+ private fun onGooglePayReady(isReady: Boolean) {
93
+ if (isReady) {
94
+ initPromise.resolve(WritableNativeMap())
95
+ } else {
96
+ initPromise.resolve(
97
+ createError(
98
+ GooglePayErrorType.Failed.toString(),
99
+ "Google Pay is not available on this device. You can use isGooglePaySupported to preemptively check for Google Pay support."
100
+ )
101
+ )
102
+ }
103
+ }
104
+
105
+ fun presentForPaymentIntent(clientSecret: String, promise: Promise) {
93
106
  val launcher = googlePayLauncher ?: run {
94
- val intent = Intent(ON_GOOGLE_PAY_RESULT)
95
- intent.putExtra("error", "GooglePayLauncher is not initialized.")
96
- localBroadcastManager.sendBroadcast(intent)
107
+ promise.resolve(createError(GooglePayErrorType.Failed.toString(), "GooglePay is not initialized."))
97
108
  return
98
109
  }
99
110
  runCatching {
111
+ presentPromise = promise
100
112
  launcher.presentForPaymentIntent(clientSecret)
101
113
  }.onFailure {
102
- val intent = Intent(ON_GOOGLE_PAY_RESULT)
103
- intent.putExtra("error", it.localizedMessage)
104
- localBroadcastManager.sendBroadcast(intent)
114
+ promise.resolve(createError(GooglePayErrorType.Failed.toString(), it))
105
115
  }
106
116
  }
107
117
 
108
- fun presentForSetupIntent(clientSecret: String, currencyCode: String) {
118
+ fun presentForSetupIntent(clientSecret: String, currencyCode: String, promise: Promise) {
109
119
  val launcher = googlePayLauncher ?: run {
110
- val intent = Intent(ON_GOOGLE_PAY_RESULT)
111
- intent.putExtra("error", "GooglePayLauncher is not initialized.")
112
- localBroadcastManager.sendBroadcast(intent)
120
+ promise.resolve(createError(GooglePayErrorType.Failed.toString(), "GooglePay is not initialized."))
113
121
  return
114
122
  }
115
123
  runCatching {
124
+ presentPromise = promise
116
125
  launcher.presentForSetupIntent(clientSecret, currencyCode)
117
126
  }.onFailure {
118
- val intent = Intent(ON_GOOGLE_PAY_RESULT)
119
- intent.putExtra("error", it.localizedMessage)
120
- localBroadcastManager.sendBroadcast(intent)
127
+ promise.resolve(createError(GooglePayErrorType.Failed.toString(), it))
121
128
  }
122
129
  }
123
130
 
124
- fun createPaymentMethod(currencyCode: String, amount: Int) {
131
+ fun createPaymentMethod(currencyCode: String, amount: Int, promise: Promise) {
125
132
  val launcher = googlePayMethodLauncher ?: run {
126
- val intent = Intent(ON_GOOGLE_PAYMENT_METHOD_RESULT)
127
- intent.putExtra("error", "GooglePayPaymentMethodLauncher is not initialized.")
128
- localBroadcastManager.sendBroadcast(intent)
133
+ promise.resolve(createError(GooglePayErrorType.Failed.toString(), "GooglePayPaymentMethodLauncher is not initialized."))
129
134
  return
130
135
  }
131
136
 
132
137
  runCatching {
138
+ createPaymentMethodPromise = promise
133
139
  launcher.present(
134
140
  currencyCode = currencyCode,
135
141
  amount = amount
136
142
  )
137
143
  }.onFailure {
138
- val intent = Intent(ON_GOOGLE_PAYMENT_METHOD_RESULT)
139
- intent.putExtra("error", it.localizedMessage)
140
- localBroadcastManager.sendBroadcast(intent)
144
+ promise.resolve(createError(GooglePayErrorType.Failed.toString(), it))
141
145
  }
142
146
  }
143
147
 
144
- private fun onGooglePayReady(isReady: Boolean) {
145
- val intent = Intent(ON_INIT_GOOGLE_PAY)
146
- intent.putExtra("isReady", isReady)
147
- localBroadcastManager.sendBroadcast(intent)
148
- }
149
-
150
148
  private fun onGooglePayResult(result: GooglePayLauncher.Result) {
151
- val intent = Intent(ON_GOOGLE_PAY_RESULT)
152
- intent.putExtra("paymentResult", result)
153
- localBroadcastManager.sendBroadcast(intent)
149
+ when (result) {
150
+ GooglePayLauncher.Result.Completed -> {
151
+ presentPromise?.resolve(WritableNativeMap())
152
+ }
153
+ GooglePayLauncher.Result.Canceled -> {
154
+ presentPromise?.resolve(createError(GooglePayErrorType.Canceled.toString(), "Google Pay has been canceled"))
155
+ }
156
+ is GooglePayLauncher.Result.Failed -> {
157
+ presentPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), result.error))
158
+ }
159
+ }
160
+ presentPromise = null
154
161
  }
155
162
 
156
163
  private fun onGooglePayResult(result: GooglePayPaymentMethodLauncher.Result) {
157
- val intent = Intent(ON_GOOGLE_PAYMENT_METHOD_RESULT)
158
- intent.putExtra("paymentResult", result)
159
- localBroadcastManager.sendBroadcast(intent)
164
+ when (result) {
165
+ is GooglePayPaymentMethodLauncher.Result.Completed -> {
166
+ createPaymentMethodPromise?.resolve(createResult("paymentMethod", mapFromPaymentMethod(result.paymentMethod)))
167
+ }
168
+ GooglePayPaymentMethodLauncher.Result.Canceled -> {
169
+ createPaymentMethodPromise?.resolve(createError(GooglePayErrorType.Canceled.toString(), "Google Pay has been canceled"))
170
+ }
171
+ is GooglePayPaymentMethodLauncher.Result.Failed -> {
172
+ createPaymentMethodPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), result.error))
173
+ }
174
+ }
175
+ createPaymentMethodPromise = null
160
176
  }
161
177
 
162
178
  private fun mapToGooglePayLauncherBillingAddressConfig(formatString: String, isRequired: Boolean, isPhoneNumberRequired: Boolean): GooglePayLauncher.BillingAddressConfig {
@@ -5,83 +5,171 @@ import android.view.LayoutInflater
5
5
  import android.view.View
6
6
  import android.view.ViewGroup
7
7
  import android.widget.FrameLayout
8
+ import androidx.appcompat.app.AppCompatActivity
8
9
  import androidx.fragment.app.Fragment
9
10
  import com.facebook.react.bridge.Promise
11
+ import com.facebook.react.bridge.ReactApplicationContext
10
12
  import com.stripe.android.ApiResultCallback
11
13
  import com.stripe.android.Stripe
12
14
  import com.stripe.android.model.*
13
15
  import com.stripe.android.payments.paymentlauncher.PaymentLauncher
14
16
  import com.stripe.android.payments.paymentlauncher.PaymentResult
15
17
 
18
+ /**
19
+ * Instances of this class should only be initialized with the companion's helper methods.
20
+ */
16
21
  class PaymentLauncherFragment(
22
+ private val context: ReactApplicationContext,
17
23
  private val stripe: Stripe,
18
24
  private val publishableKey: String,
19
25
  private val stripeAccountId: String?,
26
+ private val promise: Promise,
27
+ // Used when confirming a payment intent
28
+ private val paymentIntentClientSecret: String? = null,
29
+ private val confirmPaymentParams: ConfirmPaymentIntentParams? = null,
30
+ // Used when confirming a setup intent
31
+ private val setupIntentClientSecret: String? = null,
32
+ private val confirmSetupParams: ConfirmSetupIntentParams? = null,
33
+ // Used when handling the next action on a payment intent
34
+ private val handleNextActionClientSecret: String? = null,
20
35
  ) : Fragment() {
21
36
  private lateinit var paymentLauncher: PaymentLauncher
22
- private var clientSecret: String? = null
23
- private var promise: Promise? = null
24
- private var isPaymentIntent: Boolean = true
37
+
38
+ companion object {
39
+ /**
40
+ * Helper-constructor used for confirming payment intents
41
+ */
42
+ fun forPayment(context: ReactApplicationContext,
43
+ stripe: Stripe,
44
+ publishableKey: String,
45
+ stripeAccountId: String?,
46
+ promise: Promise,
47
+ paymentIntentClientSecret: String,
48
+ confirmPaymentParams: ConfirmPaymentIntentParams): PaymentLauncherFragment {
49
+ val paymentLauncherFragment = PaymentLauncherFragment(
50
+ context,
51
+ stripe,
52
+ publishableKey,
53
+ stripeAccountId,
54
+ promise,
55
+ paymentIntentClientSecret = paymentIntentClientSecret,
56
+ confirmPaymentParams = confirmPaymentParams
57
+ )
58
+ addFragment(paymentLauncherFragment, context, promise)
59
+ return paymentLauncherFragment
60
+ }
61
+
62
+ /**
63
+ * Helper-constructor used for confirming setup intents
64
+ */
65
+ fun forSetup(context: ReactApplicationContext,
66
+ stripe: Stripe,
67
+ publishableKey: String,
68
+ stripeAccountId: String?,
69
+ promise: Promise,
70
+ setupIntentClientSecret: String,
71
+ confirmSetupParams: ConfirmSetupIntentParams): PaymentLauncherFragment {
72
+ val paymentLauncherFragment = PaymentLauncherFragment(
73
+ context,
74
+ stripe,
75
+ publishableKey,
76
+ stripeAccountId,
77
+ promise,
78
+ setupIntentClientSecret = setupIntentClientSecret,
79
+ confirmSetupParams = confirmSetupParams
80
+ )
81
+ addFragment(paymentLauncherFragment, context, promise)
82
+ return paymentLauncherFragment
83
+ }
84
+
85
+ /**
86
+ * Helper-constructor used for handling the next action on a payment intent
87
+ */
88
+ fun forNextAction(context: ReactApplicationContext,
89
+ stripe: Stripe,
90
+ publishableKey: String,
91
+ stripeAccountId: String?,
92
+ promise: Promise,
93
+ handleNextActionClientSecret: String): PaymentLauncherFragment {
94
+ val paymentLauncherFragment = PaymentLauncherFragment(
95
+ context,
96
+ stripe,
97
+ publishableKey,
98
+ stripeAccountId,
99
+ promise,
100
+ handleNextActionClientSecret = handleNextActionClientSecret,
101
+ )
102
+ addFragment(paymentLauncherFragment, context, promise)
103
+ return paymentLauncherFragment
104
+ }
105
+
106
+ private fun addFragment(fragment: PaymentLauncherFragment, context: ReactApplicationContext, promise: Promise) {
107
+ (context.currentActivity as? AppCompatActivity)?.let {
108
+ try {
109
+ it.supportFragmentManager.beginTransaction()
110
+ .add(fragment, "payment_launcher_fragment")
111
+ .commit()
112
+ } catch (error: IllegalStateException) {
113
+ promise.resolve(createError(ErrorType.Failed.toString(), error.message))
114
+ }
115
+ } ?: run {
116
+ promise.resolve(createMissingActivityError())
117
+ }
118
+ }
119
+ }
25
120
 
26
121
  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
27
122
  savedInstanceState: Bundle?): View {
28
123
  paymentLauncher = createPaymentLauncher()
124
+ if (paymentIntentClientSecret != null && confirmPaymentParams != null) {
125
+ paymentLauncher.confirm(confirmPaymentParams)
126
+ } else if (setupIntentClientSecret != null && confirmSetupParams != null) {
127
+ paymentLauncher.confirm(confirmSetupParams)
128
+ } else if (handleNextActionClientSecret != null) {
129
+ paymentLauncher.handleNextActionForPaymentIntent(handleNextActionClientSecret)
130
+ } else {
131
+ throw Exception("Invalid parameters provided to PaymentLauncher. Ensure that you are providing the correct client secret and setup params (if necessary).")
132
+ }
29
133
  return FrameLayout(requireActivity()).also {
30
134
  it.visibility = View.GONE
31
135
  }
32
136
  }
33
137
 
34
- fun handleNextActionForPaymentIntent(clientSecret: String, promise: Promise) {
35
- this.clientSecret = clientSecret
36
- this.promise = promise
37
- paymentLauncher.handleNextActionForPaymentIntent(clientSecret)
38
- }
39
-
40
- fun confirm(params: ConfirmPaymentIntentParams, clientSecret: String, promise: Promise) {
41
- this.clientSecret = clientSecret
42
- this.promise = promise
43
- this.isPaymentIntent = true
44
- paymentLauncher.confirm(params)
45
- }
46
-
47
- fun confirm(params: ConfirmSetupIntentParams, clientSecret: String, promise: Promise) {
48
- this.clientSecret = clientSecret
49
- this.promise = promise
50
- this.isPaymentIntent = false
51
- paymentLauncher.confirm(params)
52
- }
53
-
54
138
  private fun createPaymentLauncher(): PaymentLauncher {
55
139
  return PaymentLauncher.create(this, publishableKey, stripeAccountId) { paymentResult ->
56
140
  when (paymentResult) {
57
141
  is PaymentResult.Completed -> {
58
- clientSecret?.let {
59
- if (isPaymentIntent) retrievePaymentIntent(it, stripeAccountId)
60
- else retrieveSetupIntent(it, stripeAccountId)
61
- } ?: run {
62
- promise?.resolve(
63
- createError(ConfirmPaymentErrorType.Failed.toString(), "Client secret must be set before responding to payment results.")
64
- )
65
- ?: throw Exception("Client secret must be set before responding to payment results.")
142
+ if (paymentIntentClientSecret != null) {
143
+ retrievePaymentIntent(paymentIntentClientSecret, stripeAccountId)
144
+ } else if (handleNextActionClientSecret != null) {
145
+ retrievePaymentIntent(handleNextActionClientSecret, stripeAccountId)
146
+ } else if (setupIntentClientSecret != null) {
147
+ retrieveSetupIntent(setupIntentClientSecret, stripeAccountId)
148
+ } else {
149
+ throw Exception("Failed to create Payment Launcher. No client secret provided.")
66
150
  }
67
151
  }
68
152
  is PaymentResult.Canceled -> {
69
- promise?.resolve(createError(ConfirmPaymentErrorType.Canceled.toString(), message = null))
70
- ?: throw Exception("No promise is set to handle payment results.")
153
+ promise.resolve(createError(ConfirmPaymentErrorType.Canceled.toString(), message = null))
154
+ cleanup()
71
155
  }
72
156
  is PaymentResult.Failed -> {
73
- promise?.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), paymentResult.throwable.localizedMessage))
74
- ?: throw Exception("No promise is set to handle payment results.")
157
+ promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), paymentResult.throwable))
158
+ cleanup()
75
159
  }
76
160
  }
77
161
  }
78
162
  }
79
163
 
164
+ private fun cleanup() {
165
+ (context.currentActivity as? AppCompatActivity)?.supportFragmentManager?.beginTransaction()?.remove(this)?.commitAllowingStateLoss()
166
+ }
167
+
80
168
  private fun retrieveSetupIntent(clientSecret: String, stripeAccountId: String?) {
81
- val promise = promise ?: throw Exception("No promise is set to handle payment results.")
82
169
  stripe.retrieveSetupIntent(clientSecret, stripeAccountId, object : ApiResultCallback<SetupIntent> {
83
170
  override fun onError(e: Exception) {
84
171
  promise.resolve(createError(ConfirmSetupIntentErrorType.Failed.toString(), e))
172
+ cleanup()
85
173
  }
86
174
 
87
175
  override fun onSuccess(result: SetupIntent) {
@@ -113,15 +201,16 @@ class PaymentLauncherFragment(
113
201
  promise.resolve(createError(ConfirmSetupIntentErrorType.Unknown.toString(), "unhandled error: ${result.status}"))
114
202
  }
115
203
  }
204
+ cleanup()
116
205
  }
117
206
  })
118
207
  }
119
208
 
120
209
  private fun retrievePaymentIntent(clientSecret: String, stripeAccountId: String?) {
121
- val promise = promise ?: throw Exception("No promise is set to handle payment results.")
122
210
  stripe.retrievePaymentIntent(clientSecret, stripeAccountId, object : ApiResultCallback<PaymentIntent> {
123
211
  override fun onError(e: Exception) {
124
212
  promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), e))
213
+ cleanup()
125
214
  }
126
215
 
127
216
  override fun onSuccess(result: PaymentIntent) {
@@ -153,6 +242,7 @@ class PaymentLauncherFragment(
153
242
  promise.resolve(createError(ConfirmPaymentErrorType.Unknown.toString(), "unhandled error: ${result.status}"))
154
243
  }
155
244
  }
245
+ cleanup()
156
246
  }
157
247
  })
158
248
  }