@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.
@@ -1,63 +1,50 @@
1
1
  package com.reactnativestripesdk
2
2
 
3
3
  import android.app.Activity
4
- import android.content.BroadcastReceiver
5
- import android.content.Context
6
4
  import android.content.Intent
7
- import android.content.IntentFilter
8
5
  import android.os.Parcelable
9
6
  import android.util.Log
10
7
  import androidx.appcompat.app.AppCompatActivity
11
- import androidx.localbroadcastmanager.content.LocalBroadcastManager
12
8
  import com.facebook.react.bridge.*
13
9
  import com.facebook.react.module.annotations.ReactModule
14
10
  import com.stripe.android.*
15
11
  import com.stripe.android.core.AppInfo
16
12
  import com.stripe.android.core.ApiVersion
17
- import com.stripe.android.googlepaylauncher.GooglePayLauncher
18
- import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher
19
13
  import com.stripe.android.model.*
20
14
  import com.stripe.android.payments.bankaccount.CollectBankAccountConfiguration
21
- import com.stripe.android.paymentsheet.PaymentSheetResult
22
15
  import com.stripe.android.view.AddPaymentMethodActivityStarter
23
16
  import kotlinx.coroutines.CoroutineScope
24
17
  import kotlinx.coroutines.Dispatchers
25
18
  import kotlinx.coroutines.launch
26
19
 
27
20
  @ReactModule(name = StripeSdkModule.NAME)
28
- class StripeSdkModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
29
- var cardFieldView: CardFieldView? = null
30
- var cardFormView: CardFormView? = null
31
-
21
+ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
32
22
  override fun getName(): String {
33
23
  return "StripeSdk"
34
24
  }
35
- private lateinit var stripe: Stripe
36
25
 
37
- private lateinit var paymentLauncherFragment: PaymentLauncherFragment
26
+ var cardFieldView: CardFieldView? = null
27
+ var cardFormView: CardFormView? = null
38
28
 
29
+ private lateinit var stripe: Stripe
39
30
  private lateinit var publishableKey: String
40
31
  private var stripeAccountId: String? = null
32
+ private var urlScheme: String? = null
33
+
41
34
  private var paymentSheetFragment: PaymentSheetFragment? = null
35
+ private var googlePayFragment: GooglePayFragment? = null
36
+ private var paymentLauncherFragment: PaymentLauncherFragment? = null
42
37
 
43
- private var urlScheme: String? = null
44
38
  private var confirmPromise: Promise? = null
45
- private var confirmPaymentSheetPaymentPromise: Promise? = null
46
- private var presentPaymentSheetPromise: Promise? = null
47
- private var initPaymentSheetPromise: Promise? = null
48
39
  private var confirmPaymentClientSecret: String? = null
49
40
 
