Skip to content

[FCLite] Make FC Lite available in MPE #4712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,13 @@ final class PlaygroundViewModel: ObservableObject {
}

func didSelectShow() {
let useFCLite = playgroundConfiguration.sdkType == .fcLite
FinancialConnectionsSDKAvailability.fcLiteFeatureEnabled = useFCLite
FinancialConnectionsSDKAvailability.shouldPreferFCLite = useFCLite

switch playgroundConfiguration.integrationType {
case .standalone:
if playgroundConfiguration.sdkType == .fcLite {
if useFCLite {
setupFcLite()
} else {
setupStandalone()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ struct PaymentSheetTestPlayground: View {
SettingView(setting: $playgroundController.settings.autoreload)
SettingView(setting: $playgroundController.settings.shakeAmbiguousViews)
SettingView(setting: $playgroundController.settings.instantDebitsIncentives)
SettingView(setting: $playgroundController.settings.fcLiteEnabled)
}

var body: some View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,12 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable {
case on
case off
}
enum FCLiteEnabled: String, PickerEnum {
static var enumName: String { "FCLite enabled" }

case on
case off
}
enum ExternalPaymentMethods: String, PickerEnum {
static let enumName: String = "External PMs"
// Based on https://git.corp.stripe.com/stripe-internal/stripe-js-v3/blob/55d7fd10/src/externalPaymentMethods/constants.ts#L13
Expand Down Expand Up @@ -523,6 +529,7 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable {
var autoreload: Autoreload
var shakeAmbiguousViews: ShakeAmbiguousViews
var instantDebitsIncentives: InstantDebitsIncentives
var fcLiteEnabled: FCLiteEnabled
var externalPaymentMethods: ExternalPaymentMethods
var customPaymentMethods: CustomPaymentMethods
var preferredNetworksEnabled: PreferredNetworksEnabled
Expand Down Expand Up @@ -575,6 +582,7 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable {
autoreload: .on,
shakeAmbiguousViews: .off,
instantDebitsIncentives: .off,
fcLiteEnabled: .off,
externalPaymentMethods: .off,
customPaymentMethods: .off,
preferredNetworksEnabled: .off,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,10 @@ class PlaygroundController: ObservableObject {
// Hack to enable incentives in Instant Debits
let enableInstantDebitsIncentives = newValue.instantDebitsIncentives == .on
UserDefaults.standard.set(enableInstantDebitsIncentives, forKey: "FINANCIAL_CONNECTIONS_INSTANT_DEBITS_INCENTIVES")

let enableFcLite = newValue.fcLiteEnabled == .on
FinancialConnectionsSDKAvailability.fcLiteFeatureEnabled = enableFcLite
FinancialConnectionsSDKAvailability.shouldPreferFCLite = enableFcLite
}.store(in: &subscribers)

// Listen for analytics
Expand Down Expand Up @@ -893,7 +897,7 @@ extension PlaygroundController {
else {
if let data = data,
(response as? HTTPURLResponse)?.statusCode == 400 {
let errorMessage = String(data: data, encoding: .utf8)!
let errorMessage = String(decoding: data, as: UTF8.self)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by fix. Our linter was yelling about this line

// read the error message
intentCreationCallback(.failure(ConfirmHandlerError.confirmError(errorMessage)))
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@
47AD56A9889DF5EFBBA9CEFB /* PollingViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADE49E72DD5EDA448D12D88 /* PollingViewTests.swift */; };
47B19F96CCEA290541E3B988 /* CardSectionElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D03000A6807B09BFD8E6CB1 /* CardSectionElement.swift */; };
48DA2EFE0944E737B0F197B0 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = B2AFFAD776D5F21DF837F1BD /* OHHTTPStubs */; };
4954E9712D96FA0C0061585F /* FCLiteImplementationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4954E9702D96FA0C0061585F /* FCLiteImplementationTests.swift */; };
49803444CD948F1ED28FF021 /* PaymentSheetFormFactory+FormSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7A1EFF100C589FDFF4D516 /* PaymentSheetFormFactory+FormSpec.swift */; };
498BF1722D92FF6A006E866B /* FCLiteImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498BF1712D92FF6A006E866B /* FCLiteImplementation.swift */; };
49909A162D8AF9760031EC33 /* FinancialConnectionsLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49909A152D8AF9760031EC33 /* FinancialConnectionsLite.swift */; };
49909A1C2D8AFA600031EC33 /* FCLiteApiClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49909A1B2D8AFA600031EC33 /* FCLiteApiClient.swift */; };
49909A1E2D8AFAB70031EC33 /* FCLiteManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49909A1D2D8AFAB70031EC33 /* FCLiteManifest.swift */; };
Expand Down Expand Up @@ -580,6 +582,8 @@
45B6DC9BD9183495E5649369 /* LinkAccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkAccountService.swift; sourceTree = "<group>"; };
47C5DB8C01BA7137369C8B4D /* TextFieldElement+Card.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextFieldElement+Card.swift"; sourceTree = "<group>"; };
492B254E43F3BB9F9CEAEA06 /* PaymentSheetLoaderStubbedTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetLoaderStubbedTest.swift; sourceTree = "<group>"; };
4954E9702D96FA0C0061585F /* FCLiteImplementationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCLiteImplementationTests.swift; sourceTree = "<group>"; };
498BF1712D92FF6A006E866B /* FCLiteImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCLiteImplementation.swift; sourceTree = "<group>"; };
49909A152D8AF9760031EC33 /* FinancialConnectionsLite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsLite.swift; sourceTree = "<group>"; };
49909A1B2D8AFA600031EC33 /* FCLiteApiClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCLiteApiClient.swift; sourceTree = "<group>"; };
49909A1D2D8AFAB70031EC33 /* FCLiteManifest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCLiteManifest.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1142,6 +1146,7 @@
49909A182D8AF9B50031EC33 /* Controllers */,
49909A172D8AF9AE0031EC33 /* API Client */,
49909A152D8AF9760031EC33 /* FinancialConnectionsLite.swift */,
498BF1712D92FF6A006E866B /* FCLiteImplementation.swift */,
);
path = "FC Lite";
sourceTree = "<group>";
Expand Down Expand Up @@ -1838,6 +1843,7 @@
61CBE6672BED97EE005F7FEB /* VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift */,
619AF0882BF56F9100D1C981 /* VerticalSavedPaymentMethodsViewControllerTests.swift */,
49909A2D2D8B19800031EC33 /* FCLiteAuthFlowViewControllerTests.swift */,
4954E9702D96FA0C0061585F /* FCLiteImplementationTests.swift */,
);
path = PaymentSheet;
sourceTree = "<group>";
Expand Down Expand Up @@ -2115,6 +2121,7 @@
619AF0852BF56C5E00D1C981 /* PaymentMethodRowButtonSnapshotTests.swift in Sources */,
B6CACCA02CBD9A3300682ECE /* EmbeddedPaymentElementTest.swift in Sources */,
2EC9C94DD8D62E4F4EFC8AB8 /* IntentStatusPollerTest.swift in Sources */,
4954E9712D96FA0C0061585F /* FCLiteImplementationTests.swift in Sources */,
ABC3A7CF6D5B21D0C9684A09 /* LinkPopupURLParserTests.swift in Sources */,
F94F6A157CEB937896B682D4 /* LinkURLGeneratorTests.swift in Sources */,
10A336F0F2331F22F1A0AC1B /* LinkStubs.swift in Sources */,
Expand Down Expand Up @@ -2245,6 +2252,7 @@
F3A34AD1CC2CBB899738C9D7 /* LinkInlineSignupElement.swift in Sources */,
56BB7C81AB3A24D3AD88A904 /* LinkInlineSignupView-CheckboxElement.swift in Sources */,
7479F814D1BC58A6B19F054C /* LinkInlineSignupView.swift in Sources */,
498BF1722D92FF6A006E866B /* FCLiteImplementation.swift in Sources */,
3147CEC02CC080570067B5E4 /* LinkInMemoryCookieStore.swift in Sources */,
3147CEC12CC080570067B5E4 /* LinkCookieStore.swift in Sources */,
3147CEC22CC080570067B5E4 /* LinkSecureCookieStore.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// FCLiteImplementation.swift
// StripePaymentSheet
//
// Created by Mat Schmid on 2025-03-25.
//

@_spi(STP) import StripeCore
import UIKit

/// NOTE: If you change the name of this class, make sure to also change it in the `FinancialConnectionsSDKAvailability` file.
@_spi(STP) public class FCLiteImplementation: FinancialConnectionsSDKInterface {
required public init() {}

public func presentFinancialConnectionsSheet(
apiClient: STPAPIClient,
clientSecret: String,
returnURL: String?,
style: FinancialConnectionsStyle,
elementsSessionContext: ElementsSessionContext?,
onEvent: ((FinancialConnectionsEvent) -> Void)?,
from presentingViewController: UIViewController,
completion: @escaping (FinancialConnectionsSDKResult) -> Void
) {
let returnUrl = returnURL.flatMap(URL.init(string:))

let fcLite = FinancialConnectionsLite(
clientSecret: clientSecret,
returnUrl: returnUrl
)
fcLite.present(from: presentingViewController, completion: completion)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ final class PaymentSheetLoader {
let isLinkEnabled = PaymentSheet.isLinkEnabled(elementsSession: elementsSession, configuration: configuration)
let isApplePayEnabled = PaymentSheet.isApplePayEnabled(elementsSession: elementsSession, configuration: configuration)

// Disable FC Lite if killswitch is enabled
let isFcLiteKillswitchEnabled = elementsSession.flags["elements_disable_fc_lite"] == true
FinancialConnectionsSDKAvailability.fcLiteKillswitchEnabled = isFcLiteKillswitchEnabled

// Send load finished analytic
// 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.
let (defaultSelectedIndex, paymentOptionsViewModels) = SavedPaymentOptionsViewController.makeViewModels(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// FCLiteImplementationTests.swift
// StripePaymentSheetTests
//
// Created by Mat Schmid on 2025-03-28.
//

@_spi(STP) import StripeCore
import XCTest

class FCLiteImplementationTests: XCTestCase {
func testFCLiteImplementationAvailable() {
let FinancialConnectionsLiteImplementation: FinancialConnectionsSDKInterface.Type? =
NSClassFromString("StripePaymentSheet.FCLiteImplementation")
as? FinancialConnectionsSDKInterface.Type
XCTAssertNotNil(FinancialConnectionsLiteImplementation)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,29 @@

import Foundation
@_spi(STP) import StripeCore
import SwiftUI
import UIKit

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

static let FinancialConnectionsLiteImplementation: FinancialConnectionsSDKInterface.Type? =
NSClassFromString("StripePaymentSheet.FCLiteImplementation")
as? FinancialConnectionsSDKInterface.Type

@_spi(STP) public static var fcLiteKillswitchEnabled: Bool = false
@_spi(STP) public static var shouldPreferFCLite: Bool = false
// Remove this when ready to release FC Lite:
@_spi(STP) public static var fcLiteFeatureEnabled: Bool = false

private static var FCLiteClassIfEnabled: FinancialConnectionsSDKInterface.Type? {
guard fcLiteFeatureEnabled, !fcLiteKillswitchEnabled else {
return nil
}
return Self.FinancialConnectionsLiteImplementation
}

static let isUnitTest: Bool = {
#if targetEnvironment(simulator)
return NSClassFromString("XCTest") != nil
Expand All @@ -34,14 +49,15 @@ import UIKit

// Return true for unit tests, the value of `FinancialConnectionsSDKAvailable` for UI tests,
// and whether or not the Financial Connections SDK is available otherwise.
// Falls back on FC Lite availability.
@_spi(STP) public static var isFinancialConnectionsSDKAvailable: Bool {
if isUnitTest {
return true
} else if isUITest {
let financialConnectionsSDKAvailable = ProcessInfo.processInfo.environment["FinancialConnectionsSDKAvailable"] == "true"
return financialConnectionsSDKAvailable
} else {
return FinancialConnectionsSDKClass != nil
return (FinancialConnectionsSDKClass != nil || FCLiteClassIfEnabled != nil)
}
}

Expand All @@ -51,7 +67,11 @@ import UIKit
return StubbedConnectionsSDKInterface()
}

guard let klass = FinancialConnectionsSDKClass else {
let klass: FinancialConnectionsSDKInterface.Type? = shouldPreferFCLite
? (FCLiteClassIfEnabled ?? FinancialConnectionsSDKClass)
: (FinancialConnectionsSDKClass ?? FCLiteClassIfEnabled)

guard let klass else {
return nil
}

Expand Down
Loading