Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
141 changes: 35 additions & 106 deletions AdyenComponents/BACS Direct Debit/BACSDirectDebitComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ import Adyen
#endif
import UIKit

internal protocol BACSDirectDebitRouterProtocol: AnyObject {
func presentConfirmation(with data: BACSDirectDebitData)
func confirmPayment(with data: BACSDirectDebitData)
}

/// A component that provides a form for BACS Direct Debit payments.
@MainActor
package final class BACSDirectDebitComponent: PaymentComponent, PresentableComponent {
Expand All @@ -27,7 +22,10 @@ package final class BACSDirectDebitComponent: PaymentComponent, PresentableCompo

// MARK: - PresentableComponent

package let viewController: UIViewController
package lazy var viewController: UIViewController = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this approach limit our testing capabilities? Passing a mock view controller could help us cover the lifecycle events if needed.

let bacsViewController = createViewController()
return SecuredViewController(child: bacsViewController, style: configuration.style)
}()

/// The object that acts as the delegate of the component.
package weak var delegate: PaymentComponentDelegate?
Expand All @@ -40,23 +38,21 @@ package final class BACSDirectDebitComponent: PaymentComponent, PresentableCompo
/// The context object for this component.
package let context: AdyenContext

/// The object that acts as the presentation delegate of the component.
package weak var presentationDelegate: PresentationDelegate?

/// Component's configuration
package var configuration: Configuration

// MARK: - PaymentComponent

package func performSubmit() {
bacsViewModel?.performSubmit()
}
Comment thread
nauaros marked this conversation as resolved.

// MARK: - Properties

internal let bacsPaymentMethod: BACSDirectDebitPaymentMethod

internal var confirmationPresenter: BACSConfirmationPresenterProtocol?
private var confirmationViewPresented = false

internal let inputFormViewController: BACSInputFormViewController

internal private(set) var inputPresenter: BACSInputPresenterProtocol?


internal private(set) var bacsViewModel: BACSViewModel?

// MARK: - Initializers

/// Creates and returns a BACS Direct Debit component.
Expand All @@ -72,16 +68,11 @@ package final class BACSDirectDebitComponent: PaymentComponent, PresentableCompo
self.bacsPaymentMethod = paymentMethod
self.context = context
self.configuration = configuration
self.inputFormViewController = BACSInputFormViewController(
title: paymentMethod.name,
scrollEnabled: configuration.showsSubmitButton,
styleProvider: configuration.style
)
self.viewController = SecuredViewController(
child: inputFormViewController,
style: configuration.style
)

}

// MARK: - Private

private func createViewController() -> UIViewController {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we think of extracting assembly code from the payment business logic to another assembly/provider/factory?

let tracker = BACSDirectDebitComponentTracker(
paymentMethod: bacsPaymentMethod,
context: context,
Expand All @@ -92,74 +83,27 @@ package final class BACSDirectDebitComponent: PaymentComponent, PresentableCompo
localizationParameters: configuration.localizationParameters,
scope: String(describing: self)
)
self.inputPresenter = BACSInputPresenter(
view: inputFormViewController,
router: self,
tracker: tracker,
itemsFactory: itemsFactory
)
inputPresenter?.amount = context.amount
inputFormViewController.presenter = inputPresenter

}

package func performSubmit() {
// TODO: - COSDK-1284: The confirmation screen will be removed.
}
}

// MARK: - BACSDirectDebitRouterProtocol