50
- private var googlePayFragment: GooglePayFragment? = null
51
- private var initGooglePayPromise: Promise? = null
52
- private var presentGooglePayPromise: Promise? = null
53
-
54
41
  private val mActivityEventListener = object : BaseActivityEventListener() {
55
42
  override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
56
43
  if (::stripe.isInitialized) {
57
44
  // BEGIN - Necessary on older versions of React Native (~0.64 and below)
58
45
  paymentSheetFragment?.activity?.activityResultRegistry?.dispatchResult(requestCode, resultCode, data)
59
46
  googlePayFragment?.activity?.activityResultRegistry?.dispatchResult(requestCode, resultCode, data)
60
- paymentLauncherFragment.activity?.activityResultRegistry?.dispatchResult(requestCode, resultCode, data)
47
+ paymentLauncherFragment?.activity?.activityResultRegistry?.dispatchResult(requestCode, resultCode, data)
61
48
  // END
62
49
  try {
63
50
  val result = AddPaymentMethodActivityStarter.Result.fromIntent(data)
@@ -91,108 +78,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React
91
78
  )
92
79
  }
93
80
 
94
- private val googlePayReceiver: BroadcastReceiver = object : BroadcastReceiver() {
95
- override fun onReceive(context: Context?, intent: Intent) {
96
- if (intent.action == ON_INIT_GOOGLE_PAY) {
97
- val isReady = intent.extras?.getBoolean("isReady") ?: false
98
- if (isReady) {
99
- initGooglePayPromise?.resolve(WritableNativeMap())
100
- } else {
101
- initGooglePayPromise?.resolve(
102
- createError(
103
- GooglePayErrorType.Failed.toString(),
104
- "Google Pay is not available on this device. You can use isGooglePaySupported to preemptively check for Google Pay support."
105
- )
106
- )
107
- }
108
- }
109
- if (intent.action == ON_GOOGLE_PAYMENT_METHOD_RESULT) {
110
- intent.extras?.getString("error")?.let {
111
- presentGooglePayPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), it))
112
- return
113
- }
114
- when (val result = intent.extras?.getParcelable<GooglePayPaymentMethodLauncher.Result>("paymentResult")) {
115
- is GooglePayPaymentMethodLauncher.Result.Completed -> {
116
- presentGooglePayPromise?.resolve(createResult("paymentMethod", mapFromPaymentMethod(result.paymentMethod)))
117
- }
118
- GooglePayPaymentMethodLauncher.Result.Canceled -> {
119
- presentGooglePayPromise?.resolve(createError(GooglePayErrorType.Canceled.toString(), "Google Pay has been canceled"))
120
- }
121
- is GooglePayPaymentMethodLauncher.Result.Failed -> {
122
- presentGooglePayPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), result.error))
123
- }
124
- }
125
- }
126
- if (intent.action == ON_GOOGLE_PAY_RESULT) {
127
- intent.extras?.getString("error")?.let {
128
- presentGooglePayPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), it))
129
- return
130
- }
131
- when (val result = intent.extras?.getParcelable<GooglePayLauncher.Result>("paymentResult")) {
132
- GooglePayLauncher.Result.Completed -> {
133
- presentGooglePayPromise?.resolve(WritableNativeMap())
134
- }
135
- GooglePayLauncher.Result.Canceled -> {
136
- presentGooglePayPromise?.resolve(createError(GooglePayErrorType.Canceled.toString(), "Google Pay has been canceled"))
137
- }
138
- is GooglePayLauncher.Result.Failed -> {
139
- presentGooglePayPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), result.error))
140
- }
141
- }
142
- }
143
- }
144
- }
145
-
146
- private val mPaymentSheetReceiver: BroadcastReceiver = object : BroadcastReceiver() {
147
- override fun onReceive(context: Context?, intent: Intent) {
148
- if (intent.action == ON_PAYMENT_RESULT_ACTION) {
149
- when (val result = intent.extras?.getParcelable<PaymentSheetResult>("paymentResult")) {
150
- is PaymentSheetResult.Canceled -> {
151
- val message = "The payment has been canceled"
152
- confirmPaymentSheetPaymentPromise?.resolve(createError(PaymentSheetErrorType.Canceled.toString(), message))
153
- presentPaymentSheetPromise?.resolve(createError(PaymentSheetErrorType.Canceled.toString(), message))
154
- }
155
- is PaymentSheetResult.Failed -> {
156
- confirmPaymentSheetPaymentPromise?.resolve(createError(PaymentSheetErrorType.Failed.toString(), result.error))
157
- presentPaymentSheetPromise?.resolve(createError(PaymentSheetErrorType.Failed.toString(), result.error))
158
- }
159
- is PaymentSheetResult.Completed -> {
160
- confirmPaymentSheetPaymentPromise?.resolve(WritableNativeMap())
161
- presentPaymentSheetPromise?.resolve(WritableNativeMap())
162
- }
163
- }
164
- } else if (intent.action == ON_PAYMENT_OPTION_ACTION) {
165
- val label = intent.extras?.getString("label")
166
- val image = intent.extras?.getString("image")
167
-
168
- if (label != null && image != null) {
169
- val option: WritableMap = WritableNativeMap()
170
- option.putString("label", label)
171
- option.putString("image", image)
172
- presentPaymentSheetPromise?.resolve(createResult("paymentOption", option))
173
- } else {
174
- presentPaymentSheetPromise?.resolve(WritableNativeMap())
175
- }
176
- presentPaymentSheetPromise = null
177
- }
178
- else if (intent.action == ON_INIT_PAYMENT_SHEET) {
179
- initPaymentSheetPromise?.resolve(WritableNativeMap())
180
- } else if (intent.action == ON_CONFIGURE_FLOW_CONTROLLER) {
181
- val label = intent.extras?.getString("label")
182
- val image = intent.extras?.getString("image")
183
-
184
- if (label != null && image != null) {
185
- val option: WritableMap = WritableNativeMap()
186
- option.putString("label", label)
187
- option.putString("image", image)
188
- initPaymentSheetPromise?.resolve(createResult("paymentOption", option))
189
- } else {
190
- initPaymentSheetPromise?.resolve(WritableNativeMap())
191
- }
192
- }
193
- }
194
- }
195
-
196
81
  override fun getConstants(): MutableMap<String, Any> =
