Skip to content

Commit

Permalink
Onboarding init props
Browse files Browse the repository at this point in the history
  • Loading branch information
mludowise-stripe committed Sep 28, 2024
1 parent 524b891 commit a140553
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 24 deletions.
8 changes: 8 additions & 0 deletions StripeConnect/StripeConnect.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@
41D17A6C2C5A7429007C6EE6 /* StripeiOS-Release.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 41D17A612C5A7429007C6EE6 /* StripeiOS-Release.xcconfig */; };
41D17A6D2C5A7429007C6EE6 /* StripeiOS-Shared.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 41D17A622C5A7429007C6EE6 /* StripeiOS-Shared.xcconfig */; };
41D17A6E2C5A7429007C6EE6 /* Version.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 41D17A632C5A7429007C6EE6 /* Version.xcconfig */; };
E6165CBF2CA7BF2200B76DA5 /* FetchInitComponentPropsMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6165CBE2CA7BF2200B76DA5 /* FetchInitComponentPropsMessageHandler.swift */; };
E65691222CA52D5900E0DB00 /* StripeConnect+Exports.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65691212CA52D5900E0DB00 /* StripeConnect+Exports.swift */; };
E6165CC12CA7D09900B76DA5 /* FetchInitComponentPropsMessageHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6165CC02CA7D09900B76DA5 /* FetchInitComponentPropsMessageHandlerTests.swift */; };
E6F485F82C9E35A5000D914F /* PaymentDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6F485F72C9E35A5000D914F /* PaymentDetailsViewController.swift */; };
E6F485FC2C9E360A000D914F /* ConnectJSURLParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6F485FB2C9E360A000D914F /* ConnectJSURLParams.swift */; };
E6F485FE2C9E36B2000D914F /* PaymentDetailsViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6F485FD2C9E36B2000D914F /* PaymentDetailsViewControllerTests.swift */; };
Expand Down Expand Up @@ -187,6 +189,8 @@
41D17A622C5A7429007C6EE6 /* StripeiOS-Shared.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "StripeiOS-Shared.xcconfig"; sourceTree = "<group>"; };
41D17A632C5A7429007C6EE6 /* Version.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Version.xcconfig; sourceTree = "<group>"; };
E65691212CA52D5900E0DB00 /* StripeConnect+Exports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StripeConnect+Exports.swift"; sourceTree = "<group>"; };
E6165CBE2CA7BF2200B76DA5 /* FetchInitComponentPropsMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchInitComponentPropsMessageHandler.swift; sourceTree = "<group>"; };
E6165CC02CA7D09900B76DA5 /* FetchInitComponentPropsMessageHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchInitComponentPropsMessageHandlerTests.swift; sourceTree = "<group>"; };
E6F485F72C9E35A5000D914F /* PaymentDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentDetailsViewController.swift; sourceTree = "<group>"; };
E6F485FB2C9E360A000D914F /* ConnectJSURLParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectJSURLParams.swift; sourceTree = "<group>"; };
E6F485FD2C9E36B2000D914F /* PaymentDetailsViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentDetailsViewControllerTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -233,6 +237,7 @@
413987B82C63F34B001D375E /* DebugMessageHandler.swift */,
413987B92C63F34B001D375E /* FetchClientSecretMessageHandler.swift */,
413987DC2C640A29001D375E /* FetchInitParamsMessageHandler.swift */,
E6165CBE2CA7BF2200B76DA5 /* FetchInitComponentPropsMessageHandler.swift */,
4186664B2C66AC8C003DB62E /* OnLoaderStartMessageHandler.swift */,
4186664D2C66ACB3003DB62E /* OnLoadErrorMessageHandler.swift */,
413987E02C641688001D375E /* PageDidLoadMessageHandler.swift */,
Expand Down Expand Up @@ -373,6 +378,7 @@
41814EEA2C6BCAB30014EB5E /* DebugMessageHandlerTests.swift */,
41814EEC2C6BED8C0014EB5E /* FetchClientSecretMessageHandlerTests.swift */,
41814EEE2C6BEF2C0014EB5E /* FetchInitParamsMessageHandlerTests.swift */,
E6165CC02CA7D09900B76DA5 /* FetchInitComponentPropsMessageHandlerTests.swift */,
41814EF22C6BFA4B0014EB5E /* OnLoaderStartMessageHandlerTests.swift */,
410D0FC92C6CFE27009B0E26 /* OnLoadErrorMessageHandlerTests.swift */,
410D0FCF2C6D0319009B0E26 /* PageDidLoadMessageHandlerTests.swift */,
Expand Down Expand Up @@ -635,6 +641,7 @@
41542A692C88B6F2004E728E /* JSONEncoder+extension.swift in Sources */,
416E9E842C76AE0A00A0B917 /* ComponentType.swift in Sources */,
410D0FCE2C6D000B009B0E26 /* OpenAuthenticatedWebViewMessageHandler.swift in Sources */,
E6165CBF2CA7BF2200B76DA5 /* FetchInitComponentPropsMessageHandler.swift in Sources */,
413D18482C7FBAA70051AA42 /* CSSHelpers.swift in Sources */,
413987CD2C63F34B001D375E /* MessageSender.swift in Sources */,
41BCCFF52C8B5A0A00797E01 /* CustomFontSourceWrapper.swift in Sources */,
Expand Down Expand Up @@ -693,6 +700,7 @@
410D0FE72C6D3BBC009B0E26 /* ConnectWebViewTests.swift in Sources */,
410D0FD02C6D0319009B0E26 /* PageDidLoadMessageHandlerTests.swift in Sources */,
41814EED2C6BED8C0014EB5E /* FetchClientSecretMessageHandlerTests.swift in Sources */,
E6165CC12CA7D09900B76DA5 /* FetchInitComponentPropsMessageHandlerTests.swift in Sources */,
41814EE82C6BC8800014EB5E /* ScriptWebTestBase.swift in Sources */,
41BCCFF02C8B3C8900797E01 /* AppearanceWrapper+Default.swift in Sources */,
416E9E822C76994C00A0B917 /* WebView+Tests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,37 @@ import UIKit
@_spi(PrivateBetaConnect)
public class AccountOnboardingViewController: UIViewController {

/// Delegate that recieves callbacks for this component
struct Props: Encodable {
let fullTermsOfServiceUrl: URL?
let recipientTermsOfServiceUrl: URL?
let privacyPolicyUrl: URL?
let skipTermsOfServiceCollection: Bool?
let collectionOptions: AccountCollectionOptions

// Used in FetchInitComponentPropsMessageHandler
// Each property key should match its JS setter name
enum CodingKeys: String, CodingKey {
case fullTermsOfServiceUrl = "setFullTermsOfServiceUrl"
case recipientTermsOfServiceUrl = "setRecipientTermsOfServiceUrl"
case privacyPolicyUrl = "setPrivacyPolicyUrl"
case skipTermsOfServiceCollection = "setSkipTermsOfServiceCollection"
case collectionOptions = "setCollectionOptions"
}
}

/// Delegate that receives callbacks for this component
public weak var delegate: AccountOnboardingViewControllerDelegate?

let webView: ConnectComponentWebView

init(fullTermsOfServiceUrl: URL? = nil,
recipientTermsOfServiceUrl: URL? = nil,
privacyPolicyUrl: URL? = nil,
skipTermsOfServiceCollection: Bool? = nil,
collectionOptions: AccountCollectionOptions = .init(),
init(props: Props,
componentManager: EmbeddedComponentManager,
// Test Only
loadContent: Bool = true
) {
webView = ConnectComponentWebView(
componentManager: componentManager,
componentType: .onboarding,
componentType: .onboarding(props),
loadContent: loadContent
)
super.init(nibName: nil, bundle: nil)
Expand Down
27 changes: 24 additions & 3 deletions StripeConnect/StripeConnect/Source/Components/ComponentType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,32 @@
import Foundation

/// The name of the embedded component tag in JS ([docs](https://docs.stripe.com/connect/supported-embedded-components))
enum ComponentType: String, Encodable {
enum ComponentType: Encodable {
/// Displays the balance summary, the payout schedule, and a list of payouts for the connected account
case payouts
/// The onboarding flow for the account.
case onboarding = "account-onboarding"
case onboarding(AccountOnboardingViewController.Props)
/// Show details of a given payment and allow users to manage disputes and perform refunds.
case paymentDetails = "payment-details"
case paymentDetails

var name: String {
switch self {
case .payouts:
return "payouts"
case .onboarding:
return "account-onboarding"
case .paymentDetails:
return "payment-details"
}
}

func encode(to encoder: any Encoder) throws {
switch self {
case .onboarding(let initParams):
try initParams.encode(to: encoder)
case .payouts,
.paymentDetails:
try VoidPayload().encode(to: encoder)
}
}
}
17 changes: 10 additions & 7 deletions StripeConnect/StripeConnect/Source/EmbeddedComponentManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,16 @@ public class EmbeddedComponentManager {
skipTermsOfServiceCollection: Bool? = nil,
collectionOptions: AccountCollectionOptions = .init()
) -> AccountOnboardingViewController {
return .init(fullTermsOfServiceUrl: fullTermsOfServiceUrl,
recipientTermsOfServiceUrl: recipientTermsOfServiceUrl,
privacyPolicyUrl: privacyPolicyUrl,
skipTermsOfServiceCollection: skipTermsOfServiceCollection,
collectionOptions: collectionOptions,
componentManager: self,
loadContent: shouldLoadContent)
return .init(
props: .init(
fullTermsOfServiceUrl: fullTermsOfServiceUrl,
recipientTermsOfServiceUrl: recipientTermsOfServiceUrl,
privacyPolicyUrl: privacyPolicyUrl,
skipTermsOfServiceCollection: skipTermsOfServiceCollection,
collectionOptions: collectionOptions
),
componentManager: self,
loadContent: shouldLoadContent)
}

@_spi(DashboardOnly)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Foundation
/// Structured parameters for URL params accepted by the iOS ConnectJS wrapper
struct ConnectJSURLParams: Encodable {
/// The component type
let component: ComponentType
let component: String

/// The platform publishable key. Required for non-dashboard accounts
private(set) var publicKey: String?
Expand All @@ -37,7 +37,7 @@ struct ConnectJSURLParams: Encodable {

extension ConnectJSURLParams {
init(component: ComponentType, apiClient: STPAPIClient) {
self.component = component
self.component = component.name

// Validate that publishable key has been set
STPAPIClient.validateKey(apiClient.publishableKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,18 @@ private extension ConnectComponentWebView {
appearance: .init(appearance: componentManager.appearance, traitCollection: self.traitCollection),
fonts: componentManager.fonts.map({ .init(customFontSource: $0) }))
}))
addMessageHandler(FetchInitComponentPropsMessageHandler(componentType: componentType))
addMessageHandler(DebugMessageHandler())
addMessageHandler(FetchClientSecretMessageHandler { [weak self] _ in
await self?.componentManager.fetchClientSecret()
})
addMessageHandler(PageDidLoadMessageHandler { _ in })
addMessageHandler(AccountSessionClaimedMessageHandler{ _ in
addMessageHandler(PageDidLoadMessageHandler { payload in
// TODO: MXMOBILE-2491 Use this for analytics
debugPrint("pageDidLoad: \(payload.pageViewId)")
})
addMessageHandler(AccountSessionClaimedMessageHandler { payload in
// TODO: MXMOBILE-2491 Use this for analytics
debugPrint("accountSessionClaimed: \(payload.merchantId)")
})
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// FetchInitComponentPropsMessageHandler.swift
// StripeConnect
//
// Created by Mel Ludowise on 9/27/24.
//

import Foundation

// Fetches initial property values specific to this component.
@available(iOS 15, *)
class FetchInitComponentPropsMessageHandler: ScriptMessageHandlerWithReply<VoidPayload, ComponentType> {
init(componentType: ComponentType) {
super.init(name: "fetchInitComponentProps") { _ in
componentType
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ struct CallSetterWithSerializableValueSender<Value: Codable & Equatable>: Messag
let name: String = "callSetterWithSerializableValue"
let payload: Payload
}

extension CallSetterWithSerializableValueSender {
init(_ setter: String, with value: Value) {
self.init(payload: .init(setter: setter, value: value))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation

@_spi(PrivateBetaConnect)
/// Collection options for account onboarding
public struct AccountCollectionOptions: Codable {
public struct AccountCollectionOptions: Codable, Equatable {

public enum FieldOption: String, Codable {
case currentlyDue = "currently_due"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ class ConnectJSURLParamsTests: XCTestCase {
func testInitFromApiClient_publicKey() {
apiClient.publishableKey = "pk_1234"

let urlParams = ConnectJSURLParams(component: .onboarding, apiClient: apiClient)
let urlParams = ConnectJSURLParams(component: .payouts, apiClient: apiClient)
XCTAssertEqual(urlParams.component, "payouts")
XCTAssertEqual(urlParams.publicKey, "pk_1234")
XCTAssertNil(urlParams.apiKeyOverride)
XCTAssertNil(urlParams.merchantIdOverride)
Expand All @@ -32,7 +33,8 @@ class ConnectJSURLParamsTests: XCTestCase {
func testInitFromApiClient_userKeyIncludesOverrideParams() {
apiClient.publishableKey = "uk_1234"

let urlParams = ConnectJSURLParams(component: .onboarding, apiClient: apiClient)
let urlParams = ConnectJSURLParams(component: .payouts, apiClient: apiClient)
XCTAssertEqual(urlParams.component, "payouts")
XCTAssertNil(urlParams.publicKey)
XCTAssertEqual(urlParams.apiKeyOverride, "uk_1234")
XCTAssertEqual(urlParams.merchantIdOverride, "acct_123")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ class ConnectComponentWebViewTests: XCTestCase {
""")
}

@MainActor
func testFetchInitComponentPropsMessageHandler() async throws {
let componentManager = componentManagerAssertingOnFetch()

let webView = ConnectComponentWebView(componentManager: componentManager,
componentType: .payouts,
webLocale: Locale(identifier: "fr_FR"),
loadContent: false)

try await webView.evaluateMessageWithReply(name: "fetchInitComponentProps",
json: "{}",
expectedResponse: "{}")
}

@MainActor
func testUpdateTraitCollection() async throws {
var appearance = EmbeddedComponentManager.Appearance()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// FetchInitComponentPropsMessageHandlerTests.swift
// StripeConnectTests
//
// Created by Mel Ludowise on 9/27/24.
//

@_spi(PrivateBetaConnect) @testable import StripeConnect
import XCTest

class FetchInitComponentPropsMessageHandlerTests: ScriptWebTestBase {

@MainActor
func testMessageSend() async throws {
let componentType = ComponentType.onboarding(.init(
fullTermsOfServiceUrl: URL(string: "https://fullTermsOfServiceUrl.com")!,
recipientTermsOfServiceUrl: URL(string: "https://recipientTermsOfServiceUrl.com")!,
privacyPolicyUrl: URL(string: "https://privacyPolicyUrl.com")!,
skipTermsOfServiceCollection: true,
collectionOptions: {
var collectionOptions = AccountCollectionOptions()
collectionOptions.fields = .eventuallyDue
collectionOptions.futureRequirements = .include
return collectionOptions
}()
))
webView.addMessageReplyHandler(messageHandler: FetchInitComponentPropsMessageHandler(componentType: componentType))

try await webView.evaluateMessageWithReply(name: "fetchInitComponentProps",
json: "{}",
expectedResponse: """
{"setCollectionOptions":{"fields":"eventually_due","futureRequirements":"include"},"setFullTermsOfServiceUrl":"https:\\/\\/fullTermsOfServiceUrl.com","setPrivacyPolicyUrl":"https:\\/\\/privacyPolicyUrl.com","setRecipientTermsOfServiceUrl":"https:\\/\\/recipientTermsOfServiceUrl.com","setSkipTermsOfServiceCollection":true}
""")
}
}

0 comments on commit a140553

Please sign in to comment.