Skip to content

Commit

Permalink
Merge pull request #1456 from stripe-ios/kg-accountpicker-errors
Browse files Browse the repository at this point in the history
Financial Connections: Implemented new native Account Picker errors
  • Loading branch information
kgaidis-stripe authored Sep 26, 2022
2 parents 1d1677d + 8af7d35 commit 12d5f91
Show file tree
Hide file tree
Showing 13 changed files with 613 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@
6AE2E5AF28DC928A00623523 /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE2E5AE28DC928A00623523 /* NSAttributedString+Extensions.swift */; };
6AE2E5B128DC990700623523 /* MarkdownBoldAttributedStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE2E5B028DC990700623523 /* MarkdownBoldAttributedStringTests.swift */; };
6AE2E5B328DCFBEF00623523 /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE2E5B228DCFBEF00623523 /* String+Localized.swift */; };
6AE2E5B528DE132300623523 /* AccountPickerAccountLoadErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE2E5B428DE132300623523 /* AccountPickerAccountLoadErrorView.swift */; };
6AE2E5B728DE7F4400623523 /* AccountPickerNoAccountEligibleErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE2E5B628DE7F4400623523 /* AccountPickerNoAccountEligibleErrorView.swift */; };
6AE5171828AAE2C0006E8314 /* SuccessHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE5171728AAE2C0006E8314 /* SuccessHeaderView.swift */; };
6AE5171A28AAE46A006E8314 /* SuccessFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE5171928AAE46A006E8314 /* SuccessFooterView.swift */; };
6AE5171C28AAF099006E8314 /* SuccessBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE5171B28AAF099006E8314 /* SuccessBodyView.swift */; };
Expand Down Expand Up @@ -298,6 +300,8 @@
6AE2E5AE28DC928A00623523 /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = "<group>"; };
6AE2E5B028DC990700623523 /* MarkdownBoldAttributedStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownBoldAttributedStringTests.swift; sourceTree = "<group>"; };
6AE2E5B228DCFBEF00623523 /* String+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localized.swift"; sourceTree = "<group>"; };
6AE2E5B428DE132300623523 /* AccountPickerAccountLoadErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerAccountLoadErrorView.swift; sourceTree = "<group>"; };
6AE2E5B628DE7F4400623523 /* AccountPickerNoAccountEligibleErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerNoAccountEligibleErrorView.swift; sourceTree = "<group>"; };
6AE5171728AAE2C0006E8314 /* SuccessHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuccessHeaderView.swift; sourceTree = "<group>"; };
6AE5171928AAE46A006E8314 /* SuccessFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuccessFooterView.swift; sourceTree = "<group>"; };
6AE5171B28AAF099006E8314 /* SuccessBodyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuccessBodyView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -622,6 +626,8 @@
6A1E29A928A3FF8400F99E9D /* CheckboxView.swift */,
6A2318CF28AE7EA700F2A7D8 /* RadioButtonView.swift */,
6A8B4AF928CAD40600128356 /* AccountPickerHelpers.swift */,
6AE2E5B428DE132300623523 /* AccountPickerAccountLoadErrorView.swift */,
6AE2E5B628DE7F4400623523 /* AccountPickerNoAccountEligibleErrorView.swift */,
);
path = AccountPicker;
sourceTree = "<group>";
Expand Down Expand Up @@ -878,6 +884,7 @@
6A8B4B0728D0BE4600128356 /* ConsentDataSource.swift in Sources */,
3C5B2CD3273DB3B9005C5A05 /* STPLocalizedString.swift in Sources */,
6A1E29AF28A71C5D00F99E9D /* SuccessDataSource.swift in Sources */,
6AE2E5B528DE132300623523 /* AccountPickerAccountLoadErrorView.swift in Sources */,
6A7C861728D273940025B8DF /* SuccessIconView.swift in Sources */,
6A9117E5287CA2A5007633D4 /* ConsentModel.swift in Sources */,
6A1E299E289DB83E00F99E9D /* AccountPickerDataSource.swift in Sources */,
Expand Down Expand Up @@ -916,6 +923,7 @@
6ABE2D0A285B7BA20064B3A4 /* SFSafariViewController+Extensions.swift in Sources */,
6A1E29A428A3E59F00F99E9D /* AccountPickerFooterView.swift in Sources */,
6A9117EC287F4D87007633D4 /* DataAccessNoticeViewController.swift in Sources */,
6AE2E5B728DE7F4400623523 /* AccountPickerNoAccountEligibleErrorView.swift in Sources */,
6AE2E5A928D92A8200623523 /* InstitutionSearchFooterView.swift in Sources */,
3C5430E0274589E000B1E488 /* FinancialConnectionsSheetError.swift in Sources */,
3CA52D4A27517239001F511E /* UIColor+Extensions.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
import Foundation
@_spi(STP) import StripeCore

enum FinancialConnectionsPaymentMethodType: String, SafeEnumCodable, Equatable {
case usBankAccount = "us_bank_account"
case link = "link"
case unparsable
}

struct FinancialConnectionsSessionManifest: Decodable {

// MARK: - Types
Expand Down Expand Up @@ -60,7 +66,7 @@ struct FinancialConnectionsSessionManifest: Decodable {
let nextPane: NextPane
let permissions: [StripeAPI.FinancialConnectionsAccount.Permissions]
let singleAccount: Bool
let paymentMethodType: String?
let paymentMethodType: FinancialConnectionsPaymentMethodType?
}

struct FinancialConnectionsAuthorizationSession: Decodable {
Expand Down Expand Up @@ -183,7 +189,7 @@ struct FinancialConnectionsPartnerAccount: Decodable {
let linkedAccountId: String? // determines whether we show a "Linked" label
let balanceAmount: Double?
let currency: String?
let supportedPaymentMethodTypes: [String]
let supportedPaymentMethodTypes: [FinancialConnectionsPaymentMethodType]

var balanceInfo: (balanceAmount: Double, currency: String)? {
if let balanceAmount = balanceAmount, let currency = currency {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,16 @@ extension String.Localized {
static var learn_more: String {
return STPLocalizedString("Learn more", "Represents the text of a button that can be clicked to learn more about some topic. Once clicked, a web-browser will be opened to give users more info.")
}

static var select_another_bank: String {
return STPLocalizedString("Select another bank", "The title of a button. The button presents the user an option to select another bank. For example, we may show this button after user failed to link their primary bank, but maybe the user can try to link their secondary bank!")
}

static var enter_bank_details_manually: String {
return STPLocalizedString("Enter bank details manually", "The title of a button. The button presents the user an option to enter their bank details (account number, routing number) manually. For example, we may show this button after user failed to link their bank 'automatically' with Stripe, so we offer them the option manually link it.")
}

static var link_another_account: String {
return STPLocalizedString("Link another account", "The title of a button that, once clicked, allows the user to connect (or link) an additional bank account. Once the bank accounts are connected (or linked), the user will be able to use those bank accounts for payments.")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//
// AccountPickerAccountLoadFailureView.swift
// StripeFinancialConnections
//
// Created by Krisjanis Gaidis on 9/23/22.
//

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

final class AccountPickerAccountLoadErrorView: UIView {

init(
institution: FinancialConnectionsInstitution,
didSelectAnotherBank: @escaping () -> Void,
didSelectTryAgain: (() -> Void)?, // if nil, don't show button
didSelectEnterBankDetailsManually: (() -> Void)? // if nil, don't show button
) {
super.init(frame: .zero)

let subtitle: String
let secondaryButtonConfiguration: ReusableInformationView.ButtonConfiguration?
let primaryButtonConfiguration: ReusableInformationView.ButtonConfiguration

if let didSelectTryAgain = didSelectTryAgain {
subtitle = STPLocalizedString("Please select another bank or try again.", "The subtitle/description of a screen that shows an error. The error appears after we failed to load users bank accounts. Here we instruct the user to select another bank or to try loading their bank accounts again.")
secondaryButtonConfiguration = ReusableInformationView.ButtonConfiguration(
title: String.Localized.select_another_bank,
action: didSelectAnotherBank
)
primaryButtonConfiguration = ReusableInformationView.ButtonConfiguration(
title: "Try again", // TODO: once we localize, pull in the string from StripeCore `String.Localized.tryAgain`
action: didSelectTryAgain
)
} else if let didSelectEnterBankDetailsManually = didSelectEnterBankDetailsManually {
subtitle = STPLocalizedString("Please enter your bank details manually or select another bank.", "The subtitle/description of a screen that shows an error. The error appears after we failed to load users bank accounts. Here we instruct the user to enter their bank details manually or to try selecting another bank.")
secondaryButtonConfiguration = ReusableInformationView.ButtonConfiguration(
title: String.Localized.enter_bank_details_manually,
action: didSelectEnterBankDetailsManually
)
primaryButtonConfiguration = ReusableInformationView.ButtonConfiguration(
title: String.Localized.select_another_bank,
action: didSelectAnotherBank
)
} else {
subtitle = STPLocalizedString("Please select another bank.", "The subtitle/description of a screen that shows an error. The error appears after we failed to load users bank accounts. Here we instruct the user to try selecting another bank.")
secondaryButtonConfiguration = nil
primaryButtonConfiguration = ReusableInformationView.ButtonConfiguration(
title: String.Localized.select_another_bank,
action: didSelectAnotherBank
)
}

let reusableInformationView = ReusableInformationView(
iconType: .icon, // TODO(kgaidis): set institution image with exclamation error
title: String(format: STPLocalizedString("There was a problem accessing your %@ account", "The title of a screen that shows an error. The error appears after we failed to load users bank accounts. Here we describe to the user that we had issues with the bank. '%@' gets replaced by the name of the bank."), institution.name),
subtitle: subtitle,
primaryButtonConfiguration: primaryButtonConfiguration,
secondaryButtonConfiguration: secondaryButtonConfiguration
)
addAndPinSubview(reusableInformationView)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}


#if DEBUG

import SwiftUI

@available(iOS 13.0, *)
@available(iOSApplicationExtension, unavailable)
private struct AccountPickerAccountLoadErrorViewUIViewRepresentable: UIViewRepresentable {

let institutionName: String
let didSelectTryAgain: (() -> Void)?
let didSelectEnterBankDetailsManually: (() -> Void)?

func makeUIView(context: Context) -> AccountPickerAccountLoadErrorView {
AccountPickerAccountLoadErrorView(
institution: FinancialConnectionsInstitution(
id: "123",
name: institutionName,
url: nil
),
didSelectAnotherBank: {},
didSelectTryAgain: didSelectTryAgain,
didSelectEnterBankDetailsManually: didSelectEnterBankDetailsManually
)
}

func updateUIView(_ uiView: AccountPickerAccountLoadErrorView, context: Context) {}
}

@available(iOSApplicationExtension, unavailable)
struct AccountPickerAccountLoadErrorView_Previews: PreviewProvider {
@available(iOS 13.0.0, *)
static var previews: some View {
AccountPickerAccountLoadErrorViewUIViewRepresentable(
institutionName: "Chase",
didSelectTryAgain: {},
didSelectEnterBankDetailsManually: {}
)

AccountPickerAccountLoadErrorViewUIViewRepresentable(
institutionName: "Ally",
didSelectTryAgain: nil,
didSelectEnterBankDetailsManually: {}
)

AccountPickerAccountLoadErrorViewUIViewRepresentable(
institutionName: "Chase",
didSelectTryAgain: {},
didSelectEnterBankDetailsManually: nil
)

AccountPickerAccountLoadErrorViewUIViewRepresentable(
institutionName: "Chase",
didSelectTryAgain: nil,
didSelectEnterBankDetailsManually: nil
)
}
}

#endif
Loading

0 comments on commit 12d5f91

Please sign in to comment.