From ceae25fb9136ebc96c47d8cb5c1e419d1095b0b2 Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Tue, 15 Oct 2024 13:35:03 +0200 Subject: [PATCH 01/13] Add NativeRedirectAction --- AdyenActions/Actions/Action.swift | 6 ++- AdyenActions/Actions/RedirectAction.swift | 45 +++++++++++++++---- .../Redirect/RedirectComponent.swift | 11 +++-- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/AdyenActions/Actions/Action.swift b/AdyenActions/Actions/Action.swift index bc1d6304f3..fb6315f23e 100644 --- a/AdyenActions/Actions/Action.swift +++ b/AdyenActions/Actions/Action.swift @@ -11,7 +11,7 @@ public enum Action: Decodable { /// Indicates the user should be redirected to a URL. case redirect(RedirectAction) - + /// Indicates the user should be redirected to an SDK. case sdk(SDKAction) @@ -47,8 +47,10 @@ public enum Action: Decodable { let type = try container.decode(ActionType.self, forKey: .type) switch type { - case .redirect, .nativeRedirect: + case .redirect: self = try .redirect(RedirectAction(from: decoder)) + case .nativeRedirect: + self = try .redirect(NativeRedirectAction(from: decoder)) case .threeDS2Fingerprint: self = try .threeDS2Fingerprint(ThreeDS2FingerprintAction(from: decoder)) case .threeDS2Challenge: diff --git a/AdyenActions/Actions/RedirectAction.swift b/AdyenActions/Actions/RedirectAction.swift index 9693c56bfc..7db29d19c5 100644 --- a/AdyenActions/Actions/RedirectAction.swift +++ b/AdyenActions/Actions/RedirectAction.swift @@ -7,26 +7,55 @@ import Foundation /// Describes an action in which the user is redirected to a URL. -public struct RedirectAction: Decodable { - +public class RedirectAction: Decodable { + /// The URL to which to redirect the user. public let url: URL - + /// The server-generated payment data that should be submitted to the `/payments/details` endpoint. public let paymentData: String? - + + /// Initializes a redirect action. + /// + /// - Parameters: + /// - url: The URL to which to redirect the user. + /// - paymentData: The server-generated payment data that should be submitted to the `/payments/details` endpoint. + public init(url: URL, paymentData: String?) { + self.url = url + self.paymentData = paymentData + } +} + +/// Describes an action in which the user is redirected to a URL. +public class NativeRedirectAction: RedirectAction { + /// Native redirect data. public let nativeRedirectData: String? - - /// Initializes a redirect action. + + /// Initializes a native redirect action. /// /// - Parameters: /// - url: The URL to which to redirect the user. /// - paymentData: The server-generated payment data that should be submitted to the `/payments/details` endpoint. /// - nativeRedirectData: Native redirect data. public init(url: URL, paymentData: String?, nativeRedirectData: String? = nil) { - self.url = url - self.paymentData = paymentData self.nativeRedirectData = nativeRedirectData + super.init(url: url, paymentData: paymentData) + } + + public required init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let url = try container.decode(URL.self, forKey: CodingKeys.url) + let paymentData = try container.decodeIfPresent(String.self, forKey: CodingKeys.paymentData) + self.nativeRedirectData = try container.decodeIfPresent(String.self, forKey: CodingKeys.nativeRedirectData) + super.init(url: url, paymentData: paymentData) + } + + // MARK: - CodingKeys + + private enum CodingKeys: CodingKey { + case url + case paymentData + case nativeRedirectData } } diff --git a/AdyenActions/Components/Redirect/RedirectComponent.swift b/AdyenActions/Components/Redirect/RedirectComponent.swift index 8d471039a5..ef2eb55c13 100644 --- a/AdyenActions/Components/Redirect/RedirectComponent.swift +++ b/AdyenActions/Components/Redirect/RedirectComponent.swift @@ -172,14 +172,19 @@ public final class RedirectComponent: ActionComponent { } private func didOpen(url returnURL: URL, _ action: RedirectAction) throws { - if let redirectStateData = action.nativeRedirectData { - try handleNativeMobileRedirect(withReturnURL: returnURL, redirectStateData: redirectStateData, action) + if let nativeRedirectAction = action as? NativeRedirectAction { + let redirectStateData = nativeRedirectAction.nativeRedirectData + try handleNativeMobileRedirect( + withReturnURL: returnURL, + redirectStateData: redirectStateData, + action + ) } else { try notifyDelegateDidProvide(redirectDetails: RedirectDetails(returnURL: returnURL), action) } } - private func handleNativeMobileRedirect(withReturnURL returnURL: URL, redirectStateData: String, _ action: RedirectAction) throws { + private func handleNativeMobileRedirect(withReturnURL returnURL: URL, redirectStateData: String?, _ action: RedirectAction) throws { guard let queryString = returnURL.query else { throw Error.invalidRedirectParameters } From 9bd40ca1dfd475c245aedb605d01077c610c4672 Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Fri, 25 Oct 2024 14:33:33 +0200 Subject: [PATCH 02/13] Update unit tests --- Adyen.xcodeproj/project.pbxproj | 4 ++ .../Actions/NativeRedirectAction.swift | 41 +++++++++++++++++++ AdyenActions/Actions/RedirectAction.swift | 34 --------------- .../Redirect/RedirectComponentTests.swift | 6 +-- 4 files changed, 48 insertions(+), 37 deletions(-) create mode 100644 AdyenActions/Actions/NativeRedirectAction.swift diff --git a/Adyen.xcodeproj/project.pbxproj b/Adyen.xcodeproj/project.pbxproj index cbe6d20f12..a5fd02fcc3 100644 --- a/Adyen.xcodeproj/project.pbxproj +++ b/Adyen.xcodeproj/project.pbxproj @@ -496,6 +496,7 @@ C9454C37276A340B0086C218 /* BACSDirectDebitPresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9454C35276A33A00086C218 /* BACSDirectDebitPresentationDelegate.swift */; }; C9454C38276A34150086C218 /* BACSDirectDebitPresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9454C35276A33A00086C218 /* BACSDirectDebitPresentationDelegate.swift */; }; C94632BE27BA6985003DD81F /* AnalyticsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94632BD27BA6985003DD81F /* AnalyticsProvider.swift */; }; + C958E9AD2CCBC63B005F2C69 /* NativeRedirectAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C958E9AC2CCBC63B005F2C69 /* NativeRedirectAction.swift */; }; C95903DE275A48D000E7D3BC /* BACSDirectDebitComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95903DD275A48D000E7D3BC /* BACSDirectDebitComponentTests.swift */; }; C96688BF26A6FC1C00DC7297 /* AffirmComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96688BE26A6FC1C00DC7297 /* AffirmComponentTests.swift */; }; C96E07A3283B92E300345732 /* BACSDirectDebitComponentTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96E07A1283B92D500345732 /* BACSDirectDebitComponentTrackerTests.swift */; }; @@ -1806,6 +1807,7 @@ C93B01B82760B06300D311A1 /* BACSConfirmationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BACSConfirmationPresenter.swift; sourceTree = ""; }; C9454C35276A33A00086C218 /* BACSDirectDebitPresentationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BACSDirectDebitPresentationDelegate.swift; sourceTree = ""; }; C94632BD27BA6985003DD81F /* AnalyticsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsProvider.swift; sourceTree = ""; }; + C958E9AC2CCBC63B005F2C69 /* NativeRedirectAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeRedirectAction.swift; sourceTree = ""; }; C95903DD275A48D000E7D3BC /* BACSDirectDebitComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BACSDirectDebitComponentTests.swift; sourceTree = ""; }; C95C89312BF63A3500C47296 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C96688BE26A6FC1C00DC7297 /* AffirmComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AffirmComponentTests.swift; sourceTree = ""; }; @@ -3741,6 +3743,7 @@ 5A988B7A2653F1750007F4C0 /* BoletoVoucherAction.swift */, F99D2F6F2664EDC900BB5B2F /* AnyVoucherAction.swift */, A02AF3E9275A3A5100E1636C /* DocumentAction.swift */, + C958E9AC2CCBC63B005F2C69 /* NativeRedirectAction.swift */, ); path = Actions; sourceTree = ""; @@ -7495,6 +7498,7 @@ F96757C327CF909900A16FB6 /* AnyWeChatPaySDKActionComponent.swift in Sources */, F9175FD22594999600D653BE /* RedirectComponent.swift in Sources */, 00165FE02C05DA8600347399 /* RedireactableAwaitAction.swift in Sources */, + C958E9AD2CCBC63B005F2C69 /* NativeRedirectAction.swift in Sources */, F9237D3D28CB470E004F9929 /* ThreeDS2ClassicActionHandler+Initializers.swift in Sources */, 81E4231B2C74DC3600349A5E /* String+QRCode.swift in Sources */, 8182AAB72B95D3560087568E /* Twint+Injectable.swift in Sources */, diff --git a/AdyenActions/Actions/NativeRedirectAction.swift b/AdyenActions/Actions/NativeRedirectAction.swift new file mode 100644 index 0000000000..e99508fb8d --- /dev/null +++ b/AdyenActions/Actions/NativeRedirectAction.swift @@ -0,0 +1,41 @@ +// +// 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 + +/// Describes an action in which the user is redirected to an app. +public class NativeRedirectAction: RedirectAction { + + /// Native redirect data. + public let nativeRedirectData: String? + + /// Initializes a native redirect action. + /// + /// - Parameters: + /// - url: The URL to which to redirect the user. + /// - paymentData: The server-generated payment data that should be submitted to the `/payments/details` endpoint. + /// - nativeRedirectData: Native redirect data. + public init(url: URL, paymentData: String?, nativeRedirectData: String? = nil) { + self.nativeRedirectData = nativeRedirectData + super.init(url: url, paymentData: paymentData) + } + + public required init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let url = try container.decode(URL.self, forKey: CodingKeys.url) + let paymentData = try container.decodeIfPresent(String.self, forKey: CodingKeys.paymentData) + self.nativeRedirectData = try container.decodeIfPresent(String.self, forKey: CodingKeys.nativeRedirectData) + super.init(url: url, paymentData: paymentData) + } + + // MARK: - CodingKeys + + private enum CodingKeys: CodingKey { + case url + case paymentData + case nativeRedirectData + } +} diff --git a/AdyenActions/Actions/RedirectAction.swift b/AdyenActions/Actions/RedirectAction.swift index 7db29d19c5..6ef69ee238 100644 --- a/AdyenActions/Actions/RedirectAction.swift +++ b/AdyenActions/Actions/RedirectAction.swift @@ -25,37 +25,3 @@ public class RedirectAction: Decodable { self.paymentData = paymentData } } - -/// Describes an action in which the user is redirected to a URL. -public class NativeRedirectAction: RedirectAction { - - /// Native redirect data. - public let nativeRedirectData: String? - - /// Initializes a native redirect action. - /// - /// - Parameters: - /// - url: The URL to which to redirect the user. - /// - paymentData: The server-generated payment data that should be submitted to the `/payments/details` endpoint. - /// - nativeRedirectData: Native redirect data. - public init(url: URL, paymentData: String?, nativeRedirectData: String? = nil) { - self.nativeRedirectData = nativeRedirectData - super.init(url: url, paymentData: paymentData) - } - - public required init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let url = try container.decode(URL.self, forKey: CodingKeys.url) - let paymentData = try container.decodeIfPresent(String.self, forKey: CodingKeys.paymentData) - self.nativeRedirectData = try container.decodeIfPresent(String.self, forKey: CodingKeys.nativeRedirectData) - super.init(url: url, paymentData: paymentData) - } - - // MARK: - CodingKeys - - private enum CodingKeys: CodingKey { - case url - case paymentData - case nativeRedirectData - } -} diff --git a/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift b/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift index fc7ac99619..4771453310 100644 --- a/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift +++ b/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift @@ -283,7 +283,7 @@ class RedirectComponentTests: XCTestCase { } delegate.onDidFail = { _, _ in XCTFail("Should not call onDidFail") } - let action = RedirectAction(url: URL(string: "https://google.com")!, paymentData: nil, nativeRedirectData: "test_nativeRedirectData") + let action = NativeRedirectAction(url: URL(string: "https://google.com")!, paymentData: nil, nativeRedirectData: "test_nativeRedirectData") sut.handle(action) XCTAssertTrue(RedirectComponent.applicationDidOpen(from: URL(string: "url://?queryParam=value")!)) @@ -311,7 +311,7 @@ class RedirectComponentTests: XCTestCase { XCTFail("Should not call onDidProvide") } - let action = RedirectAction(url: URL(string: "https://google.com")!, paymentData: nil, nativeRedirectData: "test_nativeRedirectData") + let action = NativeRedirectAction(url: URL(string: "https://google.com")!, paymentData: nil, nativeRedirectData: "test_nativeRedirectData") sut.handle(action) XCTAssertFalse(RedirectComponent.applicationDidOpen(from: URL(string: "url://")!)) @@ -343,7 +343,7 @@ class RedirectComponentTests: XCTestCase { redirectExpectation.fulfill() } - let action = RedirectAction(url: URL(string: "https://google.com")!, paymentData: nil, nativeRedirectData: "test_nativeRedirectData") + let action = NativeRedirectAction(url: URL(string: "https://google.com")!, paymentData: nil, nativeRedirectData: "test_nativeRedirectData") sut.handle(action) XCTAssertTrue(RedirectComponent.applicationDidOpen(from: URL(string: "url://?queryParam=value")!)) From 39675e821dd7109855881a0e17e58f202d06b1bf Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Fri, 25 Oct 2024 14:46:25 +0200 Subject: [PATCH 03/13] Test native redirect flow when nativeRedirectData is nil --- .../Redirect/RedirectComponentTests.swift | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift b/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift index 4771453310..d4a4f09fe7 100644 --- a/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift +++ b/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift @@ -349,4 +349,38 @@ class RedirectComponentTests: XCTestCase { waitForExpectations(timeout: 10) } + + func testNativeRedirectWithNativeRedirectDataNilShouldPerformNativeRedirectResultRequest() { + // Given + let apiClient = APIClientMock() + let sut = RedirectComponent(context: Dummy.context, apiClient: apiClient.retryAPIClient(with: SimpleScheduler(maximumCount: 2))) + apiClient.mockedResults = [.success(try! RedirectDetails(returnURL: URL(string: "url://?redirectResult=test_redirectResult")!))] + + let appLauncher = AppLauncherMock() + sut.appLauncher = appLauncher + let appLauncherExpectation = expectation(description: "Expect appLauncher.openUniversalAppUrl() to be called") + appLauncher.onOpenUniversalAppUrl = { url, completion in + XCTAssertEqual(url, URL(string: "https://google.com")!) + completion?(true) + appLauncherExpectation.fulfill() + } + + let delegate = ActionComponentDelegateMock() + sut.delegate = delegate + let redirectExpectation = expectation(description: "Expect redirect to be proccessed") + delegate.onDidProvide = { data, component in + XCTAssertTrue(component === sut) + XCTAssertNotNil(data.details) + redirectExpectation.fulfill() + } + delegate.onDidFail = { _, _ in XCTFail("Should not call onDidFail") } + + // When + let action = NativeRedirectAction(url: URL(string: "https://google.com")!, paymentData: nil, nativeRedirectData: nil) + sut.handle(action) + + // Then + XCTAssertTrue(RedirectComponent.applicationDidOpen(from: URL(string: "url://?queryParam=value")!)) + waitForExpectations(timeout: 10) + } } From d574e11173804a4edf621abe4e2573785b5cee56 Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Mon, 28 Oct 2024 13:11:21 +0100 Subject: [PATCH 04/13] Flag internallty nativeRedirect flow --- Adyen.xcodeproj/project.pbxproj | 4 -- AdyenActions/Actions/Action.swift | 4 +- .../Actions/NativeRedirectAction.swift | 41 ------------------- AdyenActions/Actions/RedirectAction.swift | 38 ++++++++++++++++- .../Redirect/RedirectComponent.swift | 5 +-- .../ThreeDS2FingerprintSubmitterTests.swift | 2 +- 6 files changed, 41 insertions(+), 53 deletions(-) delete mode 100644 AdyenActions/Actions/NativeRedirectAction.swift diff --git a/Adyen.xcodeproj/project.pbxproj b/Adyen.xcodeproj/project.pbxproj index a5fd02fcc3..cbe6d20f12 100644 --- a/Adyen.xcodeproj/project.pbxproj +++ b/Adyen.xcodeproj/project.pbxproj @@ -496,7 +496,6 @@ C9454C37276A340B0086C218 /* BACSDirectDebitPresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9454C35276A33A00086C218 /* BACSDirectDebitPresentationDelegate.swift */; }; C9454C38276A34150086C218 /* BACSDirectDebitPresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9454C35276A33A00086C218 /* BACSDirectDebitPresentationDelegate.swift */; }; C94632BE27BA6985003DD81F /* AnalyticsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94632BD27BA6985003DD81F /* AnalyticsProvider.swift */; }; - C958E9AD2CCBC63B005F2C69 /* NativeRedirectAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C958E9AC2CCBC63B005F2C69 /* NativeRedirectAction.swift */; }; C95903DE275A48D000E7D3BC /* BACSDirectDebitComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95903DD275A48D000E7D3BC /* BACSDirectDebitComponentTests.swift */; }; C96688BF26A6FC1C00DC7297 /* AffirmComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96688BE26A6FC1C00DC7297 /* AffirmComponentTests.swift */; }; C96E07A3283B92E300345732 /* BACSDirectDebitComponentTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96E07A1283B92D500345732 /* BACSDirectDebitComponentTrackerTests.swift */; }; @@ -1807,7 +1806,6 @@ C93B01B82760B06300D311A1 /* BACSConfirmationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BACSConfirmationPresenter.swift; sourceTree = ""; }; C9454C35276A33A00086C218 /* BACSDirectDebitPresentationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BACSDirectDebitPresentationDelegate.swift; sourceTree = ""; }; C94632BD27BA6985003DD81F /* AnalyticsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsProvider.swift; sourceTree = ""; }; - C958E9AC2CCBC63B005F2C69 /* NativeRedirectAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeRedirectAction.swift; sourceTree = ""; }; C95903DD275A48D000E7D3BC /* BACSDirectDebitComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BACSDirectDebitComponentTests.swift; sourceTree = ""; }; C95C89312BF63A3500C47296 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C96688BE26A6FC1C00DC7297 /* AffirmComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AffirmComponentTests.swift; sourceTree = ""; }; @@ -3743,7 +3741,6 @@ 5A988B7A2653F1750007F4C0 /* BoletoVoucherAction.swift */, F99D2F6F2664EDC900BB5B2F /* AnyVoucherAction.swift */, A02AF3E9275A3A5100E1636C /* DocumentAction.swift */, - C958E9AC2CCBC63B005F2C69 /* NativeRedirectAction.swift */, ); path = Actions; sourceTree = ""; @@ -7498,7 +7495,6 @@ F96757C327CF909900A16FB6 /* AnyWeChatPaySDKActionComponent.swift in Sources */, F9175FD22594999600D653BE /* RedirectComponent.swift in Sources */, 00165FE02C05DA8600347399 /* RedireactableAwaitAction.swift in Sources */, - C958E9AD2CCBC63B005F2C69 /* NativeRedirectAction.swift in Sources */, F9237D3D28CB470E004F9929 /* ThreeDS2ClassicActionHandler+Initializers.swift in Sources */, 81E4231B2C74DC3600349A5E /* String+QRCode.swift in Sources */, 8182AAB72B95D3560087568E /* Twint+Injectable.swift in Sources */, diff --git a/AdyenActions/Actions/Action.swift b/AdyenActions/Actions/Action.swift index fb6315f23e..766f7b6ea5 100644 --- a/AdyenActions/Actions/Action.swift +++ b/AdyenActions/Actions/Action.swift @@ -47,10 +47,8 @@ public enum Action: Decodable { let type = try container.decode(ActionType.self, forKey: .type) switch type { - case .redirect: + case .redirect, .nativeRedirect: self = try .redirect(RedirectAction(from: decoder)) - case .nativeRedirect: - self = try .redirect(NativeRedirectAction(from: decoder)) case .threeDS2Fingerprint: self = try .threeDS2Fingerprint(ThreeDS2FingerprintAction(from: decoder)) case .threeDS2Challenge: diff --git a/AdyenActions/Actions/NativeRedirectAction.swift b/AdyenActions/Actions/NativeRedirectAction.swift deleted file mode 100644 index e99508fb8d..0000000000 --- a/AdyenActions/Actions/NativeRedirectAction.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// 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 - -/// Describes an action in which the user is redirected to an app. -public class NativeRedirectAction: RedirectAction { - - /// Native redirect data. - public let nativeRedirectData: String? - - /// Initializes a native redirect action. - /// - /// - Parameters: - /// - url: The URL to which to redirect the user. - /// - paymentData: The server-generated payment data that should be submitted to the `/payments/details` endpoint. - /// - nativeRedirectData: Native redirect data. - public init(url: URL, paymentData: String?, nativeRedirectData: String? = nil) { - self.nativeRedirectData = nativeRedirectData - super.init(url: url, paymentData: paymentData) - } - - public required init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let url = try container.decode(URL.self, forKey: CodingKeys.url) - let paymentData = try container.decodeIfPresent(String.self, forKey: CodingKeys.paymentData) - self.nativeRedirectData = try container.decodeIfPresent(String.self, forKey: CodingKeys.nativeRedirectData) - super.init(url: url, paymentData: paymentData) - } - - // MARK: - CodingKeys - - private enum CodingKeys: CodingKey { - case url - case paymentData - case nativeRedirectData - } -} diff --git a/AdyenActions/Actions/RedirectAction.swift b/AdyenActions/Actions/RedirectAction.swift index 6ef69ee238..f4e5938b37 100644 --- a/AdyenActions/Actions/RedirectAction.swift +++ b/AdyenActions/Actions/RedirectAction.swift @@ -9,19 +9,55 @@ import Foundation /// Describes an action in which the user is redirected to a URL. public class RedirectAction: Decodable { + private enum Constants { + static let nativeRedirectType = "nativeRedirect" + } + /// The URL to which to redirect the user. public let url: URL /// The server-generated payment data that should be submitted to the `/payments/details` endpoint. public let paymentData: String? + /// Native redirect data. + public let nativeRedirectData: String? + + internal let isNaviteRedirect: Bool + /// Initializes a redirect action. /// /// - Parameters: /// - url: The URL to which to redirect the user. /// - paymentData: The server-generated payment data that should be submitted to the `/payments/details` endpoint. - public init(url: URL, paymentData: String?) { + /// - nativeRedirectData: Native redirect data. + public init( + url: URL, + paymentData: String?, + nativeRedirectData: String? = nil + ) { self.url = url self.paymentData = paymentData + self.nativeRedirectData = nativeRedirectData + self.isNaviteRedirect = nativeRedirectData != nil + + } + + public required init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.url = try container.decode(URL.self, forKey: .url) + self.paymentData = try container.decodeIfPresent(String.self, forKey: .paymentData) + self.nativeRedirectData = try container.decodeIfPresent(String.self, forKey: .nativeRedirectData) + + let redirectType = try container.decode(String.self, forKey: .type) + self.isNaviteRedirect = redirectType == Constants.nativeRedirectType + } + + // MARK: - Private + + private enum CodingKeys: CodingKey { + case url + case paymentData + case nativeRedirectData + case type } } diff --git a/AdyenActions/Components/Redirect/RedirectComponent.swift b/AdyenActions/Components/Redirect/RedirectComponent.swift index ef2eb55c13..dfc4e23a68 100644 --- a/AdyenActions/Components/Redirect/RedirectComponent.swift +++ b/AdyenActions/Components/Redirect/RedirectComponent.swift @@ -172,11 +172,10 @@ public final class RedirectComponent: ActionComponent { } private func didOpen(url returnURL: URL, _ action: RedirectAction) throws { - if let nativeRedirectAction = action as? NativeRedirectAction { - let redirectStateData = nativeRedirectAction.nativeRedirectData + if action.isNaviteRedirect { try handleNativeMobileRedirect( withReturnURL: returnURL, - redirectStateData: redirectStateData, + redirectStateData: action.nativeRedirectData, action ) } else { diff --git a/Tests/IntegrationTests/Card Tests/3DS2 Component/3DS2 Fingerprint Submitter/ThreeDS2FingerprintSubmitterTests.swift b/Tests/IntegrationTests/Card Tests/3DS2 Component/3DS2 Fingerprint Submitter/ThreeDS2FingerprintSubmitterTests.swift index f0acc3959c..231040aab5 100644 --- a/Tests/IntegrationTests/Card Tests/3DS2 Component/3DS2 Fingerprint Submitter/ThreeDS2FingerprintSubmitterTests.swift +++ b/Tests/IntegrationTests/Card Tests/3DS2 Component/3DS2 Fingerprint Submitter/ThreeDS2FingerprintSubmitterTests.swift @@ -26,7 +26,7 @@ class ThreeDS2FingerprintSubmitterTests: XCTestCase { let apiClient = APIClientMock() let sut = ThreeDS2FingerprintSubmitter(apiContext: Dummy.apiContext, apiClient: apiClient) - let mockedRedirectAction = RedirectAction(url: URL(string: "https://www.adyen.com")!, paymentData: "data") + let mockedRedirectAction = RedirectAction(url: URL(string: "https://www.adyen.com")!, paymentData: "data", nativeRedirectData: <#String?#>) let mockedAction = Action.redirect(mockedRedirectAction) let mockedResponse = Submit3DS2FingerprintResponse(result: .action(mockedAction)) apiClient.mockedResults = [.success(mockedResponse)] From d339c2873334b0eda0468b956adfb0946d9804b8 Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Mon, 28 Oct 2024 14:09:27 +0100 Subject: [PATCH 05/13] Update unit tests --- .../Redirect/RedirectComponentTests.swift | 24 +++++++++++++++---- .../ThreeDS2FingerprintSubmitterTests.swift | 2 +- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift b/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift index d4a4f09fe7..b6d7efd516 100644 --- a/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift +++ b/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift @@ -283,7 +283,11 @@ class RedirectComponentTests: XCTestCase { } delegate.onDidFail = { _, _ in XCTFail("Should not call onDidFail") } - let action = NativeRedirectAction(url: URL(string: "https://google.com")!, paymentData: nil, nativeRedirectData: "test_nativeRedirectData") + let action = RedirectAction( + url: URL(string: "https://google.com")!, + paymentData: nil, + nativeRedirectData: "test_nativeRedirectData" + ) sut.handle(action) XCTAssertTrue(RedirectComponent.applicationDidOpen(from: URL(string: "url://?queryParam=value")!)) @@ -311,7 +315,11 @@ class RedirectComponentTests: XCTestCase { XCTFail("Should not call onDidProvide") } - let action = NativeRedirectAction(url: URL(string: "https://google.com")!, paymentData: nil, nativeRedirectData: "test_nativeRedirectData") + let action = RedirectAction( + url: URL(string: "https://google.com")!, + paymentData: nil, + nativeRedirectData: "test_nativeRedirectData" + ) sut.handle(action) XCTAssertFalse(RedirectComponent.applicationDidOpen(from: URL(string: "url://")!)) @@ -343,7 +351,11 @@ class RedirectComponentTests: XCTestCase { redirectExpectation.fulfill() } - let action = NativeRedirectAction(url: URL(string: "https://google.com")!, paymentData: nil, nativeRedirectData: "test_nativeRedirectData") + let action = RedirectAction( + url: URL(string: "https://google.com")!, + paymentData: nil, + nativeRedirectData: "test_nativeRedirectData" + ) sut.handle(action) XCTAssertTrue(RedirectComponent.applicationDidOpen(from: URL(string: "url://?queryParam=value")!)) @@ -376,7 +388,11 @@ class RedirectComponentTests: XCTestCase { delegate.onDidFail = { _, _ in XCTFail("Should not call onDidFail") } // When - let action = NativeRedirectAction(url: URL(string: "https://google.com")!, paymentData: nil, nativeRedirectData: nil) + let action = RedirectAction( + url: URL(string: "https://google.com")!, + paymentData: nil, + nativeRedirectData: nil + ) sut.handle(action) // Then diff --git a/Tests/IntegrationTests/Card Tests/3DS2 Component/3DS2 Fingerprint Submitter/ThreeDS2FingerprintSubmitterTests.swift b/Tests/IntegrationTests/Card Tests/3DS2 Component/3DS2 Fingerprint Submitter/ThreeDS2FingerprintSubmitterTests.swift index 231040aab5..f0acc3959c 100644 --- a/Tests/IntegrationTests/Card Tests/3DS2 Component/3DS2 Fingerprint Submitter/ThreeDS2FingerprintSubmitterTests.swift +++ b/Tests/IntegrationTests/Card Tests/3DS2 Component/3DS2 Fingerprint Submitter/ThreeDS2FingerprintSubmitterTests.swift @@ -26,7 +26,7 @@ class ThreeDS2FingerprintSubmitterTests: XCTestCase { let apiClient = APIClientMock() let sut = ThreeDS2FingerprintSubmitter(apiContext: Dummy.apiContext, apiClient: apiClient) - let mockedRedirectAction = RedirectAction(url: URL(string: "https://www.adyen.com")!, paymentData: "data", nativeRedirectData: <#String?#>) + let mockedRedirectAction = RedirectAction(url: URL(string: "https://www.adyen.com")!, paymentData: "data") let mockedAction = Action.redirect(mockedRedirectAction) let mockedResponse = Submit3DS2FingerprintResponse(result: .action(mockedAction)) apiClient.mockedResults = [.success(mockedResponse)] From 1a3c809aeef2dabc59c406ce0b305c54237e004d Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Tue, 29 Oct 2024 12:10:49 +0100 Subject: [PATCH 06/13] Expose redirect type --- AdyenActions/Actions/RedirectAction.swift | 31 +++++++++++++------ .../Redirect/RedirectComponent.swift | 5 +-- .../Redirect/RedirectComponentTests.swift | 4 +++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/AdyenActions/Actions/RedirectAction.swift b/AdyenActions/Actions/RedirectAction.swift index f4e5938b37..604745eecb 100644 --- a/AdyenActions/Actions/RedirectAction.swift +++ b/AdyenActions/Actions/RedirectAction.swift @@ -9,8 +9,20 @@ import Foundation /// Describes an action in which the user is redirected to a URL. public class RedirectAction: Decodable { - private enum Constants { - static let nativeRedirectType = "nativeRedirect" + public enum RedirectType: Decodable { + case redirect + case nativeRedirect + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let type = try? container.decode(String.self) + switch type { + case "nativeRedirect": self = .nativeRedirect + case "redirect": self = .redirect + default: + self = .redirect + } + } } /// The URL to which to redirect the user. @@ -19,11 +31,12 @@ public class RedirectAction: Decodable { /// The server-generated payment data that should be submitted to the `/payments/details` endpoint. public let paymentData: String? + /// Redirect type + public let type: RedirectType + /// Native redirect data. public let nativeRedirectData: String? - internal let isNaviteRedirect: Bool - /// Initializes a redirect action. /// /// - Parameters: @@ -33,23 +46,21 @@ public class RedirectAction: Decodable { public init( url: URL, paymentData: String?, + type: RedirectType = .redirect, nativeRedirectData: String? = nil ) { self.url = url self.paymentData = paymentData + self.type = type self.nativeRedirectData = nativeRedirectData - self.isNaviteRedirect = nativeRedirectData != nil - } public required init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.url = try container.decode(URL.self, forKey: .url) self.paymentData = try container.decodeIfPresent(String.self, forKey: .paymentData) + self.type = try container.decode(RedirectType.self, forKey: .type) self.nativeRedirectData = try container.decodeIfPresent(String.self, forKey: .nativeRedirectData) - - let redirectType = try container.decode(String.self, forKey: .type) - self.isNaviteRedirect = redirectType == Constants.nativeRedirectType } // MARK: - Private @@ -57,7 +68,7 @@ public class RedirectAction: Decodable { private enum CodingKeys: CodingKey { case url case paymentData - case nativeRedirectData case type + case nativeRedirectData } } diff --git a/AdyenActions/Components/Redirect/RedirectComponent.swift b/AdyenActions/Components/Redirect/RedirectComponent.swift index dfc4e23a68..ff583de308 100644 --- a/AdyenActions/Components/Redirect/RedirectComponent.swift +++ b/AdyenActions/Components/Redirect/RedirectComponent.swift @@ -172,13 +172,14 @@ public final class RedirectComponent: ActionComponent { } private func didOpen(url returnURL: URL, _ action: RedirectAction) throws { - if action.isNaviteRedirect { + switch action.type { + case .nativeRedirect: try handleNativeMobileRedirect( withReturnURL: returnURL, redirectStateData: action.nativeRedirectData, action ) - } else { + case .redirect: try notifyDelegateDidProvide(redirectDetails: RedirectDetails(returnURL: returnURL), action) } } diff --git a/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift b/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift index b6d7efd516..e2eecce1db 100644 --- a/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift +++ b/Tests/IntegrationTests/Actions Tests/Redirect/RedirectComponentTests.swift @@ -286,6 +286,7 @@ class RedirectComponentTests: XCTestCase { let action = RedirectAction( url: URL(string: "https://google.com")!, paymentData: nil, + type: .nativeRedirect, nativeRedirectData: "test_nativeRedirectData" ) sut.handle(action) @@ -318,6 +319,7 @@ class RedirectComponentTests: XCTestCase { let action = RedirectAction( url: URL(string: "https://google.com")!, paymentData: nil, + type: .nativeRedirect, nativeRedirectData: "test_nativeRedirectData" ) sut.handle(action) @@ -354,6 +356,7 @@ class RedirectComponentTests: XCTestCase { let action = RedirectAction( url: URL(string: "https://google.com")!, paymentData: nil, + type: .nativeRedirect, nativeRedirectData: "test_nativeRedirectData" ) sut.handle(action) @@ -391,6 +394,7 @@ class RedirectComponentTests: XCTestCase { let action = RedirectAction( url: URL(string: "https://google.com")!, paymentData: nil, + type: .nativeRedirect, nativeRedirectData: nil ) sut.handle(action) From 5c64d5cbd473dc297b05d0d7428c7189436e976e Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Tue, 29 Oct 2024 13:15:28 +0100 Subject: [PATCH 07/13] Make RedirectAction value type --- AdyenActions/Actions/RedirectAction.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AdyenActions/Actions/RedirectAction.swift b/AdyenActions/Actions/RedirectAction.swift index 604745eecb..45d5875e61 100644 --- a/AdyenActions/Actions/RedirectAction.swift +++ b/AdyenActions/Actions/RedirectAction.swift @@ -7,7 +7,7 @@ import Foundation /// Describes an action in which the user is redirected to a URL. -public class RedirectAction: Decodable { +public struct RedirectAction: Decodable { public enum RedirectType: Decodable { case redirect @@ -55,7 +55,7 @@ public class RedirectAction: Decodable { self.nativeRedirectData = nativeRedirectData } - public required init(from decoder: any Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.url = try container.decode(URL.self, forKey: .url) self.paymentData = try container.decodeIfPresent(String.self, forKey: .paymentData) From e6a8a461d59193e4680ed7e52c17251d8e318989 Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Tue, 29 Oct 2024 13:21:19 +0100 Subject: [PATCH 08/13] Update documentation --- AdyenActions/Actions/RedirectAction.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/AdyenActions/Actions/RedirectAction.swift b/AdyenActions/Actions/RedirectAction.swift index 45d5875e61..29d1007c22 100644 --- a/AdyenActions/Actions/RedirectAction.swift +++ b/AdyenActions/Actions/RedirectAction.swift @@ -8,7 +8,8 @@ import Foundation /// Describes an action in which the user is redirected to a URL. public struct RedirectAction: Decodable { - + + /// The redirect flow type used by the `RedirectAction` object. public enum RedirectType: Decodable { case redirect case nativeRedirect @@ -31,7 +32,7 @@ public struct RedirectAction: Decodable { /// The server-generated payment data that should be submitted to the `/payments/details` endpoint. public let paymentData: String? - /// Redirect type + /// Redirect type. public let type: RedirectType /// Native redirect data. @@ -42,7 +43,8 @@ public struct RedirectAction: Decodable { /// - Parameters: /// - url: The URL to which to redirect the user. /// - paymentData: The server-generated payment data that should be submitted to the `/payments/details` endpoint. - /// - nativeRedirectData: Native redirect data. + /// - type: The redirect flow used by the action. Defaults to `redirect`. + /// - nativeRedirectData: Native redirect data. Defaults to `nil`. public init( url: URL, paymentData: String?, From ad894f88e4c853a7461ebcf1ef4748a9b76df5b4 Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Tue, 29 Oct 2024 14:22:59 +0100 Subject: [PATCH 09/13] Update documentation --- AdyenActions/Actions/RedirectAction.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AdyenActions/Actions/RedirectAction.swift b/AdyenActions/Actions/RedirectAction.swift index 29d1007c22..fe4695ed6b 100644 --- a/AdyenActions/Actions/RedirectAction.swift +++ b/AdyenActions/Actions/RedirectAction.swift @@ -9,7 +9,7 @@ import Foundation /// Describes an action in which the user is redirected to a URL. public struct RedirectAction: Decodable { - /// The redirect flow type used by the `RedirectAction` object. + /// Defines the type of redirect flow utilized by the `RedirectAction` object. public enum RedirectType: Decodable { case redirect case nativeRedirect From 68a0bb0006b354ad34f7ffff5d723078f81e6014 Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Thu, 31 Oct 2024 12:25:19 +0100 Subject: [PATCH 10/13] Refactor RedirectType decoding --- AdyenActions/Actions/RedirectAction.swift | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/AdyenActions/Actions/RedirectAction.swift b/AdyenActions/Actions/RedirectAction.swift index fe4695ed6b..3bba00e45d 100644 --- a/AdyenActions/Actions/RedirectAction.swift +++ b/AdyenActions/Actions/RedirectAction.swift @@ -10,19 +10,14 @@ import Foundation public struct RedirectAction: Decodable { /// Defines the type of redirect flow utilized by the `RedirectAction` object. - public enum RedirectType: Decodable { + public enum RedirectType: String, Decodable { case redirect case nativeRedirect public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() - let type = try? container.decode(String.self) - switch type { - case "nativeRedirect": self = .nativeRedirect - case "redirect": self = .redirect - default: - self = .redirect - } + let type = try container.decode(String.self) + self = RedirectType(rawValue: type) ?? .redirect } } From 8f36bd1af5851c4db4d92e1624463ffdb2e92007 Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Thu, 31 Oct 2024 14:28:39 +0100 Subject: [PATCH 11/13] Explicitly indicate decoding value --- AdyenActions/Actions/RedirectAction.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AdyenActions/Actions/RedirectAction.swift b/AdyenActions/Actions/RedirectAction.swift index 3bba00e45d..6d2559dd7e 100644 --- a/AdyenActions/Actions/RedirectAction.swift +++ b/AdyenActions/Actions/RedirectAction.swift @@ -11,8 +11,9 @@ public struct RedirectAction: Decodable { /// Defines the type of redirect flow utilized by the `RedirectAction` object. public enum RedirectType: String, Decodable { - case redirect - case nativeRedirect + // swiftlint:disable redundant_string_enum_value + case redirect = "redirect" + case nativeRedirect = "nativeRedirect" public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() From 63c13556f5e65af9e829d2e9a1f130ca19e4520f Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Fri, 1 Nov 2024 16:06:01 +0100 Subject: [PATCH 12/13] Make redirect type private --- AdyenActions/Actions/RedirectAction.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AdyenActions/Actions/RedirectAction.swift b/AdyenActions/Actions/RedirectAction.swift index 6d2559dd7e..54e5664aeb 100644 --- a/AdyenActions/Actions/RedirectAction.swift +++ b/AdyenActions/Actions/RedirectAction.swift @@ -28,8 +28,7 @@ public struct RedirectAction: Decodable { /// The server-generated payment data that should be submitted to the `/payments/details` endpoint. public let paymentData: String? - /// Redirect type. - public let type: RedirectType + private let type: RedirectType /// Native redirect data. public let nativeRedirectData: String? From 3d9f471d56a38c8273a64443bad40e4fc16736a7 Mon Sep 17 00:00:00 2001 From: Naufal Aros Date: Mon, 4 Nov 2024 11:34:13 +0100 Subject: [PATCH 13/13] Change redirect type to be internal --- AdyenActions/Actions/RedirectAction.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AdyenActions/Actions/RedirectAction.swift b/AdyenActions/Actions/RedirectAction.swift index 54e5664aeb..d8f7b22b51 100644 --- a/AdyenActions/Actions/RedirectAction.swift +++ b/AdyenActions/Actions/RedirectAction.swift @@ -28,7 +28,7 @@ public struct RedirectAction: Decodable { /// The server-generated payment data that should be submitted to the `/payments/details` endpoint. public let paymentData: String? - private let type: RedirectType + internal let type: RedirectType /// Native redirect data. public let nativeRedirectData: String?