Skip to content

Commit 68c8eb7

Browse files
authored
[FCLite] Make FC Lite available in MPE (#4712)
## Summary This integrates FC Lite into MPE. The main idea here is we first check if the full SDK is available, use that if so, and otherwise fallback on FC Lite. > [!IMPORTANT] >This PR does not release FC Lite. There is a guardrail in place (`fcLiteFeatureEnabled`) that prevents it from being accessed, and this is only currently enabled by our example apps. ## Motivation <!-- Why are you making this change? If it's for fixing a bug, if possible, please include a code snippet or example project that demonstrates the issue. --> ## Testing Manual testing done! Here's the Payment Sheet example app: https://github.com/user-attachments/assets/d9c7f0ef-fcff-48ea-b5fd-ed89b64f36fb And the Financial Connections example app: https://github.com/user-attachments/assets/e47df2f8-362e-49d0-a7db-0b5f401d8c8b ## Changelog N/a
1 parent 8867c51 commit 68c8eb7

File tree

9 files changed

+105
-5
lines changed

9 files changed

+105
-5
lines changed

Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundViewModel.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,13 @@ final class PlaygroundViewModel: ObservableObject {
288288
}
289289

290290
func didSelectShow() {
291+
let useFCLite = playgroundConfiguration.sdkType == .fcLite
292+
FinancialConnectionsSDKAvailability.fcLiteFeatureEnabled = useFCLite
293+
FinancialConnectionsSDKAvailability.shouldPreferFCLite = useFCLite
294+
291295
switch playgroundConfiguration.integrationType {
292296
case .standalone:
293-
if playgroundConfiguration.sdkType == .fcLite {
297+
if useFCLite {
294298
setupFcLite()
295299
} else {
296300
setupStandalone()

Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ struct PaymentSheetTestPlayground: View {
5656
SettingView(setting: $playgroundController.settings.autoreload)
5757
SettingView(setting: $playgroundController.settings.shakeAmbiguousViews)
5858
SettingView(setting: $playgroundController.settings.instantDebitsIncentives)
59+
SettingView(setting: $playgroundController.settings.fcLiteEnabled)
5960
}
6061

6162
var body: some View {

Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift

+8
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,12 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable {
348348
case on
349349
case off
350350
}
351+
enum FCLiteEnabled: String, PickerEnum {
352+
static var enumName: String { "FCLite enabled" }
353+
354+
case on
355+
case off
356+
}
351357
enum ExternalPaymentMethods: String, PickerEnum {
352358
static let enumName: String = "External PMs"
353359
// Based on https://git.corp.stripe.com/stripe-internal/stripe-js-v3/blob/55d7fd10/src/externalPaymentMethods/constants.ts#L13
@@ -523,6 +529,7 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable {
523529
var autoreload: Autoreload
524530
var shakeAmbiguousViews: ShakeAmbiguousViews
525531
var instantDebitsIncentives: InstantDebitsIncentives
532+
var fcLiteEnabled: FCLiteEnabled
526533
var externalPaymentMethods: ExternalPaymentMethods
527534
var customPaymentMethods: CustomPaymentMethods
528535
var preferredNetworksEnabled: PreferredNetworksEnabled
@@ -575,6 +582,7 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable {
575582
autoreload: .on,
576583
shakeAmbiguousViews: .off,
577584
instantDebitsIncentives: .off,
585+
fcLiteEnabled: .off,
578586
externalPaymentMethods: .off,
579587
customPaymentMethods: .off,
580588
preferredNetworksEnabled: .off,

Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,10 @@ class PlaygroundController: ObservableObject {
491491
// Hack to enable incentives in Instant Debits
492492
let enableInstantDebitsIncentives = newValue.instantDebitsIncentives == .on
493493
UserDefaults.standard.set(enableInstantDebitsIncentives, forKey: "FINANCIAL_CONNECTIONS_INSTANT_DEBITS_INCENTIVES")
494+
495+
let enableFcLite = newValue.fcLiteEnabled == .on
496+
FinancialConnectionsSDKAvailability.fcLiteFeatureEnabled = enableFcLite
497+
FinancialConnectionsSDKAvailability.shouldPreferFCLite = enableFcLite
494498
}.store(in: &subscribers)
495499

496500
// Listen for analytics
@@ -893,7 +897,7 @@ extension PlaygroundController {
893897
else {
894898
if let data = data,
895899
(response as? HTTPURLResponse)?.statusCode == 400 {
896-
let errorMessage = String(data: data, encoding: .utf8)!
900+
let errorMessage = String(decoding: data, as: UTF8.self)
897901
// read the error message
898902
intentCreationCallback(.failure(ConfirmHandlerError.confirmError(errorMessage)))
899903
} else {

StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj

+8
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@
136136
47AD56A9889DF5EFBBA9CEFB /* PollingViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADE49E72DD5EDA448D12D88 /* PollingViewTests.swift */; };
137137
47B19F96CCEA290541E3B988 /* CardSectionElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D03000A6807B09BFD8E6CB1 /* CardSectionElement.swift */; };
138138
48DA2EFE0944E737B0F197B0 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = B2AFFAD776D5F21DF837F1BD /* OHHTTPStubs */; };
139+
4954E9712D96FA0C0061585F /* FCLiteImplementationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4954E9702D96FA0C0061585F /* FCLiteImplementationTests.swift */; };
139140
49803444CD948F1ED28FF021 /* PaymentSheetFormFactory+FormSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7A1EFF100C589FDFF4D516 /* PaymentSheetFormFactory+FormSpec.swift */; };
141+
498BF1722D92FF6A006E866B /* FCLiteImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498BF1712D92FF6A006E866B /* FCLiteImplementation.swift */; };
140142
49909A162D8AF9760031EC33 /* FinancialConnectionsLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49909A152D8AF9760031EC33 /* FinancialConnectionsLite.swift */; };
141143
49909A1C2D8AFA600031EC33 /* FCLiteApiClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49909A1B2D8AFA600031EC33 /* FCLiteApiClient.swift */; };
142144
49909A1E2D8AFAB70031EC33 /* FCLiteManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49909A1D2D8AFAB70031EC33 /* FCLiteManifest.swift */; };
@@ -580,6 +582,8 @@
580582
45B6DC9BD9183495E5649369 /* LinkAccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkAccountService.swift; sourceTree = "<group>"; };
581583
47C5DB8C01BA7137369C8B4D /* TextFieldElement+Card.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextFieldElement+Card.swift"; sourceTree = "<group>"; };
582584
492B254E43F3BB9F9CEAEA06 /* PaymentSheetLoaderStubbedTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetLoaderStubbedTest.swift; sourceTree = "<group>"; };
585+
4954E9702D96FA0C0061585F /* FCLiteImplementationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCLiteImplementationTests.swift; sourceTree = "<group>"; };
586+
498BF1712D92FF6A006E866B /* FCLiteImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCLiteImplementation.swift; sourceTree = "<group>"; };
583587
49909A152D8AF9760031EC33 /* FinancialConnectionsLite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsLite.swift; sourceTree = "<group>"; };
584588
49909A1B2D8AFA600031EC33 /* FCLiteApiClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCLiteApiClient.swift; sourceTree = "<group>"; };
585589
49909A1D2D8AFAB70031EC33 /* FCLiteManifest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCLiteManifest.swift; sourceTree = "<group>"; };
@@ -1142,6 +1146,7 @@
11421146
49909A182D8AF9B50031EC33 /* Controllers */,
11431147
49909A172D8AF9AE0031EC33 /* API Client */,
11441148
49909A152D8AF9760031EC33 /* FinancialConnectionsLite.swift */,
1149+
498BF1712D92FF6A006E866B /* FCLiteImplementation.swift */,
11451150
);
11461151
path = "FC Lite";
11471152
sourceTree = "<group>";
@@ -1838,6 +1843,7 @@
18381843
61CBE6672BED97EE005F7FEB /* VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift */,
18391844
619AF0882BF56F9100D1C981 /* VerticalSavedPaymentMethodsViewControllerTests.swift */,
18401845
49909A2D2D8B19800031EC33 /* FCLiteAuthFlowViewControllerTests.swift */,
1846+
4954E9702D96FA0C0061585F /* FCLiteImplementationTests.swift */,
18411847
);
18421848
path = PaymentSheet;
18431849
sourceTree = "<group>";
@@ -2115,6 +2121,7 @@
21152121
619AF0852BF56C5E00D1C981 /* PaymentMethodRowButtonSnapshotTests.swift in Sources */,
21162122
B6CACCA02CBD9A3300682ECE /* EmbeddedPaymentElementTest.swift in Sources */,
21172123
2EC9C94DD8D62E4F4EFC8AB8 /* IntentStatusPollerTest.swift in Sources */,
2124+
4954E9712D96FA0C0061585F /* FCLiteImplementationTests.swift in Sources */,
21182125
ABC3A7CF6D5B21D0C9684A09 /* LinkPopupURLParserTests.swift in Sources */,
21192126
F94F6A157CEB937896B682D4 /* LinkURLGeneratorTests.swift in Sources */,
21202127
10A336F0F2331F22F1A0AC1B /* LinkStubs.swift in Sources */,
@@ -2245,6 +2252,7 @@
22452252
F3A34AD1CC2CBB899738C9D7 /* LinkInlineSignupElement.swift in Sources */,
22462253
56BB7C81AB3A24D3AD88A904 /* LinkInlineSignupView-CheckboxElement.swift in Sources */,
22472254
7479F814D1BC58A6B19F054C /* LinkInlineSignupView.swift in Sources */,
2255+
498BF1722D92FF6A006E866B /* FCLiteImplementation.swift in Sources */,
22482256
3147CEC02CC080570067B5E4 /* LinkInMemoryCookieStore.swift in Sources */,
22492257
3147CEC12CC080570067B5E4 /* LinkCookieStore.swift in Sources */,
22502258
3147CEC22CC080570067B5E4 /* LinkSecureCookieStore.swift in Sources */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// FCLiteImplementation.swift
3+
// StripePaymentSheet
4+
//
5+
// Created by Mat Schmid on 2025-03-25.
6+
//
7+
8+
@_spi(STP) import StripeCore
9+
import UIKit
10+
11+
/// NOTE: If you change the name of this class, make sure to also change it in the `FinancialConnectionsSDKAvailability` file.
12+
@_spi(STP) public class FCLiteImplementation: FinancialConnectionsSDKInterface {
13+
required public init() {}
14+
15+
public func presentFinancialConnectionsSheet(
16+
apiClient: STPAPIClient,
17+
clientSecret: String,
18+
returnURL: String?,
19+
style: FinancialConnectionsStyle,
20+
elementsSessionContext: ElementsSessionContext?,
21+
onEvent: ((FinancialConnectionsEvent) -> Void)?,
22+
from presentingViewController: UIViewController,
23+
completion: @escaping (FinancialConnectionsSDKResult) -> Void
24+
) {
25+
let returnUrl = returnURL.flatMap(URL.init(string:))
26+
27+
let fcLite = FinancialConnectionsLite(
28+
clientSecret: clientSecret,
29+
returnUrl: returnUrl
30+
)
31+
fcLite.present(from: presentingViewController, completion: completion)
32+
}
33+
}

StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift

+4
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ final class PaymentSheetLoader {
110110
let isLinkEnabled = PaymentSheet.isLinkEnabled(elementsSession: elementsSession, configuration: configuration)
111111
let isApplePayEnabled = PaymentSheet.isApplePayEnabled(elementsSession: elementsSession, configuration: configuration)
112112

113+
// Disable FC Lite if killswitch is enabled
114+
let isFcLiteKillswitchEnabled = elementsSession.flags["elements_disable_fc_lite"] == true
115+
FinancialConnectionsSDKAvailability.fcLiteKillswitchEnabled = isFcLiteKillswitchEnabled
116+
113117
// Send load finished analytic
114118
// This is hacky; the logic to determine the default selected payment method belongs to the SavedPaymentOptionsViewController. We invoke it here just to report it to analytics before that VC loads.
115119
let (defaultSelectedIndex, paymentOptionsViewModels) = SavedPaymentOptionsViewController.makeViewModels(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// FCLiteImplementationTests.swift
3+
// StripePaymentSheetTests
4+
//
5+
// Created by Mat Schmid on 2025-03-28.
6+
//
7+
8+
@_spi(STP) import StripeCore
9+
import XCTest
10+
11+
class FCLiteImplementationTests: XCTestCase {
12+
func testFCLiteImplementationAvailable() {
13+
let FinancialConnectionsLiteImplementation: FinancialConnectionsSDKInterface.Type? =
14+
NSClassFromString("StripePaymentSheet.FCLiteImplementation")
15+
as? FinancialConnectionsSDKInterface.Type
16+
XCTAssertNotNil(FinancialConnectionsLiteImplementation)
17+
}
18+
}

StripePayments/StripePayments/Source/Internal/Helpers/ConnectionsSDKAvailability.swift

+23-3
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,29 @@
88

99
import Foundation
1010
@_spi(STP) import StripeCore
11-
import SwiftUI
1211
import UIKit
1312

1413
@_spi(STP) public struct FinancialConnectionsSDKAvailability {
1514
static let FinancialConnectionsSDKClass: FinancialConnectionsSDKInterface.Type? =
1615
NSClassFromString("StripeFinancialConnections.FinancialConnectionsSDKImplementation")
1716
as? FinancialConnectionsSDKInterface.Type
1817

18+
static let FinancialConnectionsLiteImplementation: FinancialConnectionsSDKInterface.Type? =
19+
NSClassFromString("StripePaymentSheet.FCLiteImplementation")
20+
as? FinancialConnectionsSDKInterface.Type
21+
22+
@_spi(STP) public static var fcLiteKillswitchEnabled: Bool = false
23+
@_spi(STP) public static var shouldPreferFCLite: Bool = false
24+
// Remove this when ready to release FC Lite:
25+
@_spi(STP) public static var fcLiteFeatureEnabled: Bool = false
26+
27+
private static var FCLiteClassIfEnabled: FinancialConnectionsSDKInterface.Type? {
28+
guard fcLiteFeatureEnabled, !fcLiteKillswitchEnabled else {
29+
return nil
30+
}
31+
return Self.FinancialConnectionsLiteImplementation
32+
}
33+
1934
static let isUnitTest: Bool = {
2035
#if targetEnvironment(simulator)
2136
return NSClassFromString("XCTest") != nil
@@ -34,14 +49,15 @@ import UIKit
3449

3550
// Return true for unit tests, the value of `FinancialConnectionsSDKAvailable` for UI tests,
3651
// and whether or not the Financial Connections SDK is available otherwise.
52+
// Falls back on FC Lite availability.
3753
@_spi(STP) public static var isFinancialConnectionsSDKAvailable: Bool {
3854
if isUnitTest {
3955
return true
4056
} else if isUITest {
4157
let financialConnectionsSDKAvailable = ProcessInfo.processInfo.environment["FinancialConnectionsSDKAvailable"] == "true"
4258
return financialConnectionsSDKAvailable
4359
} else {
44-
return FinancialConnectionsSDKClass != nil
60+
return (FinancialConnectionsSDKClass != nil || FCLiteClassIfEnabled != nil)
4561
}
4662
}
4763

@@ -51,7 +67,11 @@ import UIKit
5167
return StubbedConnectionsSDKInterface()
5268
}
5369

54-
guard let klass = FinancialConnectionsSDKClass else {
70+
let klass: FinancialConnectionsSDKInterface.Type? = shouldPreferFCLite
71+
? (FCLiteClassIfEnabled ?? FinancialConnectionsSDKClass)
72+
: (FinancialConnectionsSDKClass ?? FCLiteClassIfEnabled)
73+
74+
guard let klass else {
5575
return nil
5676
}
5777

0 commit comments

Comments
 (0)