Skip to content

Commit

Permalink
FC Networking: implemented broken account support.
Browse files Browse the repository at this point in the history
  • Loading branch information
kgaidis-stripe committed May 18, 2023
1 parent 077a9e6 commit 6a171c5
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ struct FinancialConnectionsPartnerAccount: Decodable {
return nil
}
}
var isBroken: Bool {
return (status != "active")
}
}

struct FinancialConnectionsAuthSessionAccounts: Decodable {
Expand All @@ -41,4 +44,6 @@ struct FinancialConnectionsAuthSessionAccounts: Decodable {

struct FinancialConnectionsNetworkedAccountsResponse: Decodable {
let data: [FinancialConnectionsPartnerAccount]
let repairAuthorizationEnabled: Bool?
let partnerToCoreAuths: [String:String]?
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct FinancialConnectionsSessionManifest: Decodable {
case accountPicker = "account_picker"
case attachLinkedPaymentAccount = "attach_linked_payment_account"
case authOptions = "auth_options"
case bankAuthRepair = "bank_auth_repair"
case consent = "consent"
case institutionPicker = "institution_picker"
case linkAccountPicker = "link_account_picker"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ extension FinancialConnectionsAnalyticsClient {
_ viewController: UIViewController?
) -> FinancialConnectionsSessionManifest.NextPane {
switch viewController {
// TODO(kgaidis): add bank repair
case is ConsentViewController:
return .consent
case is InstitutionPickerViewController:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ protocol LinkAccountPickerBodyViewDelegate: AnyObject {
final class LinkAccountPickerBodyView: UIView {

private let accounts: [FinancialConnectionsPartnerAccount]
private let repairAuthorizationEnabled: Bool
weak var delegate: LinkAccountPickerBodyViewDelegate?

private lazy var verticalStackView: UIStackView = {
Expand All @@ -34,8 +35,9 @@ final class LinkAccountPickerBodyView: UIView {
return verticalStackView
}()

init(accounts: [FinancialConnectionsPartnerAccount]) {
init(accounts: [FinancialConnectionsPartnerAccount], repairAuthorizationEnabled: Bool) {
self.accounts = accounts
self.repairAuthorizationEnabled = repairAuthorizationEnabled
super.init(frame: .zero)
addAndPinSubview(verticalStackView)
}
Expand All @@ -50,9 +52,10 @@ final class LinkAccountPickerBodyView: UIView {

// list all accounts
accounts.forEach { account in
let isDisabled = (account.status != "active")
let isAccountBroken = account.isBroken
let accountRowView = LinkAccountPickerRowView(
isDisabled: isDisabled,
isDisabled: isAccountBroken && !repairAuthorizationEnabled,
isBroken: isAccountBroken,
didSelect: { [weak self] in
guard let self = self else { return }
self.delegate?.linkAccountPickerBodyView(
Expand All @@ -61,13 +64,12 @@ final class LinkAccountPickerBodyView: UIView {
)
}
)
// TODO(kgaidis): when we implement repair logic, this will have new text
let rowTitles = AccountPickerHelpers.rowTitles(forAccount: account)
accountRowView.configure(
institutionImageUrl: account.institution?.icon?.default,
leadingTitle: rowTitles.leadingTitle,
trailingTitle: rowTitles.trailingTitle,
subtitle: isDisabled ? STPLocalizedString("Disconnected", "A subtitle on a button that represents a bank account. It explains to the user that this bank account is disconnected and needs to be re-added.") : AccountPickerHelpers.rowSubtitle(forAccount: account),
subtitle: isAccountBroken ? STPLocalizedString("Select to repair and connect", "A subtitle on a button that represents a bank account. It explains to the user that this bank account is disconnected from the bank and needs to be re-connected by going through the bank process.") : AccountPickerHelpers.rowSubtitle(forAccount: account),
isSelected: selectedAccount?.id == account.id
)
verticalStackView.addArrangedSubview(accountRowView)
Expand Down Expand Up @@ -128,7 +130,8 @@ private struct LinkAccountPickerBodyViewUIViewRepresentable: UIViewRepresentable
status: "disabled",
institution: nil
),
]
],
repairAuthorizationEnabled: true
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ final class LinkAccountPickerDataSourceImplementation: LinkAccountPickerDataSour
private let apiClient: FinancialConnectionsAPIClient
private let clientSecret: String
private let consumerSession: ConsumerSessionData
private var partnerToCoreAuths: [String:String]?

private(set) var selectedAccount: FinancialConnectionsPartnerAccount? {
didSet {
Expand All @@ -60,7 +61,10 @@ final class LinkAccountPickerDataSourceImplementation: LinkAccountPickerDataSour
return apiClient.fetchNetworkedAccounts(
clientSecret: clientSecret,
consumerSessionClientSecret: consumerSession.clientSecret
)
).chained { [weak self] response in
self?.partnerToCoreAuths = response.partnerToCoreAuths
return Promise(value: response)
}
}

func updateSelectedAccount(_ selectedAccount: FinancialConnectionsPartnerAccount) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ final class LinkAccountPickerFooterView: UIView {

private lazy var connectAccountButton: Button = {
let connectAccountButton = Button(configuration: .financialConnectionsPrimary)
connectAccountButton.title = STPLocalizedString(
"Connect account",
"A button that allows users to confirm the process of saving their bank accounts for future payments. This button appears in a screen that allows users to select which bank accounts they want to use to pay for something."
)
connectAccountButton.isEnabled = false // disable by default
connectAccountButton.addTarget(self, action: #selector(didSelectLinkAccountsButton), for: .touchUpInside)
connectAccountButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
Expand Down Expand Up @@ -60,6 +55,9 @@ final class LinkAccountPickerFooterView: UIView {
verticalStackView.axis = .vertical
verticalStackView.spacing = 24
addAndPinSubview(verticalStackView)

// setup the button
userSelectedAccount(nil)
}

required init?(coder: NSCoder) {
Expand All @@ -70,7 +68,19 @@ final class LinkAccountPickerFooterView: UIView {
didSelectConnectAccount()
}

func enableButton(_ enableButton: Bool) {
connectAccountButton.isEnabled = enableButton
func userSelectedAccount(_ selectedAccount: FinancialConnectionsPartnerAccount?) {
connectAccountButton.isEnabled = (selectedAccount != nil)

if let selectedAccount = selectedAccount, selectedAccount.isBroken {
connectAccountButton.title = STPLocalizedString(
"Repair and connect account",
"A button that initiates the process of repairing and connecting a users bank account. A bank account that needs to be repaired is one that lost connection to the users bank. This button appears in a screen that allows users to select which bank accounts they want to use to pay for something."
)
} else {
connectAccountButton.title = STPLocalizedString(
"Connect account",
"A button that allows users to confirm the process of saving their bank accounts for future payments. This button appears in a screen that allows users to select which bank accounts they want to use to pay for something."
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ final class LinkAccountPickerRowView: UIView {

init(
isDisabled: Bool,
isBroken: Bool,
didSelect: @escaping () -> Void
) {
self.didSelect = didSelect
Expand All @@ -49,6 +50,17 @@ final class LinkAccountPickerRowView: UIView {
if isDisabled {
horizontalStackView.alpha = 0.25
}
if isBroken {
let warningIconImageView = UIImageView()
warningIconImageView.image = Image.warning_triangle.makeImage()
.withTintColor(.textCritical)
warningIconImageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
warningIconImageView.widthAnchor.constraint(equalToConstant: 15),
warningIconImageView.heightAnchor.constraint(equalToConstant: 15),
])
horizontalStackView.addArrangedSubview(warningIconImageView)
}
addAndPinSubviewToSafeArea(horizontalStackView)

if !isDisabled {
Expand Down Expand Up @@ -118,10 +130,12 @@ private struct LinkAccountPickerRowViewUIViewRepresentable: UIViewRepresentable
let subtitle: String?
let isSelected: Bool
let isDisabled: Bool
let isBroken: Bool

func makeUIView(context: Context) -> LinkAccountPickerRowView {
let view = LinkAccountPickerRowView(
isDisabled: isDisabled,
isBroken: isBroken,
didSelect: {}
)
view.configure(
Expand Down Expand Up @@ -159,23 +173,35 @@ struct LinkAccountPickerRowView_Previews: PreviewProvider {
trailingTitle: "••••6789",
subtitle: "$2,000",
isSelected: true,
isDisabled: false
isDisabled: false,
isBroken: false
).frame(height: 60)
LinkAccountPickerRowViewUIViewRepresentable(
institutionImageUrl: nil,
leadingTitle: "Joint Checking",
trailingTitle: nil,
subtitle: nil,
isSelected: false,
isDisabled: false
isDisabled: false,
isBroken: false
).frame(height: 60)
LinkAccountPickerRowViewUIViewRepresentable(
institutionImageUrl: nil,
leadingTitle: "Joint Checking",
trailingTitle: nil,
subtitle: "Select to repair and connect",
isSelected: false,
isDisabled: false,
isBroken: true
).frame(height: 60)
LinkAccountPickerRowViewUIViewRepresentable(
institutionImageUrl: nil,
leadingTitle: "Joint Checking",
trailingTitle: nil,
subtitle: "Must be US checking account",
isSelected: false,
isDisabled: true
isDisabled: true,
isBroken: true
).frame(height: 60)
}
}.padding()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,24 @@ final class LinkAccountPickerViewController: UIViewController {

switch result {
case .success(let networkedAccountsResponse):
self.displayAccounts(networkedAccountsResponse.data)
self.display(
accounts: networkedAccountsResponse.data.map {
FinancialConnectionsPartnerAccount(
id: $0.id,
name: $0.name,
displayableAccountNumbers: $0.displayableAccountNumbers,
linkedAccountId: $0.linkedAccountId,
balanceAmount: $0.balanceAmount,
currency: $0.currency,
supportedPaymentMethodTypes: $0.supportedPaymentMethodTypes,
allowSelection: $0.allowSelection,
allowSelectionMessage: $0.allowSelectionMessage,
status: Bool.random() ? "inactive" : $0.status,
institution: $0.institution
)
},
repairAuthorizationEnabled: (networkedAccountsResponse.repairAuthorizationEnabled ?? false)
)
case .failure(let error):
self.dataSource
.analyticsClient
Expand All @@ -108,8 +125,11 @@ final class LinkAccountPickerViewController: UIViewController {
}
}

private func displayAccounts(_ accounts: [FinancialConnectionsPartnerAccount]) {
let bodyView = LinkAccountPickerBodyView(accounts: accounts)
private func display(accounts: [FinancialConnectionsPartnerAccount], repairAuthorizationEnabled: Bool) {
let bodyView = LinkAccountPickerBodyView(
accounts: accounts,
repairAuthorizationEnabled: repairAuthorizationEnabled
)
bodyView.delegate = self
self.bodyView = bodyView

Expand Down Expand Up @@ -139,15 +159,22 @@ final class LinkAccountPickerViewController: UIViewController {
}

private func didSelectConectAccount() {
// TODO(kgaidis): implement repair bank account

guard let selectedAccount = dataSource.selectedAccount else {
assertionFailure("user shouldn't be able to press the connect account button without an account")
delegate?.linkAccountPickerViewController(self, didRequestNextPane: .institutionPicker)
return
}

if dataSource.manifest.stepUpAuthenticationRequired == true {
if selectedAccount.isBroken {
// TODO(kgaidis): handle partnerToCoreAuths and setPartnerToCoreAuths
dataSource
.analyticsClient
.log(
eventName: "click.repair_accounts",
pane: .linkAccountPicker
)
delegate?.linkAccountPickerViewController(self, didRequestNextPane: .bankAuthRepair)
} else if dataSource.manifest.stepUpAuthenticationRequired == true {
delegate?.linkAccountPickerViewController(self, requestedStepUpVerificationWithSelectedAccount: selectedAccount)
} else {
let linkingAccountsLoadingView = LinkingAccountsLoadingView(
Expand Down Expand Up @@ -235,6 +262,6 @@ extension LinkAccountPickerViewController: LinkAccountPickerDataSourceDelegate {
didSelectAccount selectedAccount: FinancialConnectionsPartnerAccount?
) {
bodyView?.selectAccount(selectedAccount)
footerView.enableButton(selectedAccount != nil)
footerView.userSelectedAccount(selectedAccount)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,8 @@ private func CreatePaneViewController(
assertionFailure("Code logic error. Missing parameters for \(pane).")
viewController = nil
}
case .bankAuthRepair:
viewController = nil // TODO(kgaidis): implement
case .consent:
let consentDataSource = ConsentDataSourceImplementation(
manifest: dataManager.manifest,
Expand Down

0 comments on commit 6a171c5

Please sign in to comment.