From 3be2735b0e5b0fb5c73242ddb3d097c7cd6df940 Mon Sep 17 00:00:00 2001 From: erenbesel Date: Thu, 18 Jun 2026 14:07:15 +0200 Subject: [PATCH 1/3] feature: Add URL handling to Checkout --- AdyenCheckout/Setup/Checkout.swift | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/AdyenCheckout/Setup/Checkout.swift b/AdyenCheckout/Setup/Checkout.swift index 991d28d21f..d1b193d2ec 100644 --- a/AdyenCheckout/Setup/Checkout.swift +++ b/AdyenCheckout/Setup/Checkout.swift @@ -71,6 +71,41 @@ public enum Checkout { provider: CheckoutProvider.default ) } + + /// Passes a URL to the SDK to resume an active redirect action after the shopper returns from a browser or external app. + /// + /// Call this from every entry point where your app receives incoming URLs: + /// + /// **UIKit - AppDelegate:** + /// ```swift + /// func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + /// Checkout.handleReturn(url: url) + /// return true + /// } + /// ``` + /// + /// **UIKit - SceneDelegate:** + /// ```swift + /// func scene(_ scene: UIScene, openURLContexts contexts: Set) { + /// guard let url = contexts.first?.url else { return } + /// Checkout.handleReturn(url: url) + /// } + /// ``` + /// + /// **SwiftUI:** + /// ```swift + /// ContentView() + /// .onOpenURL { url in Checkout.handleReturn(url: url) } + /// ``` + /// + /// It is safe to pass all incoming URLs; any URL not belonging to an active checkout redirect is ignored. + /// + /// - Parameter url: The URL received when the shopper returns to the app. + /// - Returns: `true` if the URL was handled by an active checkout redirect; `false` otherwise. + @discardableResult + public static func handleReturn(url: URL) -> Bool { + RedirectComponent.applicationDidOpen(from: url) + } } internal extension Checkout { From 979e611e45fab66ed5045425dcbcb386bbbfe373 Mon Sep 17 00:00:00 2001 From: erenbesel Date: Thu, 18 Jun 2026 14:21:43 +0200 Subject: [PATCH 2/3] Add conditional import for URL handling --- AdyenCheckout/Setup/Checkout.swift | 73 ++++++++++--------- .../AdyenCheckout/CheckoutTests.swift | 30 ++++++++ 2 files changed, 69 insertions(+), 34 deletions(-) diff --git a/AdyenCheckout/Setup/Checkout.swift b/AdyenCheckout/Setup/Checkout.swift index d1b193d2ec..2c9bbb7e90 100644 --- a/AdyenCheckout/Setup/Checkout.swift +++ b/AdyenCheckout/Setup/Checkout.swift @@ -5,6 +5,9 @@ // import Adyen +#if canImport(AdyenActions) + import AdyenActions +#endif #if canImport(AdyenSession) import AdyenSession #endif @@ -72,40 +75,42 @@ public enum Checkout { ) } - /// Passes a URL to the SDK to resume an active redirect action after the shopper returns from a browser or external app. - /// - /// Call this from every entry point where your app receives incoming URLs: - /// - /// **UIKit - AppDelegate:** - /// ```swift - /// func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { - /// Checkout.handleReturn(url: url) - /// return true - /// } - /// ``` - /// - /// **UIKit - SceneDelegate:** - /// ```swift - /// func scene(_ scene: UIScene, openURLContexts contexts: Set) { - /// guard let url = contexts.first?.url else { return } - /// Checkout.handleReturn(url: url) - /// } - /// ``` - /// - /// **SwiftUI:** - /// ```swift - /// ContentView() - /// .onOpenURL { url in Checkout.handleReturn(url: url) } - /// ``` - /// - /// It is safe to pass all incoming URLs; any URL not belonging to an active checkout redirect is ignored. - /// - /// - Parameter url: The URL received when the shopper returns to the app. - /// - Returns: `true` if the URL was handled by an active checkout redirect; `false` otherwise. - @discardableResult - public static func handleReturn(url: URL) -> Bool { - RedirectComponent.applicationDidOpen(from: url) - } + #if canImport(AdyenActions) + /// Passes a URL to the SDK to resume an active redirect action after the shopper returns from a browser or external app. + /// + /// Call this from every entry point where your app receives incoming URLs: + /// + /// **UIKit - AppDelegate:** + /// ```swift + /// func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + /// Checkout.handleReturn(url: url) + /// return true + /// } + /// ``` + /// + /// **UIKit - SceneDelegate:** + /// ```swift + /// func scene(_ scene: UIScene, openURLContexts contexts: Set) { + /// guard let url = contexts.first?.url else { return } + /// Checkout.handleReturn(url: url) + /// } + /// ``` + /// + /// **SwiftUI:** + /// ```swift + /// ContentView() + /// .onOpenURL { url in Checkout.handleReturn(url: url) } + /// ``` + /// + /// It is safe to pass all incoming URLs; any URL not belonging to an active checkout redirect is ignored. + /// + /// - Parameter url: The URL received when the shopper returns to the app. + /// - Returns: `true` if the URL was handled by an active checkout redirect; `false` otherwise. + @discardableResult + public static func handleReturn(url: URL) -> Bool { + RedirectComponent.applicationDidOpen(from: url) + } + #endif } internal extension Checkout { diff --git a/Tests/UnitTests/AdyenCheckout/CheckoutTests.swift b/Tests/UnitTests/AdyenCheckout/CheckoutTests.swift index 6eebfe98d5..682c42da3e 100644 --- a/Tests/UnitTests/AdyenCheckout/CheckoutTests.swift +++ b/Tests/UnitTests/AdyenCheckout/CheckoutTests.swift @@ -718,6 +718,36 @@ final class CheckoutTests: XCTestCase { XCTAssertFalse(onFailureCalled) } + // MARK: - handleReturn + + func test_handleReturn_withRegisteredHandler_returnsTrue() throws { + // Given + let url = try XCTUnwrap(URL(string: "adyen://redirect?payload=test")) + let handlerExpectation = expectation(description: "URL handler called") + RedirectListener.registerForURL { receivedURL in + XCTAssertEqual(receivedURL, url) + handlerExpectation.fulfill() + } + + // When + let result = Checkout.handleReturn(url: url) + + // Then + XCTAssertTrue(result) + waitForExpectations(timeout: 1) + } + + func test_handleReturn_withNoRegisteredHandler_returnsFalse() throws { + // Given + let url = try XCTUnwrap(URL(string: "adyen://redirect?payload=test")) + + // When + let result = Checkout.handleReturn(url: url) + + // Then + XCTAssertFalse(result) + } + private func makeSessionCheckoutCore( session: SessionProtocol, callbackStore: SessionCheckoutCallbackStore = SessionCheckoutCallbackStore() From 671faf654c328840b606b345a8d399f9b2beb35e Mon Sep 17 00:00:00 2001 From: erenbesel Date: Thu, 18 Jun 2026 16:33:39 +0200 Subject: [PATCH 3/3] docs: Update redirect return URL docs and retire legacy redirect folder --- docs/redirect/index.html | 3 --- docs/v6/README.md | 26 ++++++++++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) delete mode 100644 docs/redirect/index.html diff --git a/docs/redirect/index.html b/docs/redirect/index.html deleted file mode 100644 index cf503ba9a8..0000000000 --- a/docs/redirect/index.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/v6/README.md b/docs/v6/README.md index 22ad3a4d9a..e52aa09164 100644 --- a/docs/v6/README.md +++ b/docs/v6/README.md @@ -220,21 +220,31 @@ Use app-bundle `.strings` or `.xcstrings` files when you want to add a fully new ## Redirect return URLs -If you need to build redirect details from a return URL yourself, use the public `RedirectDetails` API: +Pass incoming URLs to the SDK so active redirect actions can resume after the shopper returns from a browser or external app. +**UIKit - AppDelegate:** ```swift func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { - guard let redirectDetails = try? RedirectDetails(returnURL: url) else { - return false - } - - let actionData = ActionComponentData(details: redirectDetails, paymentData: nil) - // Submit actionData to your /payments/details handling. + Checkout.handleReturn(url: url) return true } ``` -If your backend expects `paymentData`, use the value from your previous `/payments` response instead of `nil`. +**UIKit - SceneDelegate:** +```swift +func scene(_ scene: UIScene, openURLContexts contexts: Set) { + guard let url = contexts.first?.url else { return } + Checkout.handleReturn(url: url) +} +``` + +**SwiftUI:** +```swift +ContentView() + .onOpenURL { url in Checkout.handleReturn(url: url) } +``` + +It is safe to pass all incoming URLs; any URL not belonging to an active checkout redirect is ignored. ## Next steps