diff --git a/Adyen.xcodeproj/project.pbxproj b/Adyen.xcodeproj/project.pbxproj index 91f4ffe7ec..8814c9442d 100644 --- a/Adyen.xcodeproj/project.pbxproj +++ b/Adyen.xcodeproj/project.pbxproj @@ -159,6 +159,7 @@ 81825CC22AC59C6400F91912 /* XCTestCase+RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81825CC02AC59C6400F91912 /* XCTestCase+RootViewController.swift */; }; 81825CC42AC59C6C00F91912 /* XCTestCase+Wait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81825CC32AC59C6C00F91912 /* XCTestCase+Wait.swift */; }; 81825CC52AC59C6C00F91912 /* XCTestCase+Wait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81825CC32AC59C6C00F91912 /* XCTestCase+Wait.swift */; }; + 81881BCE2B1A0A510020E3F2 /* AnyPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81881BCD2B1A0A510020E3F2 /* AnyPaymentMethod.swift */; }; 81896E852A4DB5F300C532CA /* SearchViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81896E842A4DB5F300C532CA /* SearchViewControllerTests.swift */; }; 8191838E2A53062F008EB61A /* FormAddressItem+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8191838D2A53062F008EB61A /* FormAddressItem+Configuration.swift */; }; 819CC3342B14C53200D2EEE9 /* PaymentMethods+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 819CC3332B14C53200D2EEE9 /* PaymentMethods+Equatable.swift */; }; @@ -1419,6 +1420,7 @@ 81825CBA2AC59C4000F91912 /* UIViewController+Search.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Search.swift"; sourceTree = ""; }; 81825CC02AC59C6400F91912 /* XCTestCase+RootViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTestCase+RootViewController.swift"; sourceTree = ""; }; 81825CC32AC59C6C00F91912 /* XCTestCase+Wait.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Wait.swift"; sourceTree = ""; }; + 81881BCD2B1A0A510020E3F2 /* AnyPaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyPaymentMethod.swift; sourceTree = ""; }; 81896E842A4DB5F300C532CA /* SearchViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewControllerTests.swift; sourceTree = ""; }; 8191838D2A53062F008EB61A /* FormAddressItem+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FormAddressItem+Configuration.swift"; sourceTree = ""; }; 819CC3332B14C53200D2EEE9 /* PaymentMethods+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaymentMethods+Equatable.swift"; sourceTree = ""; }; @@ -3797,6 +3799,7 @@ E746E67327B429DF0076BB71 /* Abstract */ = { isa = PBXGroup; children = ( + 81881BCD2B1A0A510020E3F2 /* AnyPaymentMethod.swift */, F9D5751723799BB0009C18B5 /* AnyPaymentMethodDecoder.swift */, F9D5753023828EBB009C18B5 /* AnyCardPaymentMethod.swift */, E9B36C9C223FDE4F00EAA368 /* PaymentMethods.swift */, @@ -6528,6 +6531,7 @@ E278EB4322AA5EC800497FD5 /* IssuerListPaymentMethod.swift in Sources */, F90FB7BC2446E8C8005BFE0E /* BrowserInfo.swift in Sources */, A0414C1F278C27CF00DF3FE9 /* ACHDirectDebitPaymentMethod.swift in Sources */, + 81881BCE2B1A0A510020E3F2 /* AnyPaymentMethod.swift in Sources */, E787C9D1246E948F00B401E0 /* CoreFonts.swift in Sources */, F9589D032601EE7800E4113F /* FormItemInjector.swift in Sources */, 5A15D5A1264BE1E500A8E3C7 /* PrefilledShopperInformation.swift in Sources */, diff --git a/Adyen/Core/Core Protocols/PaymentMethod.swift b/Adyen/Core/Core Protocols/PaymentMethod.swift index bb9f07eaf0..4824d8f545 100644 --- a/Adyen/Core/Core Protocols/PaymentMethod.swift +++ b/Adyen/Core/Core Protocols/PaymentMethod.swift @@ -7,7 +7,7 @@ import Foundation /// A payment method that is available to use. -public protocol PaymentMethod: Decodable { +public protocol PaymentMethod: Codable { /// A string identifying the type of payment method, such as `"card"`, `"ideal"`, `"applepay"`. var type: PaymentMethodType { get } diff --git a/Adyen/Core/Models/ShopperInteraction.swift b/Adyen/Core/Models/ShopperInteraction.swift index caa3a4f0d3..92c455a9c5 100644 --- a/Adyen/Core/Models/ShopperInteraction.swift +++ b/Adyen/Core/Models/ShopperInteraction.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2021 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -7,7 +7,7 @@ import Foundation /// A type of shopper interaction during a payment. -public enum ShopperInteraction: String, Decodable { +public enum ShopperInteraction: String, Codable { /// Indicates the shopper is present during the payment. case shopperPresent = "Ecommerce" diff --git a/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethod.swift b/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethod.swift new file mode 100644 index 0000000000..73add95e16 --- /dev/null +++ b/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethod.swift @@ -0,0 +1,112 @@ +// +// Copyright (c) 2023 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +internal enum AnyPaymentMethod: Codable { + case storedInstant(StoredInstantPaymentMethod) + case storedCard(StoredCardPaymentMethod) + case storedPayPal(StoredPayPalPaymentMethod) + case storedBCMC(StoredBCMCPaymentMethod) + case storedBlik(StoredBLIKPaymentMethod) + case storedAchDirectDebit(StoredACHDirectDebitPaymentMethod) + case storedCashAppPay(StoredCashAppPayPaymentMethod) + + case instant(PaymentMethod) + case card(AnyCardPaymentMethod) + case issuerList(IssuerListPaymentMethod) + case sepaDirectDebit(SEPADirectDebitPaymentMethod) + case bacsDirectDebit(BACSDirectDebitPaymentMethod) + case achDirectDebit(ACHDirectDebitPaymentMethod) + case applePay(ApplePayPaymentMethod) + case qiwiWallet(QiwiWalletPaymentMethod) + case weChatPay(WeChatPayPaymentMethod) + case mbWay(MBWayPaymentMethod) + case blik(BLIKPaymentMethod) + case giftcard(GiftCardPaymentMethod) + case mealVoucher(MealVoucherPaymentMethod) + case doku(DokuPaymentMethod) + case sevenEleven(SevenElevenPaymentMethod) + case econtextStores(EContextPaymentMethod) + case econtextATM(EContextPaymentMethod) + case econtextOnline(EContextPaymentMethod) + case boleto(BoletoPaymentMethod) + case affirm(AffirmPaymentMethod) + case atome(AtomePaymentMethod) + case onlineBanking(OnlineBankingPaymentMethod) + case upi(UPIPaymentMethod) + case cashAppPay(CashAppPayPaymentMethod) + + case none + + internal var value: PaymentMethod? { + switch self { + case let .storedCard(paymentMethod): return paymentMethod + case let .storedPayPal(paymentMethod): return paymentMethod + case let .storedBCMC(paymentMethod): return paymentMethod + case let .instant(paymentMethod): return paymentMethod + case let .storedInstant(paymentMethod): return paymentMethod + case let .storedAchDirectDebit(paymentMethod): return paymentMethod + case let .storedCashAppPay(paymentMethod): return paymentMethod + case let .card(paymentMethod): return paymentMethod + case let .issuerList(paymentMethod): return paymentMethod + case let .sepaDirectDebit(paymentMethod): return paymentMethod + case let .bacsDirectDebit(paymentMethod): return paymentMethod + case let .achDirectDebit(paymentMethod): return paymentMethod + case let .applePay(paymentMethod): return paymentMethod + case let .qiwiWallet(paymentMethod): return paymentMethod + case let .weChatPay(paymentMethod): return paymentMethod + case let .mbWay(paymentMethod): return paymentMethod + case let .blik(paymentMethod): return paymentMethod + case let .storedBlik(paymentMethod): return paymentMethod + case let .doku(paymentMethod): return paymentMethod + case let .giftcard(paymentMethod): return paymentMethod + case let .mealVoucher(paymentMethod): return paymentMethod + case let .sevenEleven(paymentMethod): return paymentMethod + case let .econtextStores(paymentMethod): return paymentMethod + case let .econtextATM(paymentMethod): return paymentMethod + case let .econtextOnline(paymentMethod): return paymentMethod + case let .boleto(paymentMethod): return paymentMethod + case let .affirm(paymentMethod): return paymentMethod + case let .atome(paymentMethod): return paymentMethod + case let .onlineBanking(paymentMethod): return paymentMethod + case let .upi(paymentMethod): return paymentMethod + case let .cashAppPay(paymentMethod): return paymentMethod + case .none: return nil + } + } + + // MARK: - Decoding + + internal init(from decoder: Decoder) throws { + self = AnyPaymentMethodDecoder.decode(from: decoder) + } + + internal func encode(to encoder: Encoder) throws { + try value?.encode(to: encoder) + } + + internal enum CodingKeys: String, CodingKey { + case type + case details + case brand + case issuers + } +} + +extension AnyPaymentMethod { + + init(_ paymentMethod: PaymentMethod) { + self = AnyPaymentMethodDecoder.anyPaymentMethod(from: paymentMethod) + } +} + +extension PaymentMethod { + + var toAnyPaymentMethod: AnyPaymentMethod { + .init(self) + } +} diff --git a/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethodDecoder.swift b/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethodDecoder.swift index d030477092..6a7301248e 100644 --- a/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethodDecoder.swift +++ b/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethodDecoder.swift @@ -121,10 +121,16 @@ internal enum AnyPaymentMethodDecoder { return .none } } + + internal static func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod { + let paymentDecoder = decoders[paymentMethod.type] ?? defaultDecoder + return paymentDecoder.anyPaymentMethod(from: paymentMethod) ?? .instant(paymentMethod) + } } private protocol PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? } private struct CardPaymentMethodDecoder: PaymentMethodDecoder { @@ -135,6 +141,16 @@ private struct CardPaymentMethodDecoder: PaymentMethodDecoder { return try .card(CardPaymentMethod(from: decoder)) } } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + if let method = paymentMethod as? StoredCardPaymentMethod { + return .storedCard(method) + } + if let method = paymentMethod as? CardPaymentMethod { + return .card(method) + } + return nil + } } private struct BCMCCardPaymentMethodDecoder: PaymentMethodDecoder { @@ -145,24 +161,46 @@ private struct BCMCCardPaymentMethodDecoder: PaymentMethodDecoder { return try .card(BCMCPaymentMethod(from: decoder)) } } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + if let method = paymentMethod as? StoredBCMCPaymentMethod { + return .storedBCMC(method) + } + if let method = paymentMethod as? BCMCPaymentMethod { + return .card(method) + } + return nil + } } private struct IssuerListPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .issuerList(IssuerListPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? IssuerListPaymentMethod).map { .issuerList($0) } + } } private struct SEPADirectDebitPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .sepaDirectDebit(SEPADirectDebitPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? SEPADirectDebitPaymentMethod).map { .sepaDirectDebit($0) } + } } private struct BACSDirectDebitPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .bacsDirectDebit(BACSDirectDebitPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? BACSDirectDebitPaymentMethod).map { .bacsDirectDebit($0) } + } } private struct ACHDirectDebitPaymentMethodDecoder: PaymentMethodDecoder { @@ -173,12 +211,26 @@ private struct ACHDirectDebitPaymentMethodDecoder: PaymentMethodDecoder { return try .achDirectDebit(ACHDirectDebitPaymentMethod(from: decoder)) } } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + if let method = paymentMethod as? StoredACHDirectDebitPaymentMethod { + return .storedAchDirectDebit(method) + } + if let method = paymentMethod as? ACHDirectDebitPaymentMethod { + return .achDirectDebit(method) + } + return nil + } } private struct ApplePayPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .applePay(ApplePayPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? ApplePayPaymentMethod).map { .applePay($0) } + } } private struct PayPalPaymentMethodDecoder: PaymentMethodDecoder { @@ -189,6 +241,16 @@ private struct PayPalPaymentMethodDecoder: PaymentMethodDecoder { return try .instant(InstantPaymentMethod(from: decoder)) } } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + if let method = paymentMethod as? StoredPayPalPaymentMethod { + return .storedPayPal(method) + } + if let method = paymentMethod as? InstantPaymentMethod { + return .instant(method) + } + return nil + } } private struct InstantPaymentMethodDecoder: PaymentMethodDecoder { @@ -199,30 +261,56 @@ private struct InstantPaymentMethodDecoder: PaymentMethodDecoder { return try .instant(InstantPaymentMethod(from: decoder)) } } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + if let method = paymentMethod as? StoredInstantPaymentMethod { + return .storedInstant(method) + } + if let method = paymentMethod as? InstantPaymentMethod { + return .instant(method) + } + return nil + } } private struct WeChatPayPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .weChatPay(WeChatPayPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? WeChatPayPaymentMethod).map { .weChatPay($0) } + } } private struct UnsupportedPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { .none } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + nil + } } private struct QiwiWalletPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .qiwiWallet(QiwiWalletPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? QiwiWalletPaymentMethod).map { .qiwiWallet($0) } + } } private struct MBWayPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .mbWay(MBWayPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? MBWayPaymentMethod).map { .mbWay($0) } + } } private struct BLIKPaymentMethodDecoder: PaymentMethodDecoder { @@ -233,78 +321,136 @@ private struct BLIKPaymentMethodDecoder: PaymentMethodDecoder { return try .blik(BLIKPaymentMethod(from: decoder)) } } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + if let method = paymentMethod as? StoredBLIKPaymentMethod { + return .storedBlik(method) + } + if let method = paymentMethod as? BLIKPaymentMethod { + return .blik(method) + } + return nil + } } private struct DokuPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .doku(DokuPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? DokuPaymentMethod).map { .doku($0) } + } } private struct GiftCardPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .giftcard(GiftCardPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? GiftCardPaymentMethod).map { .giftcard($0) } + } } private struct MealVoucherPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .mealVoucher(MealVoucherPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? MealVoucherPaymentMethod).map { .mealVoucher($0) } + } } private struct SevenElevenPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .sevenEleven(SevenElevenPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? SevenElevenPaymentMethod).map { .sevenEleven($0) } + } } private struct EContextStoresPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .econtextStores(EContextPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? EContextPaymentMethod).map { .econtextStores($0) } + } } private struct EContextATMPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .econtextATM(EContextPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? EContextPaymentMethod).map { .econtextATM($0) } + } } private struct EContextOnlinePaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .econtextOnline(EContextPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? EContextPaymentMethod).map { .econtextOnline($0) } + } } private struct BoletoPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .boleto(BoletoPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? BoletoPaymentMethod).map { .boleto($0) } + } } private struct AffirmPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .affirm(AffirmPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? AffirmPaymentMethod).map { .affirm($0) } + } } private struct AtomePaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .atome(AtomePaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? AtomePaymentMethod).map { .atome($0) } + } } private struct OnlineBankingPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .onlineBanking(OnlineBankingPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? OnlineBankingPaymentMethod).map { .onlineBanking($0) } + } } private struct UPIPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .upi(UPIPaymentMethod(from: decoder)) } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + (paymentMethod as? UPIPaymentMethod).map { .upi($0) } + } } private struct CashAppPayPaymentMethodDecoder: PaymentMethodDecoder { @@ -319,4 +465,17 @@ private struct CashAppPayPaymentMethodDecoder: PaymentMethodDecoder { return .none #endif } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + #if canImport(PayKit) + if let method = paymentMethod as? StoredCashAppPayPaymentMethod { + return .storedCashAppPay(method) + } + if let method = paymentMethod as? CashAppPayPaymentMethod { + return .cashAppPay(method) + } + #endif + + return nil + } } diff --git a/Adyen/Core/Payment Methods/Abstract/PaymentMethods.swift b/Adyen/Core/Payment Methods/Abstract/PaymentMethods.swift index 8eb1528b28..5fd62a82bd 100644 --- a/Adyen/Core/Payment Methods/Abstract/PaymentMethods.swift +++ b/Adyen/Core/Payment Methods/Abstract/PaymentMethods.swift @@ -12,7 +12,7 @@ import Foundation - SeeAlso: [API Reference](https://docs.adyen.com/api-explorer/#/CheckoutService/latest/post/paymentMethods__section_resParams) */ -public struct PaymentMethods: Decodable { +public struct PaymentMethods: Codable { /// The already paid payment methods, in case of partial payments. public var paid: [PaymentMethod] = [] @@ -147,137 +147,37 @@ public struct PaymentMethods: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.regular = try container.decode([AnyPaymentMethod].self, forKey: .regular).compactMap(\.value) - if try container.containsValue(.stored) { - self.stored = try container.decode([AnyPaymentMethod].self, forKey: .stored).compactMap { $0.value as? StoredPaymentMethod } - } else { - self.stored = [] - } - } - - internal enum CodingKeys: String, CodingKey { - case regular = "paymentMethods" - case stored = "storedPaymentMethods" + self.regular = try container.decode( + [AnyPaymentMethod].self, + forKey: .regular + ).compactMap(\.value) + + self.stored = try container.decodeIfPresent( + [AnyPaymentMethod].self, + forKey: .stored + )?.compactMap { + $0.value as? StoredPaymentMethod + } ?? [] } -} + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) -internal enum AnyPaymentMethod: Decodable { - case storedInstant(StoredInstantPaymentMethod) - case storedCard(StoredCardPaymentMethod) - case storedPayPal(StoredPayPalPaymentMethod) - case storedBCMC(StoredBCMCPaymentMethod) - case storedBlik(StoredBLIKPaymentMethod) - case storedAchDirectDebit(StoredACHDirectDebitPaymentMethod) - case storedCashAppPay(StoredCashAppPayPaymentMethod) - - case instant(PaymentMethod) - case card(AnyCardPaymentMethod) - case issuerList(IssuerListPaymentMethod) - case sepaDirectDebit(SEPADirectDebitPaymentMethod) - case bacsDirectDebit(BACSDirectDebitPaymentMethod) - case achDirectDebit(ACHDirectDebitPaymentMethod) - case applePay(ApplePayPaymentMethod) - case qiwiWallet(QiwiWalletPaymentMethod) - case weChatPay(WeChatPayPaymentMethod) - case mbWay(MBWayPaymentMethod) - case blik(BLIKPaymentMethod) - case giftcard(GiftCardPaymentMethod) - case mealVoucher(MealVoucherPaymentMethod) - case doku(DokuPaymentMethod) - case sevenEleven(SevenElevenPaymentMethod) - case econtextStores(EContextPaymentMethod) - case econtextATM(EContextPaymentMethod) - case econtextOnline(EContextPaymentMethod) - case boleto(BoletoPaymentMethod) - case affirm(AffirmPaymentMethod) - case atome(AtomePaymentMethod) - case onlineBanking(OnlineBankingPaymentMethod) - case upi(UPIPaymentMethod) - case cashAppPay(CashAppPayPaymentMethod) - - case none - - internal var value: PaymentMethod? { - switch self { - case let .storedCard(paymentMethod): - return paymentMethod - case let .storedPayPal(paymentMethod): - return paymentMethod - case let .storedBCMC(paymentMethod): - return paymentMethod - case let .instant(paymentMethod): - return paymentMethod - case let .storedInstant(paymentMethod): - return paymentMethod - case let .storedAchDirectDebit(paymentMethod): - return paymentMethod - case let .storedCashAppPay(paymentMethod): - return paymentMethod - case let .card(paymentMethod): - return paymentMethod - case let .issuerList(paymentMethod): - return paymentMethod - case let .sepaDirectDebit(paymentMethod): - return paymentMethod - case let .bacsDirectDebit(paymentMethod): - return paymentMethod - case let .achDirectDebit(paymentMethod): - return paymentMethod - case let .applePay(paymentMethod): - return paymentMethod - case let .qiwiWallet(paymentMethod): - return paymentMethod - case let .weChatPay(paymentMethod): - return paymentMethod - case let .mbWay(paymentMethod): - return paymentMethod - case let .blik(paymentMethod): - return paymentMethod - case let .storedBlik(paymentMethod): - return paymentMethod - case let .doku(paymentMethod): - return paymentMethod - case let .giftcard(paymentMethod): - return paymentMethod - case let .mealVoucher(paymentMethod): - return paymentMethod - case let .sevenEleven(paymentMethod): - return paymentMethod - case let .econtextStores(paymentMethod): - return paymentMethod - case let .econtextATM(paymentMethod): - return paymentMethod - case let .econtextOnline(paymentMethod): - return paymentMethod - case let .boleto(paymentMethod): - return paymentMethod - case let .affirm(paymentMethod): - return paymentMethod - case let .atome(paymentMethod): - return paymentMethod - case let .onlineBanking(paymentMethod): - return paymentMethod - case let .upi(paymentMethod): - return paymentMethod - case let .cashAppPay(paymentMethod): - return paymentMethod - case .none: - return nil - } - } - - // MARK: - Decoding - - internal init(from decoder: Decoder) throws { - self = AnyPaymentMethodDecoder.decode(from: decoder) + try container.encode( + regular.map(\.toAnyPaymentMethod), + forKey: .regular + ) + + try container.encode( + stored.map(\.toAnyPaymentMethod), + forKey: .stored + ) } internal enum CodingKeys: String, CodingKey { - case type - case details - case brand - case issuers + case regular = "paymentMethods" + case stored = "storedPaymentMethods" } + } diff --git a/Adyen/Core/Payment Methods/BCMCPaymentMethod.swift b/Adyen/Core/Payment Methods/BCMCPaymentMethod.swift index 8a9d805e67..909125d7d8 100644 --- a/Adyen/Core/Payment Methods/BCMCPaymentMethod.swift +++ b/Adyen/Core/Payment Methods/BCMCPaymentMethod.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -36,6 +36,10 @@ public struct BCMCPaymentMethod: AnyCardPaymentMethod { self.init(cardPaymentMethod: cardPaymentMethod) } + public func encode(to encoder: Encoder) throws { + try cardPaymentMethod.encode(to: encoder) + } + @_spi(AdyenInternal) public func buildComponent(using builder: PaymentComponentBuilder) -> PaymentComponent? { builder.build(paymentMethod: self) diff --git a/Adyen/Core/Payment Methods/CashAppPaymentMethod.swift b/Adyen/Core/Payment Methods/CashAppPaymentMethod.swift index 0166417ef7..85d1268131 100644 --- a/Adyen/Core/Payment Methods/CashAppPaymentMethod.swift +++ b/Adyen/Core/Payment Methods/CashAppPaymentMethod.swift @@ -26,11 +26,22 @@ public struct CashAppPayPaymentMethod: PaymentMethod { self.type = try container.decode(PaymentMethodType.self, forKey: .type) self.name = try container.decode(String.self, forKey: .name) - let configuration = try container.nestedContainer(keyedBy: ConfigurationCodingKeys.self, forKey: .configuration) + let configuration = try container.nestedContainer(keyedBy: CodingKeys.Configuration.self, forKey: .configuration) self.clientId = try configuration.decode(String.self, forKey: .clientId) self.scopeId = try configuration.decode(String.self, forKey: .scopeId) } + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(type, forKey: .type) + try container.encode(name, forKey: .name) + + var configuration = container.nestedContainer(keyedBy: CodingKeys.Configuration.self, forKey: .configuration) + try configuration.encode(clientId, forKey: .clientId) + try configuration.encode(scopeId, forKey: .scopeId) + } + @_spi(AdyenInternal) public func buildComponent(using builder: PaymentComponentBuilder) -> PaymentComponent? { builder.build(paymentMethod: self) @@ -42,10 +53,10 @@ public struct CashAppPayPaymentMethod: PaymentMethod { case type case name case configuration - } - - private enum ConfigurationCodingKeys: String, CodingKey { - case clientId - case scopeId + + enum Configuration: String, CodingKey { + case clientId + case scopeId + } } } diff --git a/Adyen/Core/Payment Methods/IssuerListPaymentMethod.swift b/Adyen/Core/Payment Methods/IssuerListPaymentMethod.swift index 27961b6e08..ea227a8733 100644 --- a/Adyen/Core/Payment Methods/IssuerListPaymentMethod.swift +++ b/Adyen/Core/Payment Methods/IssuerListPaymentMethod.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -31,9 +31,9 @@ public struct IssuerListPaymentMethod: PaymentMethod { var issuers = [Issuer]() while !detailsContainer.isAtEnd { - let detailContainer = try detailsContainer.nestedContainer(keyedBy: CodingKeys.self) + let detailContainer = try detailsContainer.nestedContainer(keyedBy: CodingKeys.Details.self) let detailKey = try detailContainer.decode(String.self, forKey: .key) - guard detailKey == "issuer" else { + guard detailKey == CodingKeys.Details.issuerKey else { continue } @@ -46,6 +46,13 @@ public struct IssuerListPaymentMethod: PaymentMethod { } } + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(type, forKey: .type) + try container.encode(name, forKey: .name) + try container.encode(issuers, forKey: .issuers) + } + @_spi(AdyenInternal) public func buildComponent(using builder: PaymentComponentBuilder) -> PaymentComponent? { builder.build(paymentMethod: self) @@ -54,10 +61,16 @@ public struct IssuerListPaymentMethod: PaymentMethod { private enum CodingKeys: String, CodingKey { case type case name - case details - case key - case items case issuers + + case details + + enum Details: String, CodingKey { + case key + case items + + static var issuerKey: String { "issuer" } + } } } diff --git a/Adyen/Core/Payment Methods/OnlineBankingPaymentMethod.swift b/Adyen/Core/Payment Methods/OnlineBankingPaymentMethod.swift index 83599a2566..ea8af70e7a 100644 --- a/Adyen/Core/Payment Methods/OnlineBankingPaymentMethod.swift +++ b/Adyen/Core/Payment Methods/OnlineBankingPaymentMethod.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -7,7 +7,7 @@ import Foundation /// An issuer (typically a bank) in an issuer list payment method. -public struct Issuer: Decodable, CustomStringConvertible, Equatable { +public struct Issuer: Codable, CustomStringConvertible, Equatable { /// The unique identifier of the issuer. public let identifier: String @@ -40,15 +40,6 @@ public struct OnlineBankingPaymentMethod: PaymentMethod { /// The available issuers. public let issuers: [Issuer] - // MARK: - Coding - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.type = try container.decode(PaymentMethodType.self, forKey: .type) - self.name = try container.decode(String.self, forKey: .name) - self.issuers = try container.decode([Issuer].self, forKey: .issuers) - } - @_spi(AdyenInternal) public func buildComponent(using builder: PaymentComponentBuilder) -> PaymentComponent? { builder.build(paymentMethod: self) diff --git a/Adyen/Core/Payment Methods/QiwiWalletPaymentMethod.swift b/Adyen/Core/Payment Methods/QiwiWalletPaymentMethod.swift index 68acfb19e0..6fddd5fec1 100644 --- a/Adyen/Core/Payment Methods/QiwiWalletPaymentMethod.swift +++ b/Adyen/Core/Payment Methods/QiwiWalletPaymentMethod.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -37,9 +37,9 @@ public struct QiwiWalletPaymentMethod: PaymentMethod { var phoneExtensions: [PhoneExtension]? if var detailsContainer = try? container.nestedUnkeyedContainer(forKey: .details) { while !detailsContainer.isAtEnd { - let detailContainer = try detailsContainer.nestedContainer(keyedBy: CodingKeys.self) + let detailContainer = try detailsContainer.nestedContainer(keyedBy: CodingKeys.Details.self) let detailKey = try detailContainer.decode(String.self, forKey: .key) - guard detailKey == "qiwiwallet.telephoneNumberPrefix" else { continue } + guard detailKey == CodingKeys.Details.phoneExtensionsKey else { continue } phoneExtensions = try detailContainer.decode([PhoneExtension].self, forKey: .items) } @@ -48,6 +48,19 @@ public struct QiwiWalletPaymentMethod: PaymentMethod { self.phoneExtensions = phoneExtensions ?? PhoneExtensionsRepository.get(with: PhoneExtensionsQuery(paymentMethod: .qiwiWallet)) } + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(type, forKey: .type) + try container.encode(name, forKey: .name) + + var detailsContainer = container.nestedUnkeyedContainer(forKey: .details) + var nested = detailsContainer.nestedContainer(keyedBy: CodingKeys.Details.self) + + try nested.encode(CodingKeys.Details.phoneExtensionsKey, forKey: .key) + try nested.encode(phoneExtensions, forKey: .items) + } + @_spi(AdyenInternal) public func buildComponent(using builder: PaymentComponentBuilder) -> PaymentComponent? { builder.build(paymentMethod: self) @@ -57,13 +70,18 @@ public struct QiwiWalletPaymentMethod: PaymentMethod { case type case name case details - case key - case items + + enum Details: String, CodingKey { + case key + case items + + static var phoneExtensionsKey: String { "qiwiwallet.telephoneNumberPrefix" } + } } } /// Describes a country phone extension. -public struct PhoneExtension: Decodable, Equatable { +public struct PhoneExtension: Codable, Equatable { /// The phone extension. public let value: String diff --git a/Adyen/Core/Payment Methods/StoredBCMCPaymentMethod.swift b/Adyen/Core/Payment Methods/StoredBCMCPaymentMethod.swift index 8e2009ddca..de08250a46 100644 --- a/Adyen/Core/Payment Methods/StoredBCMCPaymentMethod.swift +++ b/Adyen/Core/Payment Methods/StoredBCMCPaymentMethod.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -56,4 +56,8 @@ public struct StoredBCMCPaymentMethod: StoredPaymentMethod { self.storedCardPaymentMethod = try StoredCardPaymentMethod(from: decoder) } + public func encode(to encoder: Encoder) throws { + try storedCardPaymentMethod.encode(to: encoder) + } + } diff --git a/AdyenCard/Components/GiftCardComponent/GiftCardConfirmationPaymentMethod.swift b/AdyenCard/Components/GiftCardComponent/GiftCardConfirmationPaymentMethod.swift index 35736ad509..3966240a79 100644 --- a/AdyenCard/Components/GiftCardComponent/GiftCardConfirmationPaymentMethod.swift +++ b/AdyenCard/Components/GiftCardComponent/GiftCardConfirmationPaymentMethod.swift @@ -60,13 +60,14 @@ internal struct PartialConfirmationPaymentMethod: PaymentMethod { } internal init(from decoder: Decoder) throws { + // We have to conform to Codable because `PaymentMethod` requires it + // but this struct should never be encoded/decoded as it's an intermediate state fatalError("This class should never be decoded.") } - // MARK: - Decoding - - private enum CodingKeys: String, CodingKey { - case type - case name + internal func encode(to encoder: Encoder) throws { + // We have to conform to Codable because `PaymentMethod` requires it + // but this struct should never be encoded/decoded as it's an intermediate state + fatalError("This class should never be encoded.") } } diff --git a/Tests/Adyen Tests/Core/PaymentMethodTests.swift b/Tests/Adyen Tests/Core/PaymentMethodTests.swift index d94046547b..8c56506cdf 100644 --- a/Tests/Adyen Tests/Core/PaymentMethodTests.swift +++ b/Tests/Adyen Tests/Core/PaymentMethodTests.swift @@ -10,19 +10,13 @@ import XCTest class PaymentMethodTests: XCTestCase { - var paymentMethods: PaymentMethods! - override func tearDown() { super.tearDown() AdyenAssertion.listener = nil } - override func setUpWithError() throws { - paymentMethods = try getPaymentMethods() - } - - private func getPaymentMethods() throws -> PaymentMethods { - let dictionary = [ + private var paymentMethodsDictionary: [String: Any] { + [ "storedPaymentMethods": [ storedCreditCardDictionary, storedCreditCardDictionary, @@ -80,15 +74,36 @@ class PaymentMethodTests: XCTestCase { giftCard1, givexGiftCard, mealVoucherSodexo, - bizum + bizum, + boleto, + affirm, + atome, + upi, + cashAppPay ] ] - return try AdyenCoder.decode(dictionary) as PaymentMethods + } + + private func getPaymentMethods() throws -> PaymentMethods { + return try AdyenCoder.decode(paymentMethodsDictionary) as PaymentMethods + } + + // MARK: - Payment Methods + + func testPaymentMethodsCoding() throws { + let paymentMethods: PaymentMethods = try getPaymentMethods() + + let encodedPaymentMethods: Data = try AdyenCoder.encode(paymentMethods) + let decodedPaymentMethods: PaymentMethods = try AdyenCoder.decode(encodedPaymentMethods) + + XCTAssertEqual(paymentMethods, decodedPaymentMethods) } func testDecodingPaymentMethods() throws { // Stored payment methods + let paymentMethods = try getPaymentMethods() + XCTAssertEqual(paymentMethods.stored.count, 8) XCTAssertTrue(paymentMethods.stored[0] is StoredCardPaymentMethod) @@ -154,9 +169,10 @@ class PaymentMethodTests: XCTestCase { // Regular payment methods - XCTAssertEqual(paymentMethods.regular.count, 26) - XCTAssertTrue(paymentMethods.regular[0] is CardPaymentMethod) - XCTAssertEqual((paymentMethods.regular[0] as! CardPaymentMethod).fundingSource!, .credit) + XCTAssertEqual(paymentMethods.regular.count, 31) + + let creditCardPaymentMethod = try XCTUnwrap(paymentMethods.regular[0] as? CardPaymentMethod) + XCTAssertEqual(creditCardPaymentMethod.fundingSource, .credit) XCTAssertTrue(paymentMethods.regular[1] is IssuerListPaymentMethod) XCTAssertTrue(paymentMethods.regular[2] is SEPADirectDebitPaymentMethod) @@ -192,8 +208,7 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethods.regular[8].name, "GiroPay with non optional details") // Qiwi Wallet - XCTAssertTrue(paymentMethods.regular[9] is QiwiWalletPaymentMethod) - let qiwiMethod = paymentMethods.regular[9] as! QiwiWalletPaymentMethod + let qiwiMethod = try XCTUnwrap(paymentMethods.regular[9] as? QiwiWalletPaymentMethod) XCTAssertEqual(qiwiMethod.type.rawValue, "qiwiwallet") XCTAssertEqual(qiwiMethod.name, "Qiwi Wallet") XCTAssertEqual(qiwiMethod.phoneExtensions.count, 3) @@ -210,8 +225,8 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethods.regular[10].type.rawValue, "wechatpaySDK") XCTAssertEqual(paymentMethods.regular[10].name, "WeChat Pay") - XCTAssertTrue(paymentMethods.regular[11] is CardPaymentMethod) - XCTAssertEqual((paymentMethods.regular[11] as! CardPaymentMethod).fundingSource!, .debit) + let debitCardPaymentMethod = try XCTUnwrap(paymentMethods.regular[11] as? CardPaymentMethod) + XCTAssertEqual(debitCardPaymentMethod.fundingSource, .debit) XCTAssertTrue(paymentMethods.regular[12] is MBWayPaymentMethod) XCTAssertEqual(paymentMethods.regular[12].name, "MB WAY") @@ -268,10 +283,34 @@ class PaymentMethodTests: XCTestCase { XCTAssertTrue(paymentMethods.regular[25] is MealVoucherPaymentMethod) XCTAssertEqual(paymentMethods.regular[25].name, "Sodexo") XCTAssertEqual(paymentMethods.regular[25].type.rawValue, "mealVoucher_FR_sodexo") - + + XCTAssertTrue(paymentMethods.regular[26] is BoletoPaymentMethod) + XCTAssertEqual(paymentMethods.regular[26].name, "Boleto Bancario") + XCTAssertEqual(paymentMethods.regular[26].type.rawValue, "boletobancario_santander") + + XCTAssertTrue(paymentMethods.regular[27] is AffirmPaymentMethod) + XCTAssertEqual(paymentMethods.regular[27].name, "Affirm") + XCTAssertEqual(paymentMethods.regular[27].type.rawValue, "affirm") + + XCTAssertTrue(paymentMethods.regular[28] is AtomePaymentMethod) + XCTAssertEqual(paymentMethods.regular[28].name, "Atome") + XCTAssertEqual(paymentMethods.regular[28].type.rawValue, "atome") + + XCTAssertTrue(paymentMethods.regular[29] is UPIPaymentMethod) + XCTAssertEqual(paymentMethods.regular[29].name, "UPI") + XCTAssertEqual(paymentMethods.regular[29].type.rawValue, "upi") + + let cashAppPay = try XCTUnwrap(paymentMethods.regular[30] as? CashAppPayPaymentMethod) + XCTAssertEqual(cashAppPay.name, "Cash App Pay") + XCTAssertEqual(cashAppPay.type.rawValue, "cashapp") + XCTAssertEqual(cashAppPay.clientId, "testClient") + XCTAssertEqual(cashAppPay.scopeId, "testScope") } + // MARK: - Display Information Override + func testOverridingDisplayInformationCard() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofRegularPaymentMethod: .scheme, with: .init(title: "custom title", subtitle: "custom subtitle")) @@ -281,6 +320,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationBCMC() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofRegularPaymentMethod: .bcmc, with: .init(title: "custom title", subtitle: "custom subtitle")) @@ -290,6 +330,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationApplePay() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofRegularPaymentMethod: .applePay, with: .init(title: "custom title", subtitle: "custom subtitle")) @@ -299,6 +340,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationPayPal() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofRegularPaymentMethod: .payPal, with: .init(title: "custom title", subtitle: "custom subtitle")) @@ -308,6 +350,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationWeChat() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofRegularPaymentMethod: .weChatPaySDK, with: .init(title: "custom title", subtitle: "custom subtitle")) @@ -317,6 +360,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationQiwiWallet() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofRegularPaymentMethod: .qiwiWallet, with: .init(title: "custom title", subtitle: "custom subtitle")) @@ -326,6 +370,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationBLIK() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofRegularPaymentMethod: .blik, with: .init(title: "custom title", subtitle: "custom subtitle")) @@ -335,6 +380,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationStoredBLIK() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofStoredPaymentMethod: .blik, with: .init(title: "custom title", subtitle: "custom subtitle")) @@ -344,6 +390,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationStoredCreditCard() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofStoredPaymentMethod: .scheme, with: .init(title: "custom title", subtitle: "custom subtitle"), @@ -362,6 +409,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationStoredDebitCard() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofStoredPaymentMethod: .scheme, with: .init(title: "custom title", subtitle: "custom subtitle"), @@ -380,6 +428,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationGiro() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofRegularPaymentMethod: .other("giropay"), with: .init(title: "custom title", subtitle: "custom subtitle")) @@ -389,6 +438,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationGenericGiftCard() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation( ofRegularPaymentMethod: .giftcard, with: .init(title: "custom title", @@ -413,6 +463,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationGivexGiftCard() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation( ofRegularPaymentMethod: .giftcard, with: .init(title: "custom title", @@ -441,6 +492,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationAnyGivenGiftCard() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation( ofRegularPaymentMethod: .giftcard, with: .init(title: "custom title", @@ -469,6 +521,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationMealVoucher() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofRegularPaymentMethod: .mealVoucherSodexo, with: .init(title: "custom title", subtitle: "custom subtitle")) @@ -479,6 +532,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationDukoWallet() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofRegularPaymentMethod: .dokuWallet, with: .init(title: "custom title", subtitle: "custom subtitle")) @@ -488,6 +542,7 @@ class PaymentMethodTests: XCTestCase { } func testOverridingDisplayInformationIdeal() throws { + var paymentMethods = try getPaymentMethods() paymentMethods.overrideDisplayInformation(ofRegularPaymentMethod: .ideal, with: .init(title: "custom title", subtitle: "custom subtitle")) @@ -495,6 +550,8 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(idealPaymentMethod?.displayInformation(using: nil).title, "custom title") XCTAssertEqual(idealPaymentMethod?.displayInformation(using: nil).subtitle, "custom subtitle") } + + // MARK: - Misc func testDecodingPaymentMethodsWithNullValues() throws { @@ -600,6 +657,7 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethod.name, "Credit Card") XCTAssertEqual(paymentMethod.fundingSource!, .credit) XCTAssertEqual(paymentMethod.brands, [.masterCard, .visa, .americanExpress]) + testCoding(paymentMethod) } func testDecodingDebitCardPaymentMethod() throws { @@ -608,6 +666,7 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethod.name, "Credit Card") XCTAssertEqual(paymentMethod.fundingSource!, .debit) XCTAssertEqual(paymentMethod.brands, [.masterCard, .visa, .americanExpress]) + testCoding(paymentMethod) } func testDecodingBCMCCardPaymentMethod() throws { @@ -615,6 +674,7 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethod.type.rawValue, "bcmc") XCTAssertEqual(paymentMethod.name, "Bancontact card") XCTAssertEqual(paymentMethod.brands, []) + testCoding(paymentMethod) } func testDecodingCardPaymentMethodWithoutBrands() throws { @@ -625,6 +685,7 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethod.type.rawValue, "scheme") XCTAssertEqual(paymentMethod.name, "Credit Card") XCTAssertTrue(paymentMethod.brands.isEmpty) + testCoding(paymentMethod) } func testDecodingStoredCreditCardPaymentMethod() throws { @@ -641,6 +702,7 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethod.supportedShopperInteractions, [.shopperPresent, .shopperNotPresent]) XCTAssertEqual(paymentMethod.displayInformation(using: nil), expectedStoredCardPaymentMethodDisplayInfo(method: paymentMethod, localizationParameters: nil)) XCTAssertEqual(paymentMethod.displayInformation(using: expectedLocalizationParameters), expectedStoredCardPaymentMethodDisplayInfo(method: paymentMethod, localizationParameters: expectedLocalizationParameters)) + testCoding(paymentMethod) } func testDecodingStoredDebitCardPaymentMethod() throws { @@ -657,6 +719,7 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethod.supportedShopperInteractions, [.shopperPresent, .shopperNotPresent]) XCTAssertEqual(paymentMethod.displayInformation(using: nil), expectedStoredCardPaymentMethodDisplayInfo(method: paymentMethod, localizationParameters: nil)) XCTAssertEqual(paymentMethod.displayInformation(using: expectedLocalizationParameters), expectedStoredCardPaymentMethodDisplayInfo(method: paymentMethod, localizationParameters: expectedLocalizationParameters)) + testCoding(paymentMethod) } public func expectedStoredCardPaymentMethodDisplayInfo(method: StoredCardPaymentMethod, localizationParameters: LocalizationParameters?) -> DisplayInformation { @@ -691,6 +754,8 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethod.issuers[1].name, "Test Issuer 2") XCTAssertEqual(paymentMethod.issuers[2].identifier, "xxxx") XCTAssertEqual(paymentMethod.issuers[2].name, "Test Issuer 3") + + testCoding(paymentMethod) } func testDecodingIssuerListPaymentMethodWithoutDetailsObject() throws { @@ -705,6 +770,8 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethod.issuers[1].name, "Test Issuer 2") XCTAssertEqual(paymentMethod.issuers[2].identifier, "1153") XCTAssertEqual(paymentMethod.issuers[2].name, "Test Issuer 3") + + testCoding(paymentMethod) } // MARK: - SEPA Direct Debit @@ -718,6 +785,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(sepaDirectDebitDictionary) as SEPADirectDebitPaymentMethod XCTAssertEqual(paymentMethod.type.rawValue, "sepadirectdebit") XCTAssertEqual(paymentMethod.name, "SEPA Direct Debit") + testCoding(paymentMethod) } // MARK: - Stored PayPal @@ -729,6 +797,7 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethod.name, "PayPal") XCTAssertEqual(paymentMethod.emailAddress, "example@shopper.com") XCTAssertEqual(paymentMethod.supportedShopperInteractions, [.shopperPresent, .shopperNotPresent]) + testCoding(paymentMethod) } // MARK: - Apple Pay @@ -737,6 +806,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(applePayDictionary) as ApplePayPaymentMethod XCTAssertEqual(paymentMethod.type.rawValue, "applepay") XCTAssertEqual(paymentMethod.name, "Apple Pay") + testCoding(paymentMethod) } // MARK: - Bancontact @@ -745,6 +815,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(bcmcCardDictionary) as BCMCPaymentMethod XCTAssertEqual(paymentMethod.type.rawValue, "bcmc") XCTAssertEqual(paymentMethod.name, "Bancontact card") + testCoding(paymentMethod) } // MARK: - GiroPay @@ -753,6 +824,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(giroPayDictionaryWithOptionalDetails) as InstantPaymentMethod XCTAssertEqual(paymentMethod.type.rawValue, "giropay") XCTAssertEqual(paymentMethod.name, "GiroPay") + testCoding(paymentMethod) } // MARK: - Seven Eleven @@ -761,6 +833,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(sevenElevenDictionary) as SevenElevenPaymentMethod XCTAssertEqual(paymentMethod.name, "7-Eleven") XCTAssertEqual(paymentMethod.type.rawValue, "econtext_seven_eleven") + testCoding(paymentMethod) } // MARK: - E-Context Online @@ -769,6 +842,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(econtextOnline) as EContextPaymentMethod XCTAssertEqual(paymentMethod.name, "Online Banking") XCTAssertEqual(paymentMethod.type.rawValue, "econtext_online") + testCoding(paymentMethod) } // MARK: - OXXO @@ -777,6 +851,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(oxxo) as OXXOPaymentMethod XCTAssertEqual(paymentMethod.name, "OXXO") XCTAssertEqual(paymentMethod.type.rawValue, "oxxo") + testCoding(paymentMethod) } // MARK: - E-Context ATM @@ -785,6 +860,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(econtextATM) as EContextPaymentMethod XCTAssertEqual(paymentMethod.name, "Pay-easy ATM") XCTAssertEqual(paymentMethod.type.rawValue, "econtext_atm") + testCoding(paymentMethod) } // MARK: - E-Context Stores @@ -793,6 +869,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(econtextStores) as EContextPaymentMethod XCTAssertEqual(paymentMethod.name, "Convenience Stores") XCTAssertEqual(paymentMethod.type.rawValue, "econtext_stores") + testCoding(paymentMethod) } // MARK: - Stored Bancontact @@ -813,6 +890,7 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethod.displayInformation(using: nil), expectedDisplayInfo) XCTAssertEqual(paymentMethod.displayInformation(using: expectedLocalizationParameters), expectedBancontactCardDisplayInfo(method: paymentMethod, localizationParameters: expectedLocalizationParameters)) + testCoding(paymentMethod) } // MARK: - MBWay @@ -821,6 +899,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(mbway) as MBWayPaymentMethod XCTAssertEqual(paymentMethod.type.rawValue, "mbway") XCTAssertEqual(paymentMethod.name, "MB WAY") + testCoding(paymentMethod) } // MARK: - Doku wallet @@ -829,6 +908,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(dokuWallet) as DokuPaymentMethod XCTAssertEqual(paymentMethod.type.rawValue, "doku_wallet") XCTAssertEqual(paymentMethod.name, "DOKU wallet") + testCoding(paymentMethod) } public func expectedBancontactCardDisplayInfo(method: StoredBCMCPaymentMethod, @@ -856,8 +936,9 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(giftCard) as GiftCardPaymentMethod XCTAssertEqual(paymentMethod.type.rawValue, "giftcard") XCTAssertEqual(paymentMethod.name, "Generic GiftCard") + XCTAssertEqual(paymentMethod.displayInformation(using: nil).title, "Generic GiftCard") XCTAssertEqual(paymentMethod.displayInformation(using: nil).logoName, "genericgiftcard") - XCTAssertEqual(paymentMethod.displayInformation(using: nil).logoName, "genericgiftcard") + testCoding(paymentMethod) } func testDecodingMealVoucherPaymentMethod() throws { @@ -866,6 +947,7 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethod.name, "Sodexo") XCTAssertEqual(paymentMethod.displayInformation(using: nil).title, "Sodexo") XCTAssertEqual(paymentMethod.displayInformation(using: nil).logoName, "mealVoucher_FR_sodexo") + testCoding(paymentMethod) } // MARK: - Boleto @@ -874,8 +956,9 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(boleto) as BoletoPaymentMethod XCTAssertEqual(paymentMethod.type.rawValue, "boletobancario_santander") XCTAssertEqual(paymentMethod.name, "Boleto Bancario") + XCTAssertEqual(paymentMethod.displayInformation(using: nil).title, "Boleto Bancario") XCTAssertEqual(paymentMethod.displayInformation(using: nil).logoName, "boletobancario_santander") - XCTAssertEqual(paymentMethod.displayInformation(using: nil).logoName, "boletobancario_santander") + testCoding(paymentMethod) } // MARK: - BACS Direct Debit @@ -884,6 +967,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(bacsDirectDebit) as BACSDirectDebitPaymentMethod XCTAssertEqual(paymentMethod.type.rawValue, "directdebit_GB") XCTAssertEqual(paymentMethod.name, "BACS Direct Debit") + testCoding(paymentMethod) } // MARK: - ACH Direct Debit @@ -892,6 +976,7 @@ class PaymentMethodTests: XCTestCase { let paymentMethod = try AdyenCoder.decode(achDirectDebit) as ACHDirectDebitPaymentMethod XCTAssertEqual(paymentMethod.type.rawValue, "ach") XCTAssertEqual(paymentMethod.name, "ACH Direct Debit") + testCoding(paymentMethod) } func testDecodingStoredACHDirectDebitPaymentMethod() throws { @@ -899,6 +984,25 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(paymentMethod.type.rawValue, "ach") XCTAssertEqual(paymentMethod.name, "ACH Direct Debit") XCTAssertEqual(paymentMethod.bankAccountNumber, "123456789") + testCoding(paymentMethod) + } + + // MARK: - Cash App + + func testDecodingCashAppPayPaymentMethod() throws { + let paymentMethod = try AdyenCoder.decode(cashAppPay) as CashAppPayPaymentMethod + XCTAssertEqual(paymentMethod.type.rawValue, "cashapp") + XCTAssertEqual(paymentMethod.name, "Cash App Pay") + testCoding(paymentMethod) + } + + // MARK: - Cash App + + func testDecodingQiwiPaymentMethod() throws { + let paymentMethod = try AdyenCoder.decode(qiwiWallet) as QiwiWalletPaymentMethod + XCTAssertEqual(paymentMethod.type.rawValue, "qiwiwallet") + XCTAssertEqual(paymentMethod.name, "Qiwi Wallet") + testCoding(paymentMethod) } // MARK: - PaymentMethodDetails @@ -940,3 +1044,23 @@ class PaymentMethodTests: XCTestCase { } } } + +private extension PaymentMethodTests { + + func testCoding(_ paymentMethod: T) { + do { + let encoded: Data = try AdyenCoder.encode(paymentMethod) + let decoded: T = try AdyenCoder.decode(encoded) + + // Re-Encode to compare if the data is still the same after the roundtrip + let reEncoded: Data = try AdyenCoder.encode(decoded) + + XCTAssertEqual( + String(data: encoded, encoding: .utf8), + String(data: reEncoded, encoding: .utf8) + ) + } catch { + XCTFail(error.localizedDescription) + } + } +} diff --git a/Tests/DropIn Tests/ComponentManagerTests.swift b/Tests/DropIn Tests/ComponentManagerTests.swift index 2d718418fd..ac1c4c438a 100644 --- a/Tests/DropIn Tests/ComponentManagerTests.swift +++ b/Tests/DropIn Tests/ComponentManagerTests.swift @@ -475,6 +475,8 @@ class ComponentManagerTests: XCTestCase { init() {} init(from decoder: Decoder) throws {} + + enum CodingKeys: CodingKey {} // Satisfying Encoding requirement } let dummy = DummyPaymentMethod()