@stripe/stripe-react-native 0.10.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +3 -3
  3. package/android/gradle.properties +1 -1
  4. package/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt +46 -10
  5. package/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt +5 -0
  6. package/android/src/main/java/com/reactnativestripesdk/CardFormView.kt +8 -0
  7. package/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt +6 -1
  8. package/android/src/main/java/com/reactnativestripesdk/Errors.kt +14 -1
  9. package/android/src/main/java/com/reactnativestripesdk/GooglePayFragment.kt +68 -52
  10. package/android/src/main/java/com/reactnativestripesdk/Mappers.kt +5 -7
  11. package/android/src/main/java/com/reactnativestripesdk/PaymentLauncherFragment.kt +127 -37
  12. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetAppearance.kt +173 -0
  13. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt +60 -36
  14. package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +67 -177
  15. package/ios/CardFieldManager.m +1 -0
  16. package/ios/CardFieldView.swift +6 -0
  17. package/ios/Mappers.swift +8 -9
  18. package/ios/PaymentSheetAppearance.swift +209 -0
  19. package/ios/StripeSdk.swift +141 -116
  20. package/lib/commonjs/components/AddToWalletButton.js +1 -1
  21. package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
  22. package/lib/commonjs/components/ApplePayButton.js +1 -1
  23. package/lib/commonjs/components/ApplePayButton.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/CardField.js +1 -1
  27. package/lib/commonjs/components/CardField.js.map +1 -1
  28. package/lib/commonjs/components/CardForm.js +1 -1
  29. package/lib/commonjs/components/CardForm.js.map +1 -1
  30. package/lib/commonjs/components/GooglePayButton.js +1 -1
  31. package/lib/commonjs/components/GooglePayButton.js.map +1 -1
  32. package/lib/commonjs/components/StripeContainer.js +1 -1
  33. package/lib/commonjs/components/StripeContainer.js.map +1 -1
  34. package/lib/commonjs/functions.js.map +1 -1
  35. package/lib/commonjs/types/components/CardFieldInput.js.map +1 -1
  36. package/lib/commonjs/types/index.js.map +1 -1
  37. package/lib/module/components/AddToWalletButton.js +1 -1
  38. package/lib/module/components/AddToWalletButton.js.map +1 -1
  39. package/lib/module/components/ApplePayButton.js +1 -1
  40. package/lib/module/components/ApplePayButton.js.map +1 -1
  41. package/lib/module/components/AuBECSDebitForm.js +1 -1
  42. package/lib/module/components/AuBECSDebitForm.js.map +1 -1
  43. package/lib/module/components/CardField.js +1 -1
  44. package/lib/module/components/CardField.js.map +1 -1
  45. package/lib/module/components/CardForm.js +1 -1
  46. package/lib/module/components/CardForm.js.map +1 -1
  47. package/lib/module/components/GooglePayButton.js +1 -1
  48. package/lib/module/components/GooglePayButton.js.map +1 -1
  49. package/lib/module/components/StripeContainer.js +1 -1
  50. package/lib/module/components/StripeContainer.js.map +1 -1
  51. package/lib/module/functions.js.map +1 -1
  52. package/lib/module/types/components/CardFieldInput.js.map +1 -1
  53. package/lib/module/types/index.js.map +1 -1
  54. package/lib/typescript/example/src/screens/PaymentSheetAppearance.d.ts +3 -0
  55. package/lib/typescript/src/components/CardField.d.ts +3 -0
  56. package/lib/typescript/src/components/CardForm.d.ts +2 -0
  57. package/lib/typescript/src/types/PaymentSheet.d.ts +154 -1
  58. package/lib/typescript/src/types/Token.d.ts +9 -1
  59. package/lib/typescript/src/types/components/CardFieldInput.d.ts +1 -0
  60. package/lib/typescript/src/types/components/CardFormView.d.ts +6 -0
  61. package/lib/typescript/src/types/index.d.ts +1 -4
  62. package/package.json +1 -1
  63. package/src/components/CardField.tsx +5 -0
  64. package/src/components/CardForm.tsx +6 -0
  65. package/src/functions.ts +1 -1
  66. package/src/types/PaymentSheet.ts +159 -2
  67. package/src/types/Token.ts +13 -1
  68. package/src/types/components/CardFieldInput.ts +1 -0
  69. package/src/types/components/CardFormView.ts +7 -0
  70. package/src/types/index.ts +1 -5
  71. package/stripe-react-native.podspec +1 -1
  72. package/android/src/main/java/com/reactnativestripesdk/Constants.kt +0 -10
