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

COIOS-774: Pay by Bank #1878

Draft
wants to merge 14 commits into
base: develop
Choose a base branch
from
50 changes: 50 additions & 0 deletions Adyen.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions Adyen/Assets/Generated/LocalizationKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,14 @@ public struct LocalizationKey {
public static let paybybankTitle = LocalizationKey(key: "adyen.paybybank.title")
/// Search…
public static let searchPlaceholder = LocalizationKey(key: "adyen.search.placeholder")
/// Use Pay by Bank to pay instantly from any bank account.
public static let payByBankAISDDDisclaimerHeader = LocalizationKey(key: "adyen.payByBankAISDD.disclaimer.header")
/// By connecting your bank account you are authorizing debits to your account for any amount owed for use of our services and/or purchase of our products, until this authorization is revoked.
public static let payByBankAISDDDisclaimerBody = LocalizationKey(key: "adyen.payByBankAISDD.disclaimer.body")
/// Continue to Pay by Bank
public static let payByBankAISDDSubmit = LocalizationKey(key: "adyen.payByBankAISDD.submit")
/// + more
public static let payByBankAISDDMore = LocalizationKey(key: "adyen.payByBankAISDD.more")
/// How would you like to use UPI?
public static let upiModeSelection = LocalizationKey(key: "adyen.upi.modeSelection")
/// Enter a correct virtual payment address
Expand Down
3 changes: 3 additions & 0 deletions Adyen/Core/Core Protocols/PaymentComponentBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ public protocol PaymentComponentBuilder: AdyenContextAware {

/// Builds a certain `PaymentComponent` based on a `StoredTwintPaymentMethod`.
func build(paymentMethod: StoredTwintPaymentMethod) -> PaymentComponent?

/// Builds a certain `PaymentComponent` based on a `PayByBankUSPaymentMethod`.
func build(paymentMethod: PayByBankUSPaymentMethod) -> PaymentComponent?

/// Builds a certain `PaymentComponent` based on any `PaymentMethod`, as a default case.
func build(paymentMethod: PaymentMethod) -> PaymentComponent?
Expand Down
6 changes: 5 additions & 1 deletion Adyen/Core/Payment Methods/Abstract/AnyPaymentMethod.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal enum AnyPaymentMethod: Codable {
case storedPayPal(StoredPayPalPaymentMethod)
case storedBCMC(StoredBCMCPaymentMethod)
case storedBlik(StoredBLIKPaymentMethod)
case storedPayByBankUS(StoredPayByBankUSPaymentMethod)
case storedAchDirectDebit(StoredACHDirectDebitPaymentMethod)
case storedCashAppPay(StoredCashAppPayPaymentMethod)
case storedTwint(StoredTwintPaymentMethod)
Expand Down Expand Up @@ -41,6 +42,7 @@ internal enum AnyPaymentMethod: Codable {
case upi(UPIPaymentMethod)
case cashAppPay(CashAppPayPaymentMethod)
case twint(TwintPaymentMethod)
case payByBankUS(PayByBankUSPaymentMethod)

case none

Expand Down Expand Up @@ -79,12 +81,14 @@ internal enum AnyPaymentMethod: Codable {
case let .upi(paymentMethod): return paymentMethod
case let .cashAppPay(paymentMethod): return paymentMethod
case let .twint(paymentMethod): return paymentMethod
case let .storedPayByBankUS(paymentMethod): return paymentMethod
case let .payByBankUS(paymentMethod): return paymentMethod
case .none: return nil
}
}

// MARK: - Decoding

internal init(from decoder: Decoder) throws {
self = AnyPaymentMethodDecoder.decode(from: decoder)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ internal enum AnyPaymentMethodDecoder {
.onlineBankingSK: OnlineBankingPaymentMethodDecoder(),
.upi: UPIPaymentMethodDecoder(),
.cashAppPay: CashAppPayPaymentMethodDecoder(),
.twint: TwintPaymentMethodDecoder()
.twint: TwintPaymentMethodDecoder(),
.payByBankAISDD: PayByBankUSPaymentMethodDecoder()
]

private static var defaultDecoder: PaymentMethodDecoder = InstantPaymentMethodDecoder()
Expand Down Expand Up @@ -341,6 +342,26 @@ private struct BLIKPaymentMethodDecoder: PaymentMethodDecoder {
}
}

private struct PayByBankUSPaymentMethodDecoder: PaymentMethodDecoder {
func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod {
if isStored {
return try .storedPayByBankUS(.init(from: decoder))
} else {
return try .payByBankUS(.init(from: decoder))
}
}

func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? {
if let method = paymentMethod as? StoredPayByBankUSPaymentMethod {
return .storedPayByBankUS(method)
}
if let method = paymentMethod as? PayByBankUSPaymentMethod {
return .payByBankUS(method)
}
return nil
}
}

private struct DokuPaymentMethodDecoder: PaymentMethodDecoder {
func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod {
try .doku(DokuPaymentMethod(from: decoder))
Expand Down
4 changes: 4 additions & 0 deletions Adyen/Core/Payment Methods/Abstract/PaymentMethodType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public enum PaymentMethodType: RawRepresentable, Hashable, Codable {
case upi
case cashAppPay
case twint
case payByBankAISDD
case other(String)

// Unsupported
Expand Down Expand Up @@ -129,6 +130,7 @@ public enum PaymentMethodType: RawRepresentable, Hashable, Codable {
case "cashapp": self = .cashAppPay
case "bizum": self = .bizum
case "twint": self = .twint
case "paybybank_AIS_DD": self = .payByBankAISDD
default: self = .other(rawValue)
}
}
Expand Down Expand Up @@ -191,6 +193,7 @@ public enum PaymentMethodType: RawRepresentable, Hashable, Codable {
case .cashAppPay: return "cashapp"
case .bizum: return "bizum"
case .twint: return "twint"
case .payByBankAISDD: return "paybybank_AIS_DD"
case let .other(value): return value
}
}
Expand Down Expand Up @@ -260,6 +263,7 @@ extension PaymentMethodType {
case .cashAppPay: return "cash app"
case .bizum: return "bizum"
case .twint: return "twint"
case .payByBankAISDD: return "Pay By Bank Direct Debit"
case let .other(name): return name.replacingOccurrences(of: "_", with: " ")
}
}
Expand Down
44 changes: 44 additions & 0 deletions Adyen/Core/Payment Methods/PayByBankUSPaymentMethod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Copyright (c) 2024 Adyen N.V.
//
// This file is open source and available under the MIT license. See the LICENSE file for more info.
//

import Foundation

/// PayByBank US payment method.
public struct PayByBankUSPaymentMethod: PaymentMethod {
public let type: PaymentMethodType

public var name: String

public var merchantProvidedDisplayInformation: MerchantCustomDisplayInformation?

@_spi(AdyenInternal)
public static var logoNames: [String] {
["US-1", "US-2", "US-3", "US-4"]
}

public func defaultDisplayInformation(using parameters: LocalizationParameters?) -> DisplayInformation {
.init(
title: name,
subtitle: nil,
logoName: type.rawValue,
trailingInfo: .logos(
named: Self.logoNames,
trailingText: "+"
),
accessibilityLabel: name
)
}

@_spi(AdyenInternal)
public func buildComponent(using builder: PaymentComponentBuilder) -> PaymentComponent? {
builder.build(paymentMethod: self)
}

private enum CodingKeys: String, CodingKey {
case type
case name
}
}
55 changes: 55 additions & 0 deletions Adyen/Core/Payment Methods/StoredPayByBankUSPaymentMethod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// Copyright (c) 2024 Adyen N.V.
//
// This file is open source and available under the MIT license. See the LICENSE file for more info.
//

import Foundation

/// Stored PayByBank US payment method.
public struct StoredPayByBankUSPaymentMethod: StoredPaymentMethod {

public let type: PaymentMethodType

public let name: String

public let label: String?

public var merchantProvidedDisplayInformation: MerchantCustomDisplayInformation?

public let identifier: String

public let supportedShopperInteractions: [ShopperInteraction]

@_spi(AdyenInternal)
public func buildComponent(using builder: PaymentComponentBuilder) -> PaymentComponent? {
builder.build(paymentMethod: self)
}

@_spi(AdyenInternal)
public func defaultDisplayInformation(using parameters: LocalizationParameters?) -> DisplayInformation {
let title: String
let subtitle: String?

if let label {
title = label
subtitle = name
} else {
title = name
subtitle = nil
}

return DisplayInformation(title: title, subtitle: subtitle, logoName: type.rawValue)
}

// MARK: - Decoding

private enum CodingKeys: String, CodingKey {
case type
case name
case label
case identifier = "id"
case supportedShopperInteractions
}

}
3 changes: 2 additions & 1 deletion Adyen/Helpers/UIImageViewHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public extension AdyenScope where Base: UIImageView {
/// Applies given `ImageStyle` to the UIImageView
/// Sets `translatesAutoresizingMaskIntoConstraints` to `false`
/// - Parameter style: `ImageStyle` to be applied
internal func apply(_ style: ImageStyle) {
@_spi(AdyenInternal)
func apply(_ style: ImageStyle) {
round(using: style.cornerRounding)
base.layer.borderColor = style.borderColor?.cgColor
base.layer.borderWidth = style.borderWidth
Expand Down
3 changes: 2 additions & 1 deletion Adyen/Helpers/UILabelHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public extension AdyenScope where Base: UILabel {
/// Applies given `TextStyle` to the UILabel
/// Sets `adjustsFontForContentSizeCategory` to `true`
/// - Parameter style: `TextStyle` to be applied
internal func apply(_ style: TextStyle) {
@_spi(AdyenInternal)
func apply(_ style: TextStyle) {
base.font = style.font
base.textColor = style.color
base.textAlignment = style.textAlignment
Expand Down
11 changes: 7 additions & 4 deletions Adyen/UI/Views/SupportedPaymentMethodsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
import AdyenNetworking
import UIKit

internal class SupportedPaymentMethodLogosView: UIView {
@_spi(AdyenInternal)
public class SupportedPaymentMethodLogosView: UIView {

struct Style: ViewStyle {
public struct Style: ViewStyle {
public var backgroundColor: UIColor = .clear

public var images: ImageStyle = .init(
Expand All @@ -24,6 +25,8 @@ internal class SupportedPaymentMethodLogosView: UIView {
font: .preferredFont(forTextStyle: .callout),
color: UIColor.Adyen.componentSecondaryLabel
)

public init() {}
}

internal let imageSize: CGSize
Expand All @@ -44,7 +47,7 @@ internal class SupportedPaymentMethodLogosView: UIView {

@AdyenDependency(\.imageLoader) private var imageLoader

internal init(
public init(
imageSize: CGSize = .init(width: 24, height: 16),
imageUrls: [URL],
trailingText: String?,
Expand All @@ -60,7 +63,7 @@ internal class SupportedPaymentMethodLogosView: UIView {
self.setContentHuggingPriority(.required, for: .horizontal)
}

override internal func willMove(toSuperview newSuperview: UIView?) {
override public func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)

if newSuperview != nil {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Copyright (c) 2024 Adyen N.V.
//
// This file is open source and available under the MIT license. See the LICENSE file for more info.
//

@_spi(AdyenInternal) import Adyen

extension PayByBankUSComponent {

public struct Configuration: AnyBasicComponentConfiguration {

/// The UI style of the component.
public var style: PayByBankUSComponent.Style

public var localizationParameters: LocalizationParameters?

/// Initializes a new instance of `PayByBankUSComponent.Configuration`
///
/// - Parameters:
/// - style: The form style.
/// - localizationParameters: The localization parameters.
public init(
style: PayByBankUSComponent.Style = .init(),
localizationParameters: LocalizationParameters? = nil
) {
self.style = style
self.localizationParameters = localizationParameters
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// Copyright (c) 2024 Adyen N.V.
//
// This file is open source and available under the MIT license. See the LICENSE file for more info.
//

import Foundation
import UIKit
@_spi(AdyenInternal) import Adyen

extension PayByBankUSComponent.ConfirmationViewController {

internal class Model {

internal let headerImageUrl: URL
internal let supportedBankLogoURLs: [URL]
internal let supportedBanksMoreText: String
internal let title: String
internal let subtitle: String
internal let message: String
internal let submitTitle: String

internal let style: PayByBankUSComponent.Style
internal let headerImageViewSize = CGSize(width: 80, height: 52)

internal let continueHandler: () -> Void

private let imageLoader: ImageLoading = ImageLoaderProvider.imageLoader()
private var imageLoadingTask: AdyenCancellable? {
willSet { imageLoadingTask?.cancel() }
}

internal init(
title: String,
headerImageUrl: URL,
supportedBankLogoNames: [String],
style: PayByBankUSComponent.Style,
localizationParameters: LocalizationParameters?,
logoUrlProvider: LogoURLProvider,
continueHandler: @escaping () -> Void
) {
self.headerImageUrl = headerImageUrl
self.supportedBankLogoURLs = supportedBankLogoNames.map { logoUrlProvider.logoURL(withName: $0) }
self.supportedBanksMoreText = localizedString(.payByBankAISDDMore, localizationParameters)
self.title = title
self.subtitle = localizedString(.payByBankAISDDDisclaimerHeader, localizationParameters)
self.message = localizedString(.payByBankAISDDDisclaimerBody, localizationParameters)
self.submitTitle = localizedString(.payByBankAISDDSubmit, localizationParameters)
self.style = style
self.continueHandler = continueHandler
}

internal func loadHeaderImage(for imageView: UIImageView) {
imageLoadingTask = imageView.load(url: headerImageUrl, using: imageLoader)
}
}
}
Loading
Loading