197
82
  hashMapOf(
198
83
  "API_VERSIONS" to hashMapOf(
@@ -225,37 +110,13 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React
225
110
  stripe = Stripe(reactApplicationContext, publishableKey, stripeAccountId)
226
111
 
227
112
  PaymentConfiguration.init(reactApplicationContext, publishableKey, stripeAccountId)
228
-
229
- paymentLauncherFragment = PaymentLauncherFragment(stripe, publishableKey, stripeAccountId)
230
- getCurrentActivityOrResolveWithError(promise)?.let {
231
- try {
232
- it.supportFragmentManager.beginTransaction()
233
- .add(paymentLauncherFragment, "payment_launcher_fragment")
234
- .commit()
235
- } catch (error: IllegalStateException) {
236
- promise.resolve(createError(ErrorType.Failed.toString(), error.message))
237
- }
238
-
239
- val localBroadcastManager = LocalBroadcastManager.getInstance(reactApplicationContext)
240
- localBroadcastManager.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_PAYMENT_RESULT_ACTION))
241
- localBroadcastManager.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_PAYMENT_OPTION_ACTION))
242
- localBroadcastManager.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_CONFIGURE_FLOW_CONTROLLER))
243
- localBroadcastManager.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_INIT_PAYMENT_SHEET))
244
-
245
- localBroadcastManager.registerReceiver(googlePayReceiver, IntentFilter(ON_INIT_GOOGLE_PAY))
246
- localBroadcastManager.registerReceiver(googlePayReceiver, IntentFilter(ON_GOOGLE_PAY_RESULT))
247
- localBroadcastManager.registerReceiver(googlePayReceiver, IntentFilter(ON_GOOGLE_PAYMENT_METHOD_RESULT))
248
-
249
- promise.resolve(null)
250
- }
113
+ promise.resolve(null)
251
114
  }
252
115
 
253
116
  @ReactMethod
