diff --git a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Scenes/Cart/CartView.swift b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Scenes/Cart/CartView.swift index 151e6fae5..c3c2e9cf2 100644 --- a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Scenes/Cart/CartView.swift +++ b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Scenes/Cart/CartView.swift @@ -204,10 +204,13 @@ struct CartView: View { } private func handleCheckoutLink(_ link: CheckoutLink) -> CheckoutLinkAction { + print("[CheckoutKitSwiftDemo] Checkout link clicked: \(link.url)") switch windowOpenHandler { case .default: + print("[CheckoutKitSwiftDemo] Checkout link delegated to Checkout Kit: \(link.url)") return .open case .externalApp: + print("[CheckoutKitSwiftDemo] Checkout link handled by sample: \(link.url)") UIApplication.shared.open(link.url) return .handled } diff --git a/platforms/swift/Sources/ShopifyCheckoutKit/CheckoutWebView.swift b/platforms/swift/Sources/ShopifyCheckoutKit/CheckoutWebView.swift index a6afd0f64..0dd093bb2 100644 --- a/platforms/swift/Sources/ShopifyCheckoutKit/CheckoutWebView.swift +++ b/platforms/swift/Sources/ShopifyCheckoutKit/CheckoutWebView.swift @@ -860,11 +860,18 @@ extension CheckoutWebView: WKNavigationDelegate { // - Deep links on offsite payment sites // if CheckoutURL(from: url).isDeepLink() { - Task { @MainActor [externalURLHandler] in - if await externalURLHandler.open(url) { - OSLogger.shared.debug("Deep link intercepted: \(LogSafeURL.string(url)) - opened externally") - } else { - OSLogger.shared.error("Deep link rejected: \(LogSafeURL.string(url)). No installed app can open this scheme.") + Task { @MainActor [externalURLHandler, linkActionProvider] in + switch linkActionProvider?(CheckoutLink(url: url)) ?? .open { + case .handled: + OSLogger.shared.debug("Raw navigation link handled by host") + case .cancel: + OSLogger.shared.debug("Raw navigation link canceled by host") + case .open: + if await externalURLHandler.open(url) { + OSLogger.shared.debug("Raw navigation link opened with SDK default handler") + } else { + OSLogger.shared.error("Raw navigation link rejected by SDK default handler") + } } decisionHandler(.cancel) } diff --git a/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutWebViewTests.swift b/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutWebViewTests.swift index baaa07a9a..9731c1d73 100644 --- a/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutWebViewTests.swift +++ b/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutWebViewTests.swift @@ -126,6 +126,43 @@ class CheckoutWebViewTests: XCTestCase { XCTAssertEqual(handler.openedURL, link, "The deep link should be opened via the external URL handler exactly once") } + func testDeepLinkHandledByLinkActionProviderIsNotOpenedExternally() throws { + let link = try XCTUnwrap(URL(string: "myapp://checkout")) + let handler = MockExternalURLHandler(didOpen: true) + view.externalURLHandler = handler + var handledLink: CheckoutLink? + view.linkActionProvider = { checkoutLink in + handledLink = checkoutLink + return .handled + } + let received = expectation(description: "policy decided") + + view.webView(view, decidePolicyFor: MockExternalNavigationAction(url: link)) { policy in + XCTAssertEqual(policy, .cancel) + received.fulfill() + } + + wait(for: [received], timeout: 2.0) + XCTAssertEqual(handledLink?.url, link) + XCTAssertNil(handler.openedURL) + } + + func testDeepLinkCanceledByLinkActionProviderIsNotOpenedExternally() throws { + let link = try XCTUnwrap(URL(string: "myapp://checkout")) + let handler = MockExternalURLHandler(didOpen: true) + view.externalURLHandler = handler + view.linkActionProvider = { _ in .cancel } + let received = expectation(description: "policy decided") + + view.webView(view, decidePolicyFor: MockExternalNavigationAction(url: link)) { policy in + XCTAssertEqual(policy, .cancel) + received.fulfill() + } + + wait(for: [received], timeout: 2.0) + XCTAssertNil(handler.openedURL) + } + func testHTTPSubframeRequestIsAllowed() throws { let link = try XCTUnwrap(URL(string: "https://shopify1.shopify.com/checkouts/cn/123")) let received = expectation(description: "policy decided")