@@ -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
  }
@@ -0,0 +1,173 @@
1
+ package com.reactnativestripesdk
2
+
3
+ import android.graphics.Color
4
+ import android.os.Bundle
5
+ import com.stripe.android.paymentsheet.PaymentSheet
6
+
7
+ fun PaymentSheetFragment.buildPaymentSheetAppearance(userParams: Bundle?): PaymentSheet.Appearance {
8
+ val colorParams = userParams?.getBundle(PaymentSheetAppearanceKeys.COLORS)
9
+ val lightColorParams = colorParams?.getBundle(PaymentSheetAppearanceKeys.LIGHT) ?: colorParams
10
+ val darkColorParams = colorParams?.getBundle(PaymentSheetAppearanceKeys.DARK) ?: colorParams
11
+
12
+ return PaymentSheet.Appearance(
13
+ typography = buildTypography(userParams?.getBundle(PaymentSheetAppearanceKeys.FONT)),
14
+ colorsLight = buildColors(lightColorParams, PaymentSheet.Colors.defaultLight),
15
+ colorsDark = buildColors(darkColorParams, PaymentSheet.Colors.defaultDark),
16
+ shapes = buildShapes(userParams?.getBundle(PaymentSheetAppearanceKeys.SHAPES)),
17
+ primaryButton = buildPrimaryButton(userParams?.getBundle(PaymentSheetAppearanceKeys.PRIMARY_BUTTON))
18
+ )
19
+ }
20
+
21
+ private fun PaymentSheetFragment.buildTypography(fontParams: Bundle?): PaymentSheet.Typography {
22
+ return PaymentSheet.Typography.default.copy(
23
+ sizeScaleFactor = getFloatOr(fontParams, PaymentSheetAppearanceKeys.SCALE, PaymentSheet.Typography.default.sizeScaleFactor),
24
+ fontResId = getFontResId(fontParams, PaymentSheetAppearanceKeys.FAMILY, PaymentSheet.Typography.default.fontResId)
25
+ )
26
+ }
27
+
28
+ @Throws(PaymentSheetAppearanceException::class)
29
+ private fun colorFromHexOrDefault(hexString: String?, default: Int): Int {
30
+ return hexString?.trim()?.replace("#","")?.let {
31
+ if (it.length == 6 || it.length == 8) {
32
+ return Color.parseColor("#$it")
33
+ } else throw PaymentSheetAppearanceException("Failed to set Payment Sheet appearance. Expected hex string of length 6 or 8, but received: $it")
34
+ } ?: run {
35
+ return default
36
+ }
37
+ }
38
+
39
+ private fun buildColors(colorParams: Bundle?, default: PaymentSheet.Colors): PaymentSheet.Colors {
40
+ if (colorParams == null) {
41
+ return default
42
+ }
43
+
44
+ return default.copy(
45
+ primary = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.PRIMARY), default.primary),
46
+ surface = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.BACKGROUND), default.surface),
47
+ component = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.COMPONENT_BACKGROUND), default.component),
48
+ componentBorder = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.COMPONENT_BORDER), default.componentBorder),
49
+ componentDivider = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.COMPONENT_DIVIDER), default.componentDivider),
50
+ onComponent = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.COMPONENT_TEXT), default.onComponent),
51
+ onSurface = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.PRIMARY_TEXT), default.onSurface),
52
+ subtitle = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.SECONDARY_TEXT), default.subtitle),
53
+ placeholderText = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.PLACEHOLDER_TEXT), default.placeholderText),
54
+ appBarIcon = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.ICON), default.appBarIcon),
55
+ error = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.ERROR), default.error),
56
+ )
57
+ }
58
+
59
+ private fun buildShapes(shapeParams: Bundle?): PaymentSheet.Shapes {
60
+ return PaymentSheet.Shapes.default.copy(
61
+ cornerRadiusDp = getFloatOr(shapeParams, PaymentSheetAppearanceKeys.BORDER_RADIUS, PaymentSheet.Shapes.default.cornerRadiusDp),
62
+ borderStrokeWidthDp = getFloatOr(shapeParams, PaymentSheetAppearanceKeys.BORDER_WIDTH, PaymentSheet.Shapes.default.borderStrokeWidthDp)
63
+ )
64
+ }
65
+
66
+ private fun PaymentSheetFragment.buildPrimaryButton(params: Bundle?): PaymentSheet.PrimaryButton {
67
+ if (params == null) {
68
+ return PaymentSheet.PrimaryButton()
69
+ }
70
+
71
+ val fontParams = params.getBundle(PaymentSheetAppearanceKeys.FONT) ?: Bundle.EMPTY
72
+ val shapeParams = params.getBundle(PaymentSheetAppearanceKeys.SHAPES) ?: Bundle.EMPTY
73
+ val colorParams = params.getBundle(PaymentSheetAppearanceKeys.COLORS) ?: Bundle.EMPTY
74
+ val lightColorParams = colorParams.getBundle(PaymentSheetAppearanceKeys.LIGHT) ?: colorParams
75
+ val darkColorParams = colorParams.getBundle(PaymentSheetAppearanceKeys.DARK) ?: colorParams
76
+
77
+ return PaymentSheet.PrimaryButton(
78
+ colorsLight = buildPrimaryButtonColors(lightColorParams, PaymentSheet.PrimaryButtonColors.defaultLight),
79
+ colorsDark = buildPrimaryButtonColors(darkColorParams, PaymentSheet.PrimaryButtonColors.defaultDark),
80
+ shape = PaymentSheet.PrimaryButtonShape(
81
+ cornerRadiusDp = getFloatOrNull(shapeParams, PaymentSheetAppearanceKeys.BORDER_RADIUS),
82
+ borderStrokeWidthDp = getFloatOrNull(shapeParams, PaymentSheetAppearanceKeys.BORDER_WIDTH),
83
+ ),
84
+ typography = PaymentSheet.PrimaryButtonTypography(
85
+ fontResId = getFontResId(fontParams, PaymentSheetAppearanceKeys.FAMILY, null)
86
+ )
87
+ )
88
+ }
89
+
90
+ @Throws(PaymentSheetAppearanceException::class)
91
+ private fun buildPrimaryButtonColors(colorParams: Bundle, default: PaymentSheet.PrimaryButtonColors): PaymentSheet.PrimaryButtonColors {
92
+ return PaymentSheet.PrimaryButtonColors(
93
+ background = colorParams.getString(PaymentSheetAppearanceKeys.BACKGROUND)?.trim()?.replace("#", "")?.let {
94
+ if (it.length == 6 || it.length == 8) {
95
+ Color.parseColor("#$it")
96
+ } else throw PaymentSheetAppearanceException("Failed to set Payment Sheet appearance. Expected hex string of length 6 or 8, but received: $it")
97
+ } ?: run {
98
+ null
99
+ },
100
+ onBackground = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.TEXT), default.onBackground),
101
+ border = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.BORDER), default.border),
102
+ )
103
+ }
104
+
105
+ private fun getFloatOr(bundle: Bundle?, key: String, defaultValue: Float): Float {
106
+ return if (bundle?.containsKey(key) == true) {
107
+ bundle.getFloat(key, bundle.getInt(key).toFloat())
108
+ } else {
109
+ defaultValue
110
+ }
111
+ }
112
+
113
+ private fun getFloatOrNull(bundle: Bundle?, key: String): Float? {
114
+ return if (bundle?.containsKey(key) == true) {
115
+ bundle.getFloat(key, bundle.getInt(key).toFloat())
116
+ } else {
117
+ null
118
+ }
119
+ }
120
+
121
+ @Throws(PaymentSheetAppearanceException::class)
122
+ private fun PaymentSheetFragment.getFontResId(bundle: Bundle?, key: String, defaultValue: Int?): Int? {
123
+ val fontErrorPrefix = "Encountered an error when setting a custom font:"
124
+ if (bundle?.containsKey(key) != true) {
125
+ return defaultValue
126
+ }
127
+
128
+ val fontFileName = bundle.getString(key)
129
+ ?: throw PaymentSheetAppearanceException("$fontErrorPrefix expected String for font.$key, but received null.")
130
+ if (Regex("[^a-z0-9]").containsMatchIn(fontFileName)) {
131
+ throw PaymentSheetAppearanceException(
132
+ "$fontErrorPrefix appearance.font.$key should only contain lowercase alphanumeric characters on Android, but received '$fontFileName'. This value must match the filename in android/app/src/main/res/font"
133
+ )
134
+ }
135
+
136
+ val id = resources.getIdentifier(fontFileName, "font", context?.packageName)
137
+ if (id == 0) {
138
+ throw PaymentSheetAppearanceException("$fontErrorPrefix Failed to find font: $fontFileName")
139
+ } else {
140
+ return id
141
+ }
142
+ }
143
+
144
+ private class PaymentSheetAppearanceKeys {
145
+ companion object {
146
+ const val COLORS = "colors"
147
+ const val LIGHT = "light"
148
+ const val DARK = "dark"
149
+ const val PRIMARY = "primary"
150
+ const val BACKGROUND = "background"
151
+ const val COMPONENT_BACKGROUND = "componentBackground"
152
+ const val COMPONENT_BORDER = "componentBorder"
153
+ const val COMPONENT_DIVIDER = "componentDivider"
154
+ const val COMPONENT_TEXT = "componentText"
155
+ const val PRIMARY_TEXT = "primaryText"
156
+ const val SECONDARY_TEXT = "secondaryText"
157
+ const val PLACEHOLDER_TEXT = "placeholderText"
158
+ const val ICON = "icon"
159
+ const val ERROR = "error"
160
+
161
+ const val FONT = "font"
162
+ const val FAMILY = "family"
163
+ const val SCALE = "scale"
164
+
165
+ const val SHAPES = "shapes"
166
+ const val BORDER_RADIUS = "borderRadius"
167
+ const val BORDER_WIDTH = "borderWidth"
168
+
169
+ const val PRIMARY_BUTTON = "primaryButton"
170
+ const val TEXT = "text"
171
+ const val BORDER = "border"
172
+ }
173
+ }
@@ -1,8 +1,6 @@
1
1
  package com.reactnativestripesdk
