Skip to content

Commit

Permalink
Merge pull request #1536 from stripe-ios/kg-copyfrombackend
Browse files Browse the repository at this point in the history
Financial Connections: Implemented Consent From Backend (Synchronized API)
  • Loading branch information
kgaidis-stripe authored Oct 26, 2022
2 parents aabbc75 + 11eb6e0 commit 35f4ba1
Show file tree
Hide file tree
Showing 20 changed files with 369 additions and 289 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
6A2318D428B3C36000F2A7D8 /* AccountPickerSelectionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2318D328B3C36000F2A7D8 /* AccountPickerSelectionListView.swift */; };
6A2318D728B571E000F2A7D8 /* ManualEntryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2318D628B571E000F2A7D8 /* ManualEntryViewController.swift */; };
6A2318D928B57E5100F2A7D8 /* ManualEntryTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2318D828B57E5100F2A7D8 /* ManualEntryTextField.swift */; };
6A370F322901CA2C00A6DB9B /* FinancialConnectionsSynchronize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A370F312901CA2C00A6DB9B /* FinancialConnectionsSynchronize.swift */; };
6A4FA13928E11B3D00F07D42 /* CloseConfirmationAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A4FA13828E11B3D00F07D42 /* CloseConfirmationAlertHandler.swift */; };
6A4FA13B28E3340B00F07D42 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 6A4FA13A28E3340B00F07D42 /* [email protected] */; };
6A4FA13D28E346E600F07D42 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 6A4FA13C28E346E600F07D42 /* [email protected] */; };
Expand Down Expand Up @@ -122,7 +123,6 @@
6A8B4B0328CFC7C600128356 /* PaneLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8B4B0228CFC7C600128356 /* PaneLayoutView.swift */; };
6A8B4B0528CFD31800128356 /* PaneWithHeaderLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8B4B0428CFD31800128356 /* PaneWithHeaderLayoutView.swift */; };
6A8B4B0728D0BE4600128356 /* ConsentDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8B4B0628D0BE4600128356 /* ConsentDataSource.swift */; };
6A9117E5287CA2A5007633D4 /* ConsentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A9117E4287CA2A5007633D4 /* ConsentModel.swift */; };
6A9117E7287CB20D007633D4 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A9117E6287CB20D007633D4 /* String+Extensions.swift */; };
6A9117EC287F4D87007633D4 /* DataAccessNoticeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A9117EB287F4D87007633D4 /* DataAccessNoticeViewController.swift */; };
6A9117EE287F535C007633D4 /* DataAccessNoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A9117ED287F535C007633D4 /* DataAccessNoticeView.swift */; };
Expand Down Expand Up @@ -295,6 +295,7 @@
6A2318D328B3C36000F2A7D8 /* AccountPickerSelectionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerSelectionListView.swift; sourceTree = "<group>"; };
6A2318D628B571E000F2A7D8 /* ManualEntryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualEntryViewController.swift; sourceTree = "<group>"; };
6A2318D828B57E5100F2A7D8 /* ManualEntryTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualEntryTextField.swift; sourceTree = "<group>"; };
6A370F312901CA2C00A6DB9B /* FinancialConnectionsSynchronize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSynchronize.swift; sourceTree = "<group>"; };
6A4FA13828E11B3D00F07D42 /* CloseConfirmationAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseConfirmationAlertHandler.swift; sourceTree = "<group>"; };
6A4FA13A28E3340B00F07D42 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
6A4FA13C28E346E600F07D42 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -337,7 +338,6 @@
6A8B4B0228CFC7C600128356 /* PaneLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaneLayoutView.swift; sourceTree = "<group>"; };
6A8B4B0428CFD31800128356 /* PaneWithHeaderLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaneWithHeaderLayoutView.swift; sourceTree = "<group>"; };
6A8B4B0628D0BE4600128356 /* ConsentDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentDataSource.swift; sourceTree = "<group>"; };
6A9117E4287CA2A5007633D4 /* ConsentModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentModel.swift; sourceTree = "<group>"; };
6A9117E6287CB20D007633D4 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
6A9117EB287F4D87007633D4 /* DataAccessNoticeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataAccessNoticeViewController.swift; sourceTree = "<group>"; };
6A9117ED287F535C007633D4 /* DataAccessNoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataAccessNoticeView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -527,6 +527,7 @@
3C34E0E82798EB33002618E4 /* FinancialConnectionsSession.swift */,
3C3ADCB92800C1E0008C24EF /* BankAccountToken.swift */,
3CAD99BA284E381100B163EB /* FinancialConnectionsInstitution.swift */,
6A370F312901CA2C00A6DB9B /* FinancialConnectionsSynchronize.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -689,7 +690,6 @@
children = (
6A8B4B0628D0BE4600128356 /* ConsentDataSource.swift */,
6A1BA4AB2858D5F100759697 /* ConsentViewController.swift */,
6A9117E4287CA2A5007633D4 /* ConsentModel.swift */,
6ABE2D03285A2DEF0064B3A4 /* ConsentBodyView.swift */,
6A1BA4AD2858D76800759697 /* ConsentFooterView.swift */,
);
Expand Down Expand Up @@ -1003,7 +1003,6 @@
6A4FA14D28E477EA00F07D42 /* SpinnerIconView.swift in Sources */,
6AE2E5B528DE132300623523 /* AccountPickerAccountLoadErrorView.swift in Sources */,
6A7C861728D273940025B8DF /* SuccessIconView.swift in Sources */,
6A9117E5287CA2A5007633D4 /* ConsentModel.swift in Sources */,
6A1E299E289DB83E00F99E9D /* AccountPickerDataSource.swift in Sources */,
6A99EF6628E708D200C76293 /* Button+Extensions.swift in Sources */,
6AD448B928C25F85002CABB0 /* ResetFlowDataSource.swift in Sources */,
Expand Down Expand Up @@ -1074,6 +1073,7 @@
6A2318D428B3C36000F2A7D8 /* AccountPickerSelectionListView.swift in Sources */,
6A1E29A228A1BB9100F99E9D /* PartnerAuthDataSource.swift in Sources */,
3C2432F3275AB8140031E9B9 /* StripeCore+Import.swift in Sources */,
6A370F322901CA2C00A6DB9B /* FinancialConnectionsSynchronize.swift in Sources */,
6ABE2D06285B72A30064B3A4 /* ClickableLabel.swift in Sources */,
3CAD99C7285032E600B163EB /* InstitutionPicker.swift in Sources */,
6AE2E5AD28DB916E00623523 /* MerchantDataAccessView.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct APIVersion {

- Note: Update this value when a new API version is ready for use in production.
*/
private static let apiVersion: Int = 1
static let apiVersion: Int = 1 // WARNING: this is also referenced in other places, so double check changes!
private static let header = "financial_connections_client_api_beta=v\(apiVersion)"

