@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 +46 -0
- package/README.md +1 -1
- package/android/build.gradle +1 -1
- package/android/gradle.properties +2 -2
- package/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt +31 -4
- package/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt +1 -1
- package/android/src/main/java/com/reactnativestripesdk/Errors.kt +14 -1
- package/android/src/main/java/com/reactnativestripesdk/GooglePayFragment.kt +68 -52
- package/android/src/main/java/com/reactnativestripesdk/PaymentLauncherFragment.kt +127 -37
- package/android/src/main/java/com/reactnativestripesdk/PaymentSheetAppearance.kt +173 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt +59 -36
- package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +49 -177
- package/ios/PaymentSheetAppearance.swift +209 -0
- package/ios/StripeSdk.swift +9 -5
- package/ios/StripeSdk.xcodeproj/project.pbxproj +2 -2
- package/lib/typescript/example/src/screens/PaymentSheetAppearance.d.ts +3 -0
- package/lib/typescript/src/types/PaymentSheet.d.ts +154 -1
- package/package.json +4 -5
- package/src/types/PaymentSheet.ts +159 -2
- package/stripe-react-native.podspec +1 -1
- package/android/src/main/java/com/reactnativestripesdk/Constants.kt +0 -10
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
|
-
|
|
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.
|
package/android/build.gradle
CHANGED
|
@@ -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.
|
|
2
|
-
StripeSdk_stripeVersion=20.
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
70
|
-
|
|
153
|
+
promise.resolve(createError(ConfirmPaymentErrorType.Canceled.toString(), message = null))
|
|
154
|
+
cleanup()
|
|
71
155
|
}
|
|
72
156
|
is PaymentResult.Failed -> {
|
|
73
|
-
promise
|
|
74
|
-
|
|
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
|
}
|