2
2
 
3
3
  import android.content.Context
4
- import android.content.Intent
5
- import android.content.res.ColorStateList
6
4
  import android.graphics.Bitmap
7
5
  import android.graphics.Canvas
8
6
  import android.graphics.Color
@@ -12,29 +10,37 @@ import android.view.LayoutInflater
12
10
  import android.view.View
13
11
  import android.view.ViewGroup
14
12
  import android.widget.FrameLayout
13
+ import androidx.appcompat.app.AppCompatActivity
15
14
  import androidx.appcompat.content.res.AppCompatResources
16
15
  import androidx.core.graphics.drawable.DrawableCompat
17
16
  import androidx.fragment.app.Fragment
18
- import androidx.localbroadcastmanager.content.LocalBroadcastManager
17
+ import com.facebook.react.bridge.Promise
18
+ import com.facebook.react.bridge.ReactApplicationContext
19
+ import com.facebook.react.bridge.WritableMap
20
+ import com.facebook.react.bridge.WritableNativeMap
19
21
  import com.stripe.android.paymentsheet.PaymentOptionCallback
20
22
  import com.stripe.android.paymentsheet.PaymentSheet
23
+ import com.stripe.android.paymentsheet.PaymentSheetResult
21
24
  import com.stripe.android.paymentsheet.PaymentSheetResultCallback