static func configureFinancialConnectionsAPIVersion(apiClient: STPAPIClient) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

protocol FinancialConnectionsAPIClient {

func generateSessionManifest(clientSecret: String, returnURL: String?) -> Promise<FinancialConnectionsSessionManifest>
func generateSessionManifest(clientSecret: String, returnURL: String?) -> Promise<FinancialConnectionsSynchronize>

func fetchFinancialConnectionsAccounts(clientSecret: String,
startingAfterAccountId: String?) -> Promise<StripeAPI.FinancialConnectionsSession.AccountList>
Expand Down Expand Up @@ -76,11 +76,26 @@ extension STPAPIClient: FinancialConnectionsAPIClient {
return self.get(resource: APIEndpointSessionReceipt,
parameters: ["client_secret": clientSecret])
}

func generateSessionManifest(clientSecret: String, returnURL: String?) -> Promise<FinancialConnectionsSessionManifest> {
let body = FinancialConnectionsSessionsGenerateHostedUrlBody(clientSecret: clientSecret, fullscreen: true, hideCloseButton: true, appReturnUrl: returnURL)
return self.post(resource: APIEndpointGenerateHostedURL,
object: body)

func generateSessionManifest(clientSecret: String, returnURL: String?) -> Promise<FinancialConnectionsSynchronize> {
let parameters: [String: Any] = [
"client_secret": clientSecret,
"mobile": {
var mobileParameters: [String:Any] = [
"sdk_type": "ios",
"fullscreen": true,
"hide_close_button": true,
"sdk_version": APIVersion.apiVersion,
]
mobileParameters["app_return_url"] = returnURL
return mobileParameters
}(),
"locale": Locale.current.identifier,
]
return self.post(
resource: "financial_connections/sessions/synchronize",
parameters: parameters
)
}

func markConsentAcquired(clientSecret: String) -> Promise<FinancialConnectionsSessionManifest> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@
import Foundation
@_spi(STP) import StripeCore

struct FinancialConnectionsSessionsGenerateHostedUrlBody: Encodable {
let clientSecret: String
let fullscreen: Bool
let hideCloseButton: Bool
let appReturnUrl: String?
}

struct FinancialConnectionsSessionsClientSecretBody: Encodable {
let clientSecret: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// FinancialConnectionsSynchronize.swift
// StripeFinancialConnections
//
// Created by Krisjanis Gaidis on 10/20/22.
//

import Foundation

struct FinancialConnectionsSynchronize: Decodable {

let manifest: FinancialConnectionsSessionManifest
let text: Text

struct Text: Decodable {
let consentPane: FinancialConnectionsConsent
}
}

struct FinancialConnectionsConsent: Decodable {

let title: String
let body: Body
let aboveCta: String
let cta: String
let belowCta: String?

let dataAccessNotice: FinancialConnectionsDataAccessNotice

struct Body: Decodable {
let bullets: [BulletItem]

struct BulletItem: Decodable {
let icon: String
let content: String
}
}
}

struct FinancialConnectionsDataAccessNotice: Decodable {
let title: String
let body: Body
let learnMore: String
let cta: String

struct Body: Decodable {
let bullets: [BulletItem]

struct BulletItem: Decodable {
let icon: String
let title: String?
let content: String
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,16 @@ extension HostController: HostViewControllerDelegate {
delegate?.hostController(self, viewController: viewController, didFinish: .failed(error: error))
}

func hostViewController(_ viewController: HostViewController, didFetch manifest: FinancialConnectionsSessionManifest) {
func hostViewController(_ viewController: HostViewController, didFetch synchronizePayload: FinancialConnectionsSynchronize) {
guard useNative else {
continueWithWebFlow(manifest)
continueWithWebFlow(synchronizePayload.manifest)
return
}

navigationController.configureAppearanceForNative()

let dataManager = AuthFlowAPIDataManager(
manifest: manifest,
synchronizePayload: synchronizePayload,
apiClient: api,
clientSecret: clientSecret,
analyticsClient: analyticsClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ protocol HostViewControllerDelegate: AnyObject {

func hostViewController(
_ viewController: HostViewController,
didFetch manifest: FinancialConnectionsSessionManifest
didFetch synchronizePayload: FinancialConnectionsSynchronize
)
}

Expand Down Expand Up @@ -96,9 +96,9 @@ extension HostViewController {
.observe { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let manifest):
case .success(let synchronizePayload):
self.lastError = nil
self.delegate?.hostViewController(self, didFetch: manifest)
self.delegate?.hostViewController(self, didFetch: synchronizePayload)
case .failure(let error):
self.loadingView.activityIndicatorView.stp_stopAnimatingAndHide()
self.loadingView.errorView.isHidden = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ private func CreatePaneViewController(
case .consent:
let consentDataSource = ConsentDataSourceImplementation(
manifest: dataManager.manifest,
consentModel: ConsentModel(businessName: dataManager.manifest.businessName),
consent: dataManager.consent,
apiClient: dataManager.apiClient,
clientSecret: dataManager.clientSecret,
analyticsClient: dataManager.analyticsClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Foundation

protocol AuthFlowDataManager: AnyObject {
var manifest: FinancialConnectionsSessionManifest { get set }
var consent: FinancialConnectionsConsent { get }
var apiClient: FinancialConnectionsAPIClient { get }
var clientSecret: String { get }
var analyticsClient: FinancialConnectionsAnalyticsClient { get }
Expand All @@ -32,6 +33,7 @@ class AuthFlowAPIDataManager: AuthFlowDataManager {
didUpdateManifest()
}
}
let consent: FinancialConnectionsConsent
let apiClient: FinancialConnectionsAPIClient
let clientSecret: String
let analyticsClient: FinancialConnectionsAnalyticsClient
Expand All @@ -44,12 +46,13 @@ class AuthFlowAPIDataManager: AuthFlowDataManager {
var accountNumberLast4: String?

init(
manifest: FinancialConnectionsSessionManifest,
synchronizePayload: FinancialConnectionsSynchronize,
apiClient: FinancialConnectionsAPIClient,
clientSecret: String,
analyticsClient: FinancialConnectionsAnalyticsClient
) {
self.manifest = manifest
self.manifest = synchronizePayload.manifest
self.consent = synchronizePayload.text.consentPane
self.apiClient = apiClient
self.clientSecret = clientSecret
self.analyticsClient = analyticsClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,31 @@
//

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

@available(iOSApplicationExtension, unavailable)
class ConsentBodyView: UIView {

private let bulletItems: [ConsentModel.BodyBulletItem]
private let dataAccessNoticeModel: DataAccessNoticeModel
private let bulletItems: [FinancialConnectionsConsent.Body.BulletItem]

init(
bulletItems: [ConsentModel.BodyBulletItem],
dataAccessNoticeModel: DataAccessNoticeModel
bulletItems: [FinancialConnectionsConsent.Body.BulletItem],
didSelectURL: @escaping (URL) -> Void
) {
self.bulletItems = bulletItems
self.dataAccessNoticeModel = dataAccessNoticeModel
super.init(frame: .zero)

backgroundColor = .customBackgroundColor

let verticalStackView = UIStackView()
verticalStackView.axis = .vertical
verticalStackView.spacing = 16

let linkAction: (URL) -> Void = { url in
if let scheme = url.scheme, scheme.contains("stripe") {
let dataAccessNoticeViewController = DataAccessNoticeViewController(model: dataAccessNoticeModel)
dataAccessNoticeViewController.modalTransitionStyle = .crossDissolve
dataAccessNoticeViewController.modalPresentationStyle = .overCurrentContext
// `false` for animations because we do a custom animation inside VC logic
UIViewController
.topMostViewController()?
.present(dataAccessNoticeViewController, animated: false, completion: nil)
} else {
SFSafariViewController.present(url: url)
}
}
bulletItems.forEach { item in
bulletItems.forEach { bulletItem in
verticalStackView.addArrangedSubview(
CreateLabelView(
text: item.text,
action: linkAction
text: bulletItem.content,
action: didSelectURL
)
)
}
Expand Down Expand Up @@ -101,20 +83,20 @@ private struct ConsentBodyViewUIViewRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> ConsentBodyView {
ConsentBodyView(
bulletItems: [
ConsentModel.BodyBulletItem(
iconUrl: URL(string: "https://www.google.com/image.png")!,
text: "Stripe will allow Goldilocks to access only the [data requested](https://www.google.com). We never share your login details with them."
FinancialConnectionsConsent.Body.BulletItem(
icon: "...",
content: "Stripe will allow Goldilocks to access only the [data requested](https://www.stripe.com). We never share your login details with them."
),
ConsentModel.BodyBulletItem(
iconUrl: URL(string: "https://www.google.com/image.png")!,
text: "Your data is encrypted for your protection."
FinancialConnectionsConsent.Body.BulletItem(
icon: "...",
content: "Your data is encrypted for your protection."
),
ConsentModel.BodyBulletItem(
iconUrl: URL(string: "https://www.google.com/image.png")!,
text: "You can [disconnect](meow.com) your accounts at any time."
FinancialConnectionsConsent.Body.BulletItem(
icon: "...",
content: "You can [disconnect](https://www.stripe.com) your accounts at any time."
),
],
dataAccessNoticeModel: DataAccessNoticeModel(businessName: "Coca-Cola Inc")
didSelectURL: { _ in }
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

protocol ConsentDataSource: AnyObject {
var manifest: FinancialConnectionsSessionManifest { get }
var consentModel: ConsentModel { get }
var consent: FinancialConnectionsConsent { get }
var analyticsClient: FinancialConnectionsAnalyticsClient { get }

func markConsentAcquired() -> Promise<FinancialConnectionsSessionManifest>
Expand All @@ -19,20 +19,20 @@ protocol ConsentDataSource: AnyObject {
final class ConsentDataSourceImplementation: ConsentDataSource {

let manifest: FinancialConnectionsSessionManifest
let consentModel: ConsentModel
let consent: FinancialConnectionsConsent
private let apiClient: FinancialConnectionsAPIClient
private let clientSecret: String
let analyticsClient: FinancialConnectionsAnalyticsClient

init(
manifest: FinancialConnectionsSessionManifest,
consentModel: ConsentModel,
consent: FinancialConnectionsConsent,
apiClient: FinancialConnectionsAPIClient,
clientSecret: String,
analyticsClient: FinancialConnectionsAnalyticsClient
) {
self.manifest = manifest
self.consentModel = consentModel
self.consent = consent
self.apiClient = apiClient
self.clientSecret = clientSecret
self.analyticsClient = analyticsClient
Expand Down
Loading

0 comments on commit 35f4ba1

Please sign in to comment.