254
117
  fun initPaymentSheet(params: ReadableMap, promise: Promise) {
255
118
  getCurrentActivityOrResolveWithError(promise)?.let { activity ->
256
- this.initPaymentSheetPromise = promise
257
-
258
- paymentSheetFragment = PaymentSheetFragment().also {
119
+ paymentSheetFragment = PaymentSheetFragment(reactApplicationContext, promise).also {
259
120
  val bundle = toBundleObject(params)
260
121
  it.arguments = bundle
261
122
  }
@@ -271,14 +132,12 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React
271
132
 
272
133
  @ReactMethod
273
134
  fun presentPaymentSheet(promise: Promise) {
274
- this.presentPaymentSheetPromise = promise
275
- paymentSheetFragment?.present()
135
+ paymentSheetFragment?.present(promise)
276
136
  }
277
137
 
278
138
  @ReactMethod
279
139
  fun confirmPaymentSheetPayment(promise: Promise) {
280
- this.confirmPaymentSheetPaymentPromise = promise
281
- paymentSheetFragment?.confirmPayment()
140
+ paymentSheetFragment?.confirmPayment(promise)
282
141
  }
283
142
 
284
143
  private fun payWithFpx() {
@@ -295,13 +154,17 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React
295
154
  when (result) {
296
155
  is AddPaymentMethodActivityStarter.Result.Success -> {
297
156
  if (confirmPaymentClientSecret != null && confirmPromise != null) {
298
- paymentLauncherFragment.confirm(
157
+ paymentLauncherFragment = PaymentLauncherFragment.forPayment(
158
+ context = reactApplicationContext,
159
+ stripe,
160
+ publishableKey,
161
+ stripeAccountId,
162
+ confirmPromise!!,
163
+ confirmPaymentClientSecret!!,
299
164
  ConfirmPaymentIntentParams.createWithPaymentMethodId(
300
165
  result.paymentMethod.id!!,
301
166
  confirmPaymentClientSecret!!
302
- ),
303
- confirmPaymentClientSecret!!,
304
- confirmPromise!!
167
+ )
305
168
  )
306
169
  } else {
307
170
  Log.e("StripeReactNative", "FPX payment failed. Promise and/or client secret is not set.")
@@ -445,9 +308,13 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React
445
308
 
446
309
  @ReactMethod
447
310
  fun handleNextAction(paymentIntentClientSecret: String, promise: Promise) {
448
- paymentLauncherFragment.handleNextActionForPaymentIntent(
449
- paymentIntentClientSecret,
450
- promise
311
+ paymentLauncherFragment = PaymentLauncherFragment.forNextAction(
312
+ context = reactApplicationContext,
313
+ stripe,
314
+ publishableKey,
315
+ stripeAccountId,
316
+ promise,
317
+ paymentIntentClientSecret
451
318
  )
452
319
  }
453
320
 
@@ -505,10 +372,14 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React
505
372
  confirmParams.returnUrl = mapToReturnURL(urlScheme)
506
373
  }
507
374
  confirmParams.shipping = mapToShippingDetails(getMapOrNull(paymentMethodData, "shippingDetails"))
508
- paymentLauncherFragment.confirm(
509
- confirmParams,
375
+ paymentLauncherFragment = PaymentLauncherFragment.forPayment(
376
+ context = reactApplicationContext,
377
+ stripe,
378
+ publishableKey,
379
+ stripeAccountId,
380
+ promise,
510
381
  paymentIntentClientSecret,
511
- promise
382
+ confirmParams
512
383
  )
513
384
  } catch (error: PaymentMethodCreateParamsException) {
514
385
  promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), error))
@@ -553,10 +424,14 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React
553
424
  urlScheme?.let {
554
425
  confirmParams.returnUrl = mapToReturnURL(urlScheme)
555
426
  }
556
- paymentLauncherFragment.confirm(
557
- confirmParams,
427
+ paymentLauncherFragment = PaymentLauncherFragment.forSetup(
428
+ context = reactApplicationContext,
429
+ stripe,
430
+ publishableKey,
431
+ stripeAccountId,
432
+ promise,
558
433
  setupIntentClientSecret,
559
- promise
434
+ confirmParams
560
435
  )
561
436
  } catch (error: PaymentMethodCreateParamsException) {
562
437
  promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), error))
@@ -566,7 +441,7 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React
566
441
  @ReactMethod
567
442
  fun isGooglePaySupported(params: ReadableMap?, promise: Promise) {
568
443
  val fragment = GooglePayPaymentMethodLauncherFragment(
569
- reactContext,
444
+ reactApplicationContext,
570
445
  getBooleanOrFalse(params, "testEnv"),
571
446
  getBooleanOrFalse(params, "existingPaymentMethodRequired"),
572
447
  promise
@@ -585,14 +460,12 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React
585
460
 
586
461
  @ReactMethod
587
462
  fun initGooglePay(params: ReadableMap, promise: Promise) {
588
- googlePayFragment = GooglePayFragment().also {
463
+ googlePayFragment = GooglePayFragment(promise).also {
589
464
  val bundle = toBundleObject(params)
590
465
  it.arguments = bundle
591
466
  }
592
467
 
593
468
  getCurrentActivityOrResolveWithError(promise)?.let {
594
- initGooglePayPromise = promise
595
-
596
469
  try {
597
470
  it.supportFragmentManager.beginTransaction()
598
471
  .add(googlePayFragment!!, "google_pay_launch_fragment")
@@ -609,15 +482,15 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React
609
482
  promise.resolve(createError(GooglePayErrorType.Failed.toString(), "you must provide clientSecret"))
610
483
  return
611
484
  }
612
- presentGooglePayPromise = promise
485
+
613
486
  if (getBooleanOrFalse(params, "forSetupIntent")) {
614
487
  val currencyCode = getValOr(params, "currencyCode") ?: run {
615
488
  promise.resolve(createError(GooglePayErrorType.Failed.toString(), "you must provide currencyCode"))
616
489
  return
617
490
  }
618
- googlePayFragment?.presentForSetupIntent(clientSecret, currencyCode)
491
+ googlePayFragment?.presentForSetupIntent(clientSecret, currencyCode, promise)
619
492
  } else {
620
- googlePayFragment?.presentForPaymentIntent(clientSecret)
493
+ googlePayFragment?.presentForPaymentIntent(clientSecret, promise)
621
494
  }
622
495
  }
623
496
 
@@ -631,8 +504,7 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React
631
504
  promise.resolve(createError(GooglePayErrorType.Failed.toString(), "you must provide amount"))
632
505
  return
633
506
  }
634
- presentGooglePayPromise = promise
635
- googlePayFragment?.createPaymentMethod(currencyCode, amount)
507
+ googlePayFragment?.createPaymentMethod(currencyCode, amount, promise)
636
508
  }
637
509
 
638
510
  @ReactMethod
@@ -669,7 +541,7 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React
669
541
  )
670
542
 
671
543
  val fragment = CollectBankAccountLauncherFragment(
672
- reactContext,
544
+ reactApplicationContext,
673
545
  publishableKey,
674
546
  clientSecret,
675
547
  isPaymentIntent,
@@ -0,0 +1,209 @@
1
+ //
2
+ // PaymentSheetAppearance.swift
3
+ // stripe-react-native
4
+ //
5
+ // Created by Charles Cruzan on 5/11/22.
6
+ //
7
+ import Stripe
8
+
9
+ extension StripeSdk {
10
+ func buildPaymentSheetAppearance(userParams: NSDictionary) throws -> PaymentSheet.Appearance {
11
+ var appearance = PaymentSheet.Appearance()
12
+
13
+ if let fontParams = userParams[PaymentSheetAppearanceKeys.FONT] as? NSDictionary {
14
+ appearance.font = try buildFont(params: fontParams)
15
+ }
16
+ if let colorParams = userParams[PaymentSheetAppearanceKeys.COLORS] as? NSDictionary {
17
+ appearance.colors = try buildColors(params: colorParams)
18
+ }
19
+ if let shapeParams = userParams[PaymentSheetAppearanceKeys.SHAPES] as? NSDictionary {
20
+ appearance.cornerRadius = shapeParams[PaymentSheetAppearanceKeys.BORDER_RADIUS] as? CGFloat ?? PaymentSheet.Appearance.default.cornerRadius
21
+ appearance.borderWidth = shapeParams[PaymentSheetAppearanceKeys.BORDER_WIDTH] as? CGFloat ?? PaymentSheet.Appearance.default.borderWidth
22
+ if let shadowParams = shapeParams[PaymentSheetAppearanceKeys.SHADOW] as? NSDictionary {
23
+ appearance.shadow = try buildShadow(params: shadowParams)
24
+ }
25
+ }
26
+ if let primaryButtonParams = userParams[PaymentSheetAppearanceKeys.PRIMARY_BUTTON] as? NSDictionary {
27
+ appearance.primaryButton = try buildPrimaryButton(params: primaryButtonParams)
28
+ }
29
+
30
+ return appearance
31
+ }
32
+
33
+ private func buildFont(params: NSDictionary) throws -> Stripe.PaymentSheet.Appearance.Font {
34
+ var font = Stripe.PaymentSheet.Appearance.Font()
35
+ if let fontName = params[PaymentSheetAppearanceKeys.FAMILY] as? String {
36
+ guard let customFont = UIFont(name: fontName, size: UIFont.systemFontSize) else {
37
+ throw PaymentSheetAppearanceError.missingFont(fontName)
38
+ }
39
+ font.base = customFont
40
+ }
41
+ font.sizeScaleFactor = params[PaymentSheetAppearanceKeys.SCALE] as? CGFloat ?? PaymentSheet.Appearance.default.font.sizeScaleFactor
42
+ return font
43
+ }
44
+
45
+ private func buildColors(params: NSDictionary) throws -> Stripe.PaymentSheet.Appearance.Colors {
46
+ var colors = Stripe.PaymentSheet.Appearance.Colors()
47
+
48
+ if (params.object(forKey: PaymentSheetAppearanceKeys.LIGHT) != nil && params.object(forKey: PaymentSheetAppearanceKeys.DARK) == nil ||
49
+ params.object(forKey: PaymentSheetAppearanceKeys.DARK) != nil && params.object(forKey: PaymentSheetAppearanceKeys.LIGHT) == nil) {
50
+ throw PaymentSheetAppearanceError.missingAppearanceMode
51
+ }
52
+
53
+ let lightModeParams = params[PaymentSheetAppearanceKeys.LIGHT] as? NSDictionary ?? params
54
+ let darkModeParams = params[PaymentSheetAppearanceKeys.DARK] as? NSDictionary ?? params
55
+
56
+ colors.primary = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.PRIMARY, lightParams: lightModeParams, darkParams: darkModeParams) ?? PaymentSheet.Appearance.default.colors.primary
57
+ colors.background = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.BACKGROUND, lightParams: lightModeParams, darkParams: darkModeParams) ?? PaymentSheet.Appearance.default.colors.background
58
+ colors.componentBackground = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.COMPONENT_BACKGROUND, lightParams: lightModeParams, darkParams: darkModeParams) ?? PaymentSheet.Appearance.default.colors.componentBackground
59
+ colors.componentBorder = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.COMPONENT_BORDER, lightParams: lightModeParams, darkParams: darkModeParams) ?? PaymentSheet.Appearance.default.colors.componentBorder
60
+ colors.componentDivider = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.COMPONENT_DIVIDER, lightParams: lightModeParams, darkParams: darkModeParams) ?? PaymentSheet.Appearance.default.colors.componentDivider
61
+ colors.text = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.PRIMARY_TEXT, lightParams: lightModeParams, darkParams: darkModeParams) ?? PaymentSheet.Appearance.default.colors.text
62
+ colors.textSecondary = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.SECONDARY_TEXT, lightParams: lightModeParams, darkParams: darkModeParams) ?? PaymentSheet.Appearance.default.colors.textSecondary
63
+ colors.componentText = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.COMPONENT_TEXT, lightParams: lightModeParams, darkParams: darkModeParams) ?? PaymentSheet.Appearance.default.colors.componentText
64
+ colors.componentPlaceholderText = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.PLACEHOLDER_TEXT, lightParams: lightModeParams, darkParams: darkModeParams) ?? PaymentSheet.Appearance.default.colors.componentPlaceholderText
65
+ colors.icon = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.ICON, lightParams: lightModeParams, darkParams: darkModeParams) ?? PaymentSheet.Appearance.default.colors.icon
66
+ colors.danger = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.ERROR, lightParams: lightModeParams, darkParams: darkModeParams) ?? PaymentSheet.Appearance.default.colors.danger
67
+
68
+ return colors
69
+ }
70
+
71
+ private func buildShadow(params: NSDictionary) throws -> PaymentSheet.Appearance.Shadow {
72
+ var shadow = PaymentSheet.Appearance.Shadow()
73
+
74
+ if let color = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.SHADOW_COLOR, lightParams: params, darkParams: params) {
75
+ shadow.color = color
76
+ }
77
+ if let opacity = params[PaymentSheetAppearanceKeys.OPACITY] as? CGFloat {
78
+ shadow.opacity = opacity
79
+ }
80
+ if let radius = params[PaymentSheetAppearanceKeys.BLUR_RADIUS] as? CGFloat {
81
+ shadow.radius = radius
82
+ }
83
+ if let offsetParams = params[PaymentSheetAppearanceKeys.OFFSET] as? NSDictionary {
84
+ if let x = offsetParams[PaymentSheetAppearanceKeys.X] as? CGFloat, let y = offsetParams[PaymentSheetAppearanceKeys.Y] as? CGFloat {
85
+ shadow.offset = CGSize(width: x, height:-y)
86
+ }
87
+ }
88
+
89
+ return shadow
90
+ }
91
+
92
+ private func buildPrimaryButton(params: NSDictionary) throws -> PaymentSheet.Appearance.PrimaryButton {
93
+ var primaryButton = PaymentSheet.Appearance.PrimaryButton()
94
+
95
+ if let fontName = (params[PaymentSheetAppearanceKeys.FONT] as? NSDictionary)?[PaymentSheetAppearanceKeys.FAMILY] as? String {
96
+ guard let customFont = UIFont(name: fontName, size: UIFont.systemFontSize) else {
97
+ throw PaymentSheetAppearanceError.missingFont(fontName)
98
+ }
99
+ primaryButton.font = customFont
100
+ }
101
+ if let shapeParams = params[PaymentSheetAppearanceKeys.SHAPES] as? NSDictionary {
102
+ if let borderRadius = shapeParams[PaymentSheetAppearanceKeys.BORDER_RADIUS] as? CGFloat {
103
+ primaryButton.cornerRadius = borderRadius
104
+ }
105
+ if let borderWidth = shapeParams[PaymentSheetAppearanceKeys.BORDER_WIDTH] as? CGFloat {
106
+ primaryButton.borderWidth = borderWidth
107
+ }
108
+ if let shadowParams = shapeParams[PaymentSheetAppearanceKeys.SHADOW] as? NSDictionary {
109
+ primaryButton.shadow = try buildShadow(params: shadowParams)
110
+ }
111
+ }
112
+ if let colorParams = params[PaymentSheetAppearanceKeys.COLORS] as? NSDictionary {
113
+ if (colorParams.object(forKey: PaymentSheetAppearanceKeys.LIGHT) != nil && colorParams.object(forKey: PaymentSheetAppearanceKeys.DARK) == nil ||
114
+ colorParams.object(forKey: PaymentSheetAppearanceKeys.DARK) != nil && colorParams.object(forKey: PaymentSheetAppearanceKeys.LIGHT) == nil) {
115
+ throw PaymentSheetAppearanceError.missingAppearanceMode
116
+ }
117
+
118
+ let lightModeParams = colorParams[PaymentSheetAppearanceKeys.LIGHT] as? NSDictionary ?? colorParams
119
+ let darkModeParams = colorParams[PaymentSheetAppearanceKeys.DARK] as? NSDictionary ?? colorParams
120
+
121
+ primaryButton.backgroundColor = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.BACKGROUND, lightParams: lightModeParams, darkParams: darkModeParams)
122
+ primaryButton.textColor = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.TEXT, lightParams: lightModeParams, darkParams: darkModeParams)
123
+ primaryButton.borderColor = try StripeSdk.buildUserInterfaceStyleAwareColor(key: PaymentSheetAppearanceKeys.BORDER, lightParams: lightModeParams, darkParams: darkModeParams) ?? PaymentSheet.Appearance.default.primaryButton.borderColor
124
+ }
125
+
126
+ return primaryButton
127
+ }
128
+
129
+ private static func buildUserInterfaceStyleAwareColor(key: String, lightParams: NSDictionary, darkParams: NSDictionary) throws -> UIColor? {
130
+ guard let lightHexString = lightParams[key] as? String, let darkHexString = darkParams[key] as? String else {
131
+ return nil
132
+ }
133
+
134
+ let darkCount = darkHexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted).count
135
+ let lightCount = lightHexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted).count
136
+ if (lightCount != 6 && lightCount != 8) {
137
+ throw PaymentSheetAppearanceError.unexpectedHexStringLength(lightHexString)
138
+ } else if (darkCount != 6 && darkCount != 8) {
139
+ throw PaymentSheetAppearanceError.unexpectedHexStringLength(darkHexString)
140
+ }
141
+
142
+ let lightColor = UIColor(hexString: lightHexString)
143
+ let darkColor = UIColor(hexString: darkHexString)
144
+
145
+ if #available(iOS 13.0, *) {
146
+ return UIColor.init { traits in
147
+ return traits.userInterfaceStyle == .dark ? darkColor : lightColor
148
+ }
149
+ } else {
150
+ return lightColor
151
+ }
152
+ }
153
+ }
154
+
155
+ enum PaymentSheetAppearanceError : Error {
156
+ case missingFont(String)
157
+ case missingAppearanceMode
158
+ case unexpectedHexStringLength(String)
159
+ }
160
+
161
+ extension PaymentSheetAppearanceError: LocalizedError {
162
+ public var errorDescription: String? {
163
+ switch self {
164
+ case .missingFont(let fontFamily):
165
+ return NSLocalizedString("Failed to set Payment Sheet appearance. Unable to find font: \(fontFamily)", comment: "Failed to set font")
166
+ case .missingAppearanceMode:
167
+ return NSLocalizedString("Failed to set Payment Sheet appearance. When providing 'colors.light' or 'colors.dark', you must provide both.", comment: "Failed to set colors")
168
+ case .unexpectedHexStringLength(let hexString):
169
+ return NSLocalizedString("Failed to set Payment Sheet appearance. Expected hex string of length 6 or 8, but received: \(hexString)", comment: "Failed to set color")
170
+ }
171
+ }
172
+ }
173
+
174
+ private struct PaymentSheetAppearanceKeys {
175
+ static let COLORS = "colors"
176
+ static let LIGHT = "light"
177
+ static let DARK = "dark"
178
+ static let PRIMARY = "primary"
179
+ static let BACKGROUND = "background"
180
+ static let COMPONENT_BACKGROUND = "componentBackground"
181
+ static let COMPONENT_BORDER = "componentBorder"
182
+ static let COMPONENT_DIVIDER = "componentDivider"
183
+ static let COMPONENT_TEXT = "componentText"
184
+ static let PRIMARY_TEXT = "primaryText"
185
+ static let SECONDARY_TEXT = "secondaryText"
186
+ static let PLACEHOLDER_TEXT = "placeholderText"
187
+ static let ICON = "icon"
188
+ static let ERROR = "error"
189
+
190
+ static let FONT = "font"
191
+ static let FAMILY = "family"
192
+ static let SCALE = "scale"
193
+
194
+ static let SHAPES = "shapes"
195
+ static let BORDER_RADIUS = "borderRadius"
196
+ static let BORDER_WIDTH = "borderWidth"
197
+
198
+ static let SHADOW = "shadow"
199
+ static let SHADOW_COLOR = "color"
200
+ static let OPACITY = "opacity"
201
+ static let OFFSET = "offset"
202
+ static let BLUR_RADIUS = "blurRadius"
203
+ static let X = "x"
204
+ static let Y = "y"
205
+
206
+ static let PRIMARY_BUTTON = "primaryButton"
207
+ static let TEXT = "text"
208
+ static let BORDER = "border"
209
+ }
@@ -76,6 +76,15 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi
76
76
  var configuration = PaymentSheet.Configuration()