22
25
  import java.io.ByteArrayOutputStream
23
26
 
24
- class PaymentSheetFragment : Fragment() {
27
+ class PaymentSheetFragment(
28
+ private val context: ReactApplicationContext,
29
+ private val initPromise: Promise
30
+ ) : Fragment() {
25
31
  private var paymentSheet: PaymentSheet? = null
26
32
  private var flowController: PaymentSheet.FlowController? = null
27
33
  private var paymentIntentClientSecret: String? = null
28
34
  private var setupIntentClientSecret: String? = null
29
35
  private lateinit var paymentSheetConfiguration: PaymentSheet.Configuration
30
- private lateinit var localBroadcastManager: LocalBroadcastManager
36
+ private var confirmPromise: Promise? = null
37
+ private var presentPromise: Promise? = null
31
38
 
32
39
  override fun onCreateView(
33
40
  inflater: LayoutInflater,
34
41
  container: ViewGroup?,
35
42
  savedInstanceState: Bundle?
36
43
  ): View {
37
- localBroadcastManager = LocalBroadcastManager.getInstance(requireContext())
38
44
  return FrameLayout(requireActivity()).also {
39
45
  it.visibility = View.GONE
40
46
  }
@@ -50,34 +56,51 @@ class PaymentSheetFragment : Fragment() {
50
56
  val googlePayEnabled = arguments?.getBoolean("googlePay")
51
57
  val testEnv = arguments?.getBoolean("testEnv")
52
58
  val allowsDelayedPaymentMethods = arguments?.getBoolean("allowsDelayedPaymentMethods")
53
- val primaryButtonColorHexStr = arguments?.getString("primaryButtonColor").orEmpty()
54
59
  val billingDetailsBundle = arguments?.getBundle("defaultBillingDetails")
55
60
  paymentIntentClientSecret = arguments?.getString("paymentIntentClientSecret").orEmpty()
56
61
  setupIntentClientSecret = arguments?.getString("setupIntentClientSecret").orEmpty()
62
+ val appearance = try {
63
+ buildPaymentSheetAppearance(arguments?.getBundle("appearance"))
64
+ } catch (error: PaymentSheetAppearanceException) {
65
+ initPromise.resolve(createError(ErrorType.Failed.toString(), error))
66
+ return
67
+ }
57
68
 
58
69
  val paymentOptionCallback = PaymentOptionCallback { paymentOption ->
59
- val intent = Intent(ON_PAYMENT_OPTION_ACTION)
60
-
61
70
  if (paymentOption != null) {
62
71
  val bitmap = getBitmapFromVectorDrawable(context, paymentOption.drawableResourceId)
63
72
  val imageString = getBase64FromBitmap(bitmap)
64
-
65
- intent.putExtra("label", paymentOption.label)
66
- intent.putExtra("image", imageString)
73
+ val option: WritableMap = WritableNativeMap()
74
+ option.putString("label", paymentOption.label)
75
+ option.putString("image", imageString)
76
+ presentPromise?.resolve(createResult("paymentOption", option))
77
+ } else {
78
+ presentPromise?.resolve(createError(PaymentSheetErrorType.Canceled.toString(), "The payment option selection flow has been canceled"))
67
79
  }
68
- localBroadcastManager.sendBroadcast(intent)
69
80
  }
70
81
 
71
82
  val paymentResultCallback = PaymentSheetResultCallback { paymentResult ->
72
- val intent = Intent(ON_PAYMENT_RESULT_ACTION)
73
-
74
- intent.putExtra("paymentResult", paymentResult)
75
- localBroadcastManager.sendBroadcast(intent)
76
- }
77
-
78
- var primaryButtonColor: ColorStateList? = null
79
- if (primaryButtonColorHexStr.isNotEmpty()) {
80
- primaryButtonColor = ColorStateList.valueOf(Color.parseColor(primaryButtonColorHexStr))
83
+ when (paymentResult) {
84
+ is PaymentSheetResult.Canceled -> {
85
+ val message = "The payment flow has been canceled"
86
+ confirmPromise?.resolve(createError(PaymentSheetErrorType.Canceled.toString(), message))
87
+ ?: run {
88
+ presentPromise?.resolve(createError(PaymentSheetErrorType.Canceled.toString(), message))
89
+ }
90
+ }
91
+ is PaymentSheetResult.Failed -> {
92
+ confirmPromise?.resolve(createError(PaymentSheetErrorType.Failed.toString(), paymentResult.error))
93
+ ?: run {
94
+ presentPromise?.resolve(createError(PaymentSheetErrorType.Failed.toString(), paymentResult.error))
95
+ }
96
+ }
97
+ is PaymentSheetResult.Completed -> {
98
+ confirmPromise?.resolve(WritableNativeMap()) ?: run {
99
+ presentPromise?.resolve(WritableNativeMap())
100
+ }
101
+ }
102
+ }
103
+ (context.currentActivity as? AppCompatActivity)?.supportFragmentManager?.beginTransaction()?.remove(this)?.commitAllowingStateLoss()
81
104
  }
82
105
 
83
106
  var defaultBillingDetails: PaymentSheet.BillingDetails? = null
@@ -100,7 +123,6 @@ class PaymentSheetFragment : Fragment() {
100
123
  paymentSheetConfiguration = PaymentSheet.Configuration(
101
124
  merchantDisplayName = merchantDisplayName,
102
125
  allowsDelayedPaymentMethods = allowsDelayedPaymentMethods ?: false,
103
- primaryButtonColor = primaryButtonColor,
104
126
  defaultBillingDetails=defaultBillingDetails,
105
127
  customer = if (customerId.isNotEmpty() && customerEphemeralKeySecret.isNotEmpty()) PaymentSheet.CustomerConfiguration(
106
128
  id = customerId,
@@ -110,7 +132,8 @@ class PaymentSheetFragment : Fragment() {
110
132
  environment = if (testEnv == true) PaymentSheet.GooglePayConfiguration.Environment.Test else PaymentSheet.GooglePayConfiguration.Environment.Production,
111
133
  countryCode = countryCode,
112
134
  currencyCode = currencyCode
113
- ) else null
135
+ ) else null,
136
+ appearance = appearance
114
137
  )
115
138
 
116
139
  if (arguments?.getBoolean("customFlow") == true) {
@@ -118,12 +141,12 @@ class PaymentSheetFragment : Fragment() {
118
141
  configureFlowController()
119
142
  } else {
120
143
  paymentSheet = PaymentSheet(this, paymentResultCallback)
121
- val intent = Intent(ON_INIT_PAYMENT_SHEET)
122
- localBroadcastManager.sendBroadcast(intent)
144
+ initPromise.resolve(WritableNativeMap())
123
145
  }
124
146
  }
125
147
 
126
- fun present() {
148
+ fun present(promise: Promise) {
149
+ this.presentPromise = promise
127
150
  if(paymentSheet != null) {
128
151
  if (!paymentIntentClientSecret.isNullOrEmpty()) {
129
152
  paymentSheet?.presentWithPaymentIntent(paymentIntentClientSecret!!, paymentSheetConfiguration)
@@ -135,23 +158,24 @@ class PaymentSheetFragment : Fragment() {
135
158
  }
136
159
  }
137
160
 
138
- fun confirmPayment() {
161
+ fun confirmPayment(promise: Promise) {
162
+ this.confirmPromise = promise
139
163
  flowController?.confirm()
140
164
  }
141
165
 
142
166
  private fun configureFlowController() {
143
167
  val onFlowControllerConfigure = PaymentSheet.FlowController.ConfigCallback { _, _ ->
144
- val paymentOption = flowController?.getPaymentOption()
145
- val intent = Intent(ON_CONFIGURE_FLOW_CONTROLLER)
146
-
147
- paymentOption?.let {
168
+ val result = flowController?.getPaymentOption()?.let {
148
169
  val bitmap = getBitmapFromVectorDrawable(context, it.drawableResourceId)
149
170
  val imageString = getBase64FromBitmap(bitmap)
150
-
151
- intent.putExtra("label", it.label)
152
- intent.putExtra("image", imageString)
171
+ val option: WritableMap = WritableNativeMap()
172
+ option.putString("label", it.label)
173
+ option.putString("image", imageString)
174
+ createResult("paymentOption", option)
175
+ } ?: run {
176
+ WritableNativeMap()
153
177
  }
154
- localBroadcastManager.sendBroadcast(intent)
178
+ initPromise.resolve(result)
155
179
  }
156
180
 
157
181
  if (!paymentIntentClientSecret.isNullOrEmpty()) {