/// :nodoc:
extension BACSDirectDebitComponent: BACSDirectDebitRouterProtocol {

internal func presentConfirmation(with data: BACSDirectDebitData) {
confirmationViewPresented = true
let confirmationView = assembleConfirmationView(with: data)

let wrappedComponent = PresentableComponentWrapper(
component: self,
viewController: confirmationView
)
presentationDelegate?.present(component: wrappedComponent)
}

internal func confirmPayment(with data: BACSDirectDebitData) {
guard let bacsDirectDebitPaymentMethod = paymentMethod as? BACSDirectDebitPaymentMethod else {
return
}
let details = BACSDirectDebitDetails(
paymentMethod: bacsDirectDebitPaymentMethod,
holderName: data.holderName,
bankAccountNumber: data.bankAccountNumber,
bankLocationId: data.bankLocationId
let viewModel = BACSViewModel(
paymentMethod: bacsPaymentMethod,
amount: context.amount,
configuration: configuration,
tracker: tracker,
itemsFactory: itemsFactory,
onSubmit: { [weak self] details in
let data = PaymentComponentData(
paymentMethodDetails: details,
order: self?.order
)
self?.submit(data: data)
}
)
confirmationPresenter?.startLoading()
submit(data: PaymentComponentData(paymentMethodDetails: details, order: order))
}
self.bacsViewModel = viewModel

// MARK: - Private

private func assembleConfirmationView(with data: BACSDirectDebitData) -> UIViewController {
let confirmationViewController = BACSConfirmationViewController(
return BACSViewController(
title: paymentMethod.name,
scrollEnabled: configuration.showsSubmitButton,
styleProvider: configuration.style,
localizationParameters: configuration.localizationParameters
viewModel: viewModel
)
let itemsFactory = BACSItemsFactory(
styleProvider: configuration.style,
localizationParameters: configuration.localizationParameters,
scope: String(describing: self)
)
confirmationPresenter = BACSConfirmationPresenter(
data: data,
view: confirmationViewController,
router: self,
itemsFactory: itemsFactory
)
confirmationViewController.presenter = confirmationPresenter
return SecuredViewController(child: confirmationViewController, style: configuration.style)
}
}

Expand All @@ -170,21 +114,6 @@ extension BACSDirectDebitComponent: LoadingComponent {

/// Stops any processing animation that the component is running.
package func stopLoading() {
confirmationPresenter?.stopLoading()
}
}

// MARK: - Cancellable

/// :nodoc:
extension BACSDirectDebitComponent: Cancellable {

/// Called when the user cancels the component.
package func didCancel() {
if confirmationViewPresented == false {
inputPresenter?.resetForm()
} else {
confirmationViewPresented = false
}
bacsViewModel?.stopLoading()
}
}
20 changes: 16 additions & 4 deletions AdyenComponents/BACS Direct Debit/BACSDirectDebitDetails.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import Adyen

/// Contains the details supplied by the BACS Direct Debit component.
public struct BACSDirectDebitDetails: PaymentMethodDetails {
public struct BACSDirectDebitDetails: PaymentMethodDetails, ShopperInformation {

@_spi(AdyenInternal)
public var checkoutAttemptId: String?

/// The payment method type.
public let type: PaymentMethodType

Expand All @@ -23,7 +23,10 @@ public struct BACSDirectDebitDetails: PaymentMethodDetails {

/// The BACS location's ID.
public let bankLocationId: String


/// The shopper's email address.
public let shopperEmail: String?

/// An encoded string containing important SDK-specific data.
/// It is recommended to pass this field to your server to ensure maximum performance and reliability.
public var sdkData: String?
Expand All @@ -34,16 +37,25 @@ public struct BACSDirectDebitDetails: PaymentMethodDetails {
/// - holderName: The BACS account's holder name.
/// - bankAccountNumber: The BACS account's number.
/// - bankLocationId: The BACS location's ID.
/// - shopperEmail: The shopper's email address.
public init(
paymentMethod: BACSDirectDebitPaymentMethod,
holderName: String,
bankAccountNumber: String,
bankLocationId: String
bankLocationId: String,
shopperEmail: String? = nil
) {
self.type = paymentMethod.type
self.holderName = holderName
self.bankAccountNumber = bankAccountNumber
self.bankLocationId = bankLocationId
self.shopperEmail = shopperEmail
}

// MARK: - ShopperInformation

package var emailAddress: String? {
shopperEmail
}

// MARK: - Private
Expand Down
53 changes: 53 additions & 0 deletions AdyenComponents/BACS Direct Debit/BACSViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// Copyright (c) 2021 Adyen N.V.
//
// This file is open source and available under the MIT license. See the LICENSE file for more info.
//

import Adyen
#if canImport(AdyenUI)
import AdyenUI
@_spi(AdyenInternal) import class AdyenUI.FormViewController
#endif
import Combine
import UIKit

internal final class BACSViewController: FormViewController {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed #if canImport(AdyenUI) bracket in the imports section, but it's missing from here where we actually use the FormViewController itself.


// MARK: - Properties

private let viewModel: BACSViewModel
private var cancellables = Set<AnyCancellable>()

// MARK: - Initializers

internal init(
title: String,
viewModel: BACSViewModel
) {
self.viewModel = viewModel
super.init(
scrollEnabled: viewModel.configuration.showsSubmitButton,
style: viewModel.configuration.style,
localizationParameters: viewModel.configuration.localizationParameters
)
self.title = title
}

// MARK: - View life cycle

override internal func viewDidLoad() {
super.viewDidLoad()
viewModel.viewDidLoad()

@robertdalmeida robertdalmeida Jun 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought;
i think if we need to inform the viewModel that controller finished/"did" load, it should be at the end of this method after doing everything? (to avoid any timing issues).

bindValidation()
viewModel.items.forEach { append($0) }
}

// MARK: - Private

private func bindValidation() {
viewModel.$shouldShowValidation.sink { [weak self] shouldShowValidation in
if shouldShowValidation { self?.showValidation() }
}.store(in: &cancellables)
}
}
Loading
Loading