77
77
  self.paymentSheetFlowController = nil
78
78
 
79
+ if let appearanceParams = params["appearance"] as? NSDictionary {
80
+ do {
81
+ configuration.appearance = try buildPaymentSheetAppearance(userParams: appearanceParams)
82
+ } catch {
83
+ resolve(Errors.createError(ErrorType.Failed, error.localizedDescription))
84
+ return
85
+ }
86
+ }
87
+
79
88
  if params["applePay"] as? Bool == true {
80
89
  if let merchantIdentifier = self.merchantIdentifier, let merchantCountryCode = params["merchantCountryCode"] as? String {
81
90
  configuration.applePay = .init(merchantId: merchantIdentifier,
@@ -94,11 +103,6 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi
94
103
  configuration.returnURL = returnURL
95
104
  }
96
105
 
97
- if let buttonColorHexStr = params["primaryButtonColor"] as? String {
98
- let primaryButtonColor = UIColor(hexString: buttonColorHexStr)
99
- configuration.primaryButtonColor = primaryButtonColor
100
- }
101
-
102
106
  if let allowsDelayedPaymentMethods = params["allowsDelayedPaymentMethods"] as? Bool {
103
107
  configuration.allowsDelayedPaymentMethods = allowsDelayedPaymentMethods
104
108
  }
@@ -180,7 +180,7 @@
180
180
  isa = XCBuildConfiguration;
181
181
  buildSettings = {
182
182
  ALWAYS_SEARCH_USER_PATHS = NO;
183
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
183
+ CLANG_CXX_LANGUAGE_STANDARD = "c++17";
184
184
  CLANG_CXX_LIBRARY = "libc++";
185
185
  CLANG_ENABLE_MODULES = YES;
186
186
  CLANG_ENABLE_OBJC_ARC = YES;
@@ -230,7 +230,7 @@
230
230
  isa = XCBuildConfiguration;
231
231
  buildSettings = {
232
232
  ALWAYS_SEARCH_USER_PATHS = NO;
233
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
233
+ CLANG_CXX_LANGUAGE_STANDARD = "c++17";
234
234
  CLANG_CXX_LIBRARY = "libc++";
235
235
  CLANG_ENABLE_MODULES = YES;
236
236
  CLANG_ENABLE_OBJC_ARC = YES;
@@ -0,0 +1,3 @@
1
+ import type { PaymentSheet } from '@stripe/stripe-react-native';
2
+ declare const appearance: PaymentSheet.AppearanceParams;
3
+ export default appearance;