Skip to content
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

PayID tab UI elements #2012

Merged
merged 15 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
12 changes: 12 additions & 0 deletions Adyen.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
000633D92D5A307E004C3FBF /* FormAccountIdentifierPickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000633D82D5A307C004C3FBF /* FormAccountIdentifierPickerItem.swift */; };
000888742D4CF597009C03E1 /* PayToPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000888732D4CF58D009C03E1 /* PayToPaymentMethod.swift */; };
000888782D4CF5FC009C03E1 /* PayToComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000888772D4CF5F5009C03E1 /* PayToComponent.swift */; };
0008887C2D4CFA73009C03E1 /* PayToComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0008887A2D4CFA67009C03E1 /* PayToComponentTests.swift */; };
Expand Down Expand Up @@ -1500,6 +1501,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
000633D82D5A307C004C3FBF /* FormAccountIdentifierPickerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormAccountIdentifierPickerItem.swift; sourceTree = "<group>"; };
000888732D4CF58D009C03E1 /* PayToPaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayToPaymentMethod.swift; sourceTree = "<group>"; };
000888772D4CF5F5009C03E1 /* PayToComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayToComponent.swift; sourceTree = "<group>"; };
0008887A2D4CFA67009C03E1 /* PayToComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayToComponentTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2693,6 +2695,14 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
000633D72D5A306A004C3FBF /* Identifier Picker */ = {
isa = PBXGroup;
children = (
000633D82D5A307C004C3FBF /* FormAccountIdentifierPickerItem.swift */,
);
path = "Identifier Picker";
sourceTree = "<group>";
};
000888762D4CF5DB009C03E1 /* PayTo */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4196,6 +4206,7 @@
E7085B062628B29600D0153B /* Value Pickers */ = {
isa = PBXGroup;
children = (
000633D72D5A306A004C3FBF /* Identifier Picker */,
E7085B082628B29600D0153B /* Abstract */,
00EACBBB2876C7D10082B360 /* Issuer List Picker */,
);
Expand Down Expand Up @@ -6981,6 +6992,7 @@
E7085B132628B29600D0153B /* BaseFormPickerItemView.swift in Sources */,
81C400642A3B0C58007EC51C /* FormSearchButtonItem.swift in Sources */,
F9976BA526D654ED00D2D7CE /* Throttler.swift in Sources */,
000633D92D5A307E004C3FBF /* FormAccountIdentifierPickerItem.swift in Sources */,
A0414C302790429A00DF3FE9 /* PublicKeyConsumer.swift in Sources */,
813EF9E82A5FE03B00C65D15 /* ListItem+Icon.swift in Sources */,
E28098C4220DC66E0087928F /* ListItemView.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Copyright (c) 2025 Adyen N.V.
//
// This file is open source and available under the MIT license. See the LICENSE file for more info.
//

/// A wrapper struct to use as item in ``FormIdentifierPickerItem``
@_spi(AdyenInternal)
public struct FormIdentifierPickerElement: CustomStringConvertible, Equatable {

public let identifier: String
public let title: String
public var description: String {
title
}

public init(identifier: String, title: String) {
self.identifier = identifier
self.title = title
}
}

/// A identifier picker form item
@_spi(AdyenInternal)
public final class FormIdentifierPickerItem: BaseFormPickerItem<FormIdentifierPickerElement> {

public init(
preselectedIdentifier: FormIdentifierPickerElement,
selectableIdentifiers: [FormIdentifierPickerElement],
style: FormTextItemStyle
) {
super.init(
preselectedValue: .init(identifier: preselectedIdentifier.identifier, element: preselectedIdentifier),
selectableValues: selectableIdentifiers.map { $0.toBaseFormPickerElement() },
style: style
)
}

override public func build(with builder: FormItemViewBuilder) -> AnyFormItemView {
builder.build(with: self)
}
}

private extension FormIdentifierPickerElement {

func toBaseFormPickerElement() -> BasePickerElement<FormIdentifierPickerElement> {
.init(identifier: identifier, element: self)
}
}
112 changes: 108 additions & 4 deletions AdyenComponents/PayTo/PayToComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,50 @@ import UIKit

/// A component that provides PayTo flows for PayTo component.
public final class PayToComponent: PaymentComponent,
PresentableComponent {
PresentableComponent {

private enum ViewIdentifier {
static let flowSelectionTitleLabelItem = "flowSelectionTitleLabel"
static let flowSelectionItem = "flowSelectionSegmentedControl"
static let continueButtonItem = "continueButton"
static let identifierPickerItem = "identifierPicker"
static let firstNameInputItem = "firstNameTextfield"
static let lastNameInputItem = "lastNameTextfield"
}

private enum AccountIdentifiers: CustomStringConvertible, CaseIterable {
case phone
case email
case abn
case organizationID

// TODO: Add translation
public var description: String {
switch self {
case .phone:
return "Phone"
case .email:
return "Email"
case .abn:
return "ABN"
case .organizationID:
return "Organization ID"
}
}

public var identifier: String {
switch self {
case .phone:
return "phone"
case .email:
return "email"
case .abn:
return "abn"
case .organizationID:
return "organizationID"
}
}

}

/// Configuration for PayTo Component.
Expand Down Expand Up @@ -63,7 +101,7 @@ public final class PayToComponent: PaymentComponent,
internal lazy var flowSelectionTitleLabelItem: FormLabelItem = {
// TODO: Add translation
let item = FormLabelItem(
text: "How would you like to use Payto?",
text: localizedString(LocalizationKey(key: "How would you like to use Payto?"), configuration.localizationParameters),
style: configuration.style.footnoteLabel
)
item.style.textAlignment = .left
Expand All @@ -76,6 +114,7 @@ public final class PayToComponent: PaymentComponent,

/// The segment control item to choose the payTo flow.
internal lazy var flowSelectionItem: FormSegmentedControlItem = {
// TODO: Add translation
let item = FormSegmentedControlItem(
items: ["PayID", "BSB"],
style: configuration.style.segmentedControlStyle,
Expand All @@ -88,7 +127,7 @@ public final class PayToComponent: PaymentComponent,
}()

/// The continue button item.
internal lazy var continueButton: FormButtonItem = {
internal lazy var continueButtonItem: FormButtonItem = {
let item = FormButtonItem(style: configuration.style.mainButtonItem)
item.identifier = ViewIdentifierBuilder.build(
scopeInstance: self,
Expand All @@ -101,6 +140,52 @@ public final class PayToComponent: PaymentComponent,
return item
}()

/// The account holder firstname text input item.
internal lazy var firstNameInputItem: FormTextInputItem = {
let item = FormTextInputItem(style: configuration.style.textField)
// TODO: Add translation
item.title = localizedString(LocalizationKey(key: "Account holder first name"), configuration.localizationParameters)
item.placeholder = localizedString(LocalizationKey(key: "Account holder first name"), configuration.localizationParameters)
item.identifier = ViewIdentifierBuilder.build(
scopeInstance: self,
postfix: ViewIdentifier.firstNameInputItem
)
return item
}()

/// The account holder lastname text input item.
internal lazy var lastNameInputItem: FormTextInputItem = {
let item = FormTextInputItem(style: configuration.style.textField)
// TODO: Add translation
item.title = localizedString(LocalizationKey(key: "Account holder last name"), configuration.localizationParameters)
item.placeholder = localizedString(LocalizationKey(key: "Account holder last name"), configuration.localizationParameters)
item.identifier = ViewIdentifierBuilder.build(
scopeInstance: self,
postfix: ViewIdentifier.lastNameInputItem
)
return item
}()

/// The identifier picker item.
internal lazy var identifierPickerItem: FormIdentifierPickerItem = {
let selectableValues = AccountIdentifiers.allCases.map { accountIdentifier in
FormIdentifierPickerElement(identifier: accountIdentifier.identifier, title: accountIdentifier.description)
}

let item = FormIdentifierPickerItem(
preselectedIdentifier: selectableValues[0],
selectableIdentifiers: selectableValues,
style: configuration.style.textField
)
// TODO: Add translation
item.title = localizedString(LocalizationKey(key: "Identifier"), configuration.localizationParameters)
item.identifier = ViewIdentifierBuilder.build(
scopeInstance: self,
postfix: ViewIdentifier.identifierPickerItem
)
return item
}()

private lazy var formViewController: FormViewController = {
let formViewController = FormViewController(
scrollEnabled: configuration.showsSubmitButton,
Expand All @@ -109,17 +194,36 @@ public final class PayToComponent: PaymentComponent,
)
formViewController.title = paymentMethod.displayInformation(using: configuration.localizationParameters).title
formViewController.append(FormSpacerItem(numberOfSpaces: 1))

formViewController.append(flowSelectionTitleLabelItem.padding())
formViewController.append(FormSpacerItem(numberOfSpaces: 1))

formViewController.append(flowSelectionItem.padding())
formViewController.append(FormSpacerItem(numberOfSpaces: 1))

formViewController.append(identifierPickerItem.padding())
formViewController.append(FormSpacerItem(numberOfSpaces: 1))

contentView(formVC: formViewController)

if configuration.showsSubmitButton {
formViewController.append(FormSpacerItem(numberOfSpaces: 2))
formViewController.append(continueButton)
formViewController.append(continueButtonItem)
}

return formViewController
}()

// MARK: - Private

private func contentView(formVC: FormViewController) {
staticContent(formVC)
}

private func staticContent(_ formVC: FormViewController) {
formVC.append(firstNameInputItem)
formVC.append(lastNameInputItem)
}
}

// MARK: - Event Handling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,52 @@ class PayToComponentTests: XCTestCase {
XCTAssertNotNil(continueButton, "ContinueButton should exist")
}

func test_identifierPicker_exists() throws {
// Given
let sut = try PayToComponent(
paymentMethod: AdyenCoder.decode(payto),
context: Dummy.context
)

setupRootViewController(sut.viewController)

// Check by accessibility identifier
let identifierPickerItem: BaseFormPickerItemView<FormIdentifierPickerElement> = try XCTUnwrap(sut.viewController.view.findView(with: "AdyenComponents.PayToComponent.identifierPicker"))

// Then
XCTAssertNotNil(identifierPickerItem, "identifier picker should exist")
}

func test_firstname_textfield_exists() throws {
// Given
let sut = try PayToComponent(
paymentMethod: AdyenCoder.decode(payto),
context: Dummy.context
)

setupRootViewController(sut.viewController)

// Check by accessibility identifier
let firstNameInputItem: FormTextInputItemView = try XCTUnwrap(sut.viewController.view.findView(with: "AdyenComponents.PayToComponent.firstNameTextfield"))

// Then
XCTAssertNotNil(firstNameInputItem, "first name input field should exist")
}

func test_lastname_textfield_exists() throws {
// Given
let sut = try PayToComponent(
paymentMethod: AdyenCoder.decode(payto),
context: Dummy.context
)

setupRootViewController(sut.viewController)

// Check by accessibility identifier
let lastNameInputItem: FormTextInputItemView = try XCTUnwrap(sut.viewController.view.findView(with: "AdyenComponents.PayToComponent.lastNameTextfield"))

// Then
XCTAssertNotNil(lastNameInputItem, "last name input field should exist")
}

}
2 changes: 2 additions & 0 deletions spell-check-word-allow-list.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,5 @@ whiteList:
- SDKs
- ks
- payto
- abn
- firstname
Loading