From 6030da76480c024f853c911903954d08ad1ab8a3 Mon Sep 17 00:00:00 2001 From: Victoria Park Date: Tue, 29 Oct 2024 13:14:03 -0700 Subject: [PATCH] Convert Delegation Pattern to Completion Handler (#293) * PayPal remove delgation pattern for completion handler function for checkout * lint error for paypal start * completion and analytics into notify functions * paypal vault completion, unit tests * remove PayPal delegates, references * Card vault to completion, unit tests * Card approve to completion, unit tests * docstrings for approve completion param * wrap paypal functions in async await * async await wrapper for CardClient functions * remove CardDelegate and references * fix error in notifyCheckoutFailure * Make error names and messages payPal caps consistent * changelog entires * Steven PR feedback: typo in Chagelog --- CHANGELOG.md | 23 ++ .../PayPalVaultViews/PayPalVaultView.swift | 15 +- .../PayPalWebViewModel.swift | 12 +- .../ViewModels/CardPaymentViewModel.swift | 21 +- Demo/Demo/ViewModels/CardVaultViewModel.swift | 42 +- .../ViewModels/PayPalVaultViewModel.swift | 43 +- PayPal.xcodeproj/project.pbxproj | 32 -- Sources/CardPayments/CardClient.swift | 126 ++++-- Sources/CardPayments/CardClientError.swift | 11 +- Sources/CardPayments/CardDelegate.swift | 35 -- Sources/CardPayments/CardVaultDelegate.swift | 35 -- .../PayPalVaultDelegate.swift | 25 -- .../PayPalWebCheckoutClient.swift | 104 +++-- .../PayPalWebCheckoutClientError.swift | 24 +- .../PayPalWebCheckoutDelegate.swift | 25 -- .../CardPaymentsTests/CardClient_Tests.swift | 375 ++++++++---------- .../Mocks/MockCardDelegate.swift | 45 --- .../Mocks/MockCardVaultDelegate.swift | 45 --- .../Mocks/MockPayPalVaultDelegate.swift | 37 -- .../Mocks/MockPayPalWebDelegate.swift | 21 - .../PayPalWebCheckoutClient_Tests.swift | 164 ++++---- 21 files changed, 522 insertions(+), 738 deletions(-) delete mode 100644 Sources/CardPayments/CardDelegate.swift delete mode 100644 Sources/CardPayments/CardVaultDelegate.swift delete mode 100644 Sources/PayPalWebPayments/PayPalVaultDelegate.swift delete mode 100644 Sources/PayPalWebPayments/PayPalWebCheckoutDelegate.swift delete mode 100644 UnitTests/CardPaymentsTests/Mocks/MockCardDelegate.swift delete mode 100644 UnitTests/CardPaymentsTests/Mocks/MockCardVaultDelegate.swift delete mode 100644 UnitTests/PayPalWebPaymentsTests/Mocks/MockPayPalVaultDelegate.swift delete mode 100644 UnitTests/PayPalWebPaymentsTests/Mocks/MockPayPalWebDelegate.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index d4b555f93..c8bea1fec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,32 @@ * Breaking Changes * PayPalNativePayments * Remove entire PayPalNativePayments module + * PayPalWebPayments + * Replace delegate pattern with completion handlers and Swift concurrency + * Remove `PayPalWebCheckoutDelegate` and `PayPalVaultDelegate` + * Remove `start(request:)` method that uses delegate callbacks + * Remove `vault(vaultRequest:)` method that uses delegate callbacks + * Add `start(request:completion(PayPalWebCheckoutResult?, Error?) -> Void)` to `PayPalWebCheckoutClient` + * Add `vault(vaultRequest:completion(PayPalVaultResult?, Error?) -> Void)` to `PayPalWebCheckoutClient` + * Add `start(request:) async throws -> PayPalCheckoutResult` + * Add `vault(vaultRequest:) async throws -> PayPalVaultResult` + * CardPayments + * Replace delegate pattern with completion handlers and Swift concurrency + * Remove `CardDelegate` and `CardVaultDelegate` + * Remove `approveOrder(request:)` method that uses delegate callbacks + * Remove `vault(vaultRequest:)` method that uses delegate callbacks + * Add `approveOrder(request:completion:(CardResult?, Error?) -> Void)` to `CardClient` + * Add `vault(request:completion:(CardVaultResult?, Error?) -> Void)` to `CardClient` + * Add `approveOrder(request:) async throws -> CardResult` + * Add `vault(vaultRequest:) async throws -> CardVaultResult` * PayPalWebPayments * Deprecate `PayPalVaultRequest(url:setupTokenID:)` * Add `PayPalVaultRequest(setupTokenID:)` + * Add new error types for cancellation handling: + * `PayPalWebCheckoutClientError.payPalCancellationError` + * `PayPalWebCheckoutClientError.payPalVaultCancellationError` +* CardPayments + * Add new error type `CardClientError.threeDSecureCancellation` for handling 3DS cancellation ## 1.4.0 (2024-07-09) * PayPalNativePayments (DEPRECATED) diff --git a/Demo/Demo/SwiftUIComponents/PayPalVaultViews/PayPalVaultView.swift b/Demo/Demo/SwiftUIComponents/PayPalVaultViews/PayPalVaultView.swift index e1954dc55..5d0de7e71 100644 --- a/Demo/Demo/SwiftUIComponents/PayPalVaultViews/PayPalVaultView.swift +++ b/Demo/Demo/SwiftUIComponents/PayPalVaultViews/PayPalVaultView.swift @@ -15,13 +15,18 @@ struct PayPalVaultView: View { ) SetupTokenResultView(vaultViewModel: paypalVaultViewModel) if let setupTokenID = paypalVaultViewModel.state.setupToken?.id { - Button("Vault PayPal") { - Task { - await paypalVaultViewModel.vault(setupTokenID: setupTokenID) + ZStack { + Button("Vault PayPal") { + Task { + await paypalVaultViewModel.vault(setupTokenID: setupTokenID) + } + } + .buttonStyle(RoundedBlueButtonStyle()) + .padding() + if case .loading = paypalVaultViewModel.state.paypalVaultTokenResponse { + CircularProgressView() } } - .buttonStyle(RoundedBlueButtonStyle()) - .padding() } PayPalVaultResultView(viewModel: paypalVaultViewModel) if let paypalVaultResult = paypalVaultViewModel.state.paypalVaultToken { diff --git a/Demo/Demo/SwiftUIComponents/PayPalWebPayments/PayPalWebViewModel.swift b/Demo/Demo/SwiftUIComponents/PayPalWebPayments/PayPalWebViewModel.swift index 5bd9bfad7..051b8abd9 100644 --- a/Demo/Demo/SwiftUIComponents/PayPalWebPayments/PayPalWebViewModel.swift +++ b/Demo/Demo/SwiftUIComponents/PayPalWebPayments/PayPalWebViewModel.swift @@ -3,7 +3,7 @@ import CorePayments import PayPalWebPayments import FraudProtection -class PayPalWebViewModel: ObservableObject, PayPalWebCheckoutDelegate { +class PayPalWebViewModel: ObservableObject { @Published var state: CurrentState = .idle @Published var intent: Intent = .authorize @@ -63,7 +63,6 @@ class PayPalWebViewModel: ObservableObject, PayPalWebCheckoutDelegate { Task { do { payPalWebCheckoutClient = try await getPayPalClient() - payPalWebCheckoutClient?.delegate = self guard let payPalWebCheckoutClient else { print("Error initializing PayPalWebCheckoutClient") return @@ -71,7 +70,14 @@ class PayPalWebViewModel: ObservableObject, PayPalWebCheckoutDelegate { if let orderID { let payPalRequest = PayPalWebCheckoutRequest(orderID: orderID, fundingSource: funding) - payPalWebCheckoutClient.start(request: payPalRequest) + payPalWebCheckoutClient.start(request: payPalRequest) { result, error in + if let error { + self.updateState(.error(message: error.localizedDescription)) + } else { + self.updateState(.success) + self.checkoutResult = result + } + } } updateState(.success) } catch { diff --git a/Demo/Demo/ViewModels/CardPaymentViewModel.swift b/Demo/Demo/ViewModels/CardPaymentViewModel.swift index 7f6ab7ca7..31f5f1e09 100644 --- a/Demo/Demo/ViewModels/CardPaymentViewModel.swift +++ b/Demo/Demo/ViewModels/CardPaymentViewModel.swift @@ -3,7 +3,7 @@ import CardPayments import CorePayments import FraudProtection -class CardPaymentViewModel: ObservableObject, CardDelegate { +class CardPaymentViewModel: ObservableObject { @Published var state = CardPaymentState() private var payPalDataCollector: PayPalDataCollector? @@ -117,11 +117,22 @@ class CardPaymentViewModel: ObservableObject, CardDelegate { let config = try await configManager.getCoreConfig() cardClient = CardClient(config: config) payPalDataCollector = PayPalDataCollector(config: config) - cardClient?.delegate = self let cardRequest = CardRequest(orderID: orderID, card: card, sca: sca) - cardClient?.approveOrder(request: cardRequest) + cardClient?.approveOrder(request: cardRequest) { result, error in + if let error { + self.setUpdateSetupTokenFailureResult(vaultError: error) + } else if let result { + self.approveResultSuccessResult( + approveResult: CardPaymentState.CardResult( + id: result.orderID, + status: result.status, + didAttemptThreeDSecureAuthentication: result.didAttemptThreeDSecureAuthentication + ) + ) + } + } } catch { - self.state.approveResultResponse = .error(message: error.localizedDescription) + setUpdateSetupTokenFailureResult(vaultError: error) print("failed in checkout with card. \(error.localizedDescription)") } } @@ -134,7 +145,7 @@ class CardPaymentViewModel: ObservableObject, CardDelegate { } } - func setUpdateSetupTokenFailureResult(vaultError: CorePayments.CoreSDKError) { + func setUpdateSetupTokenFailureResult(vaultError: Error) { DispatchQueue.main.async { self.state.approveResultResponse = .error(message: vaultError.localizedDescription) } diff --git a/Demo/Demo/ViewModels/CardVaultViewModel.swift b/Demo/Demo/ViewModels/CardVaultViewModel.swift index 8b5f06f6a..5a50f8cb3 100644 --- a/Demo/Demo/ViewModels/CardVaultViewModel.swift +++ b/Demo/Demo/ViewModels/CardVaultViewModel.swift @@ -2,7 +2,7 @@ import Foundation import CardPayments import CorePayments -class CardVaultViewModel: VaultViewModel, CardVaultDelegate { +class CardVaultViewModel: VaultViewModel { let configManager = CoreConfigManager(domain: "Card Vault") @@ -13,11 +13,16 @@ class CardVaultViewModel: VaultViewModel, CardVaultDelegate { do { let config = try await configManager.getCoreConfig() let cardClient = CardClient(config: config) - cardClient.vaultDelegate = self let cardVaultRequest = CardVaultRequest(card: card, setupTokenID: setupToken) - cardClient.vault(cardVaultRequest) + cardClient.vault(cardVaultRequest) { result, error in + if let result { + self.setUpdateSetupTokenResult(vaultResult: result, vaultError: nil) + } else if let error { + self.setUpdateSetupTokenResult(vaultResult: nil, vaultError: error) + } + } } catch { - self.state.updateSetupTokenResponse = .error(message: error.localizedDescription) + self.setUpdateSetupTokenResult(vaultResult: nil, vaultError: error) print("failed in updating setup token. \(error.localizedDescription)") } } @@ -31,7 +36,7 @@ class CardVaultViewModel: VaultViewModel, CardVaultDelegate { return enabled } - func setUpdateSetupTokenResult(vaultResult: CardVaultResult? = nil, vaultError: CoreSDKError? = nil) { + func setUpdateSetupTokenResult(vaultResult: CardVaultResult? = nil, vaultError: Error? = nil) { DispatchQueue.main.async { if let vaultResult { self.state.updateSetupTokenResponse = .loaded( @@ -46,31 +51,4 @@ class CardVaultViewModel: VaultViewModel, CardVaultDelegate { } } } - - // MARK: - CardVault Delegate - - func card(_ cardClient: CardPayments.CardClient, didFinishWithVaultResult vaultResult: CardPayments.CardVaultResult) { - print("vaultResult: \(vaultResult)") - setUpdateSetupTokenResult(vaultResult: vaultResult) - } - - func card(_ cardClient: CardPayments.CardClient, didFinishWithVaultError vaultError: CorePayments.CoreSDKError) { - print("error: \(vaultError.errorDescription ?? "")") - setUpdateSetupTokenResult(vaultError: vaultError) - } - - func cardThreeDSecureDidCancel(_ cardClient: CardClient) { - DispatchQueue.main.async { - self.state.updateSetupTokenResponse = .idle - self.state.updateSetupToken = nil - } - } - - func cardThreeDSecureWillLaunch(_ cardClient: CardPayments.CardClient) { - print("About to launch 3DS") - } - - func cardThreeDSecureDidFinish(_ cardClient: CardPayments.CardClient) { - print("Finished 3DS") - } } diff --git a/Demo/Demo/ViewModels/PayPalVaultViewModel.swift b/Demo/Demo/ViewModels/PayPalVaultViewModel.swift index 61cfccfcc..fef77272c 100644 --- a/Demo/Demo/ViewModels/PayPalVaultViewModel.swift +++ b/Demo/Demo/ViewModels/PayPalVaultViewModel.swift @@ -2,7 +2,7 @@ import UIKit import PayPalWebPayments import CorePayments -class PayPalVaultViewModel: VaultViewModel, PayPalVaultDelegate { +class PayPalVaultViewModel: VaultViewModel { let configManager = CoreConfigManager(domain: "PayPal Vault") @@ -13,36 +13,23 @@ class PayPalVaultViewModel: VaultViewModel, PayPalVaultDelegate { do { let config = try await configManager.getCoreConfig() let paypalClient = PayPalWebCheckoutClient(config: config) - paypalClient.vaultDelegate = self let vaultRequest = PayPalVaultRequest(setupTokenID: setupTokenID) - paypalClient.vault(vaultRequest) + paypalClient.vault(vaultRequest) { result, error in + if let error { + DispatchQueue.main.async { + self.state.paypalVaultTokenResponse = .error(message: error.localizedDescription) + } + } else if let result { + DispatchQueue.main.async { + self.state.paypalVaultTokenResponse = .loaded(result) + } + } + } } catch { print("Error in vaulting PayPal Payment") + DispatchQueue.main.async { + self.state.paypalVaultTokenResponse = .error(message: error.localizedDescription) + } } } - - // MARK: - PayPalVault Delegate - - func paypal( - _ paypalWebClient: PayPalWebPayments.PayPalWebCheckoutClient, - didFinishWithVaultResult paypalVaultResult: PayPalVaultResult - ) { - DispatchQueue.main.async { - self.state.paypalVaultTokenResponse = .loaded(paypalVaultResult) - } - } - - func paypal( - _ paypalWebClient: PayPalWebPayments.PayPalWebCheckoutClient, - didFinishWithVaultError vaultError: CorePayments.CoreSDKError - ) { - - DispatchQueue.main.async { - self.state.paypalVaultTokenResponse = .error(message: vaultError.localizedDescription) - } - } - - func paypalDidCancel(_ payPalWebClient: PayPalWebCheckoutClient) { - print("PayPal Checkout Canceled") - } } diff --git a/PayPal.xcodeproj/project.pbxproj b/PayPal.xcodeproj/project.pbxproj index 5862f42c2..23f599d2a 100644 --- a/PayPal.xcodeproj/project.pbxproj +++ b/PayPal.xcodeproj/project.pbxproj @@ -13,12 +13,8 @@ 065A4DBF26FCDA5B0007014A /* CardClient_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065A4DBE26FCDA5B0007014A /* CardClient_Tests.swift */; }; 06CE009926F3D1660000CC46 /* CoreConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CE009826F3D1660000CC46 /* CoreConfig.swift */; }; 06CE009B26F3D5A40000CC46 /* CardClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CE009A26F3D5A40000CC46 /* CardClient.swift */; }; - 3B109B3C2A85CC6200D8135F /* MockCardVaultDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B109B3A2A85B54800D8135F /* MockCardVaultDelegate.swift */; }; - 3B22E8B62A840ECF00962E34 /* CardVaultDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B22E8B52A840ECF00962E34 /* CardVaultDelegate.swift */; }; 3B22E8B82A841AEA00962E34 /* CardVaultResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B22E8B72A841AEA00962E34 /* CardVaultResult.swift */; }; 3B29C3972B9148F70077741D /* PayPalVaultRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B29C3962B9148F70077741D /* PayPalVaultRequest.swift */; }; - 3B3C51122B20C962009125FE /* PayPalVaultDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3C51112B20C962009125FE /* PayPalVaultDelegate.swift */; }; - 3B3C51182B2221C9009125FE /* MockPayPalVaultDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3C51172B2221C9009125FE /* MockPayPalVaultDelegate.swift */; }; 3B3C511E2B2395B5009125FE /* PayPalVaultResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3C511D2B2395B5009125FE /* PayPalVaultResult.swift */; }; 3B783DC32B7A69C2004623DB /* FakeUpdateSetupTokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B783DC22B7A69C2004623DB /* FakeUpdateSetupTokenResponse.swift */; }; 3B79E4F72A8503CA00C01D06 /* UpdateVaultVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B79E4F62A8503C900C01D06 /* UpdateVaultVariables.swift */; }; @@ -83,7 +79,6 @@ BC9C18DA27D27CE40019B541 /* MockMagnesSDKResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC9C18D927D27CE40019B541 /* MockMagnesSDKResult.swift */; }; BC9C18DE27D2A1610019B541 /* DeviceInspector_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC9C18DD27D2A1610019B541 /* DeviceInspector_Tests.swift */; }; BCD5C47E27E9200800B074D5 /* CorePayments.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 80B9F85126B8750000D67843 /* CorePayments.framework */; platformFilter = ios; }; - BCE8A7F327EA531C00AC301B /* MockPayPalWebDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE8A7F227EA531C00AC301B /* MockPayPalWebDelegate.swift */; }; BCE8A7F927EA555800AC301B /* PayPalWebCheckoutClient_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE8A7F827EA555800AC301B /* PayPalWebCheckoutClient_Tests.swift */; }; BCE8A7FB27EA5B1D00AC301B /* Environment+PayPalWebCheckout_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE8A7FA27EA5B1D00AC301B /* Environment+PayPalWebCheckout_Tests.swift */; }; BCF735DB27D1583400A52E03 /* TestShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 80E743F8270E40CE00BACECA /* TestShared.framework */; platformFilter = ios; }; @@ -110,7 +105,6 @@ BE4F785327EB656400FF4C0E /* Environment+PayPalWebCheckout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE4F784327EB629100FF4C0E /* Environment+PayPalWebCheckout.swift */; }; BE4F785427EB656400FF4C0E /* PayPalWebCheckoutClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE4F784627EB629100FF4C0E /* PayPalWebCheckoutClient.swift */; }; BE4F785527EB656400FF4C0E /* PayPalWebCheckoutClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE4F784427EB629100FF4C0E /* PayPalWebCheckoutClientError.swift */; }; - BE4F785627EB656400FF4C0E /* PayPalWebCheckoutDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE4F784927EB629100FF4C0E /* PayPalWebCheckoutDelegate.swift */; }; BE4F785727EB656400FF4C0E /* PayPalWebCheckoutRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE4F784527EB629100FF4C0E /* PayPalWebCheckoutRequest.swift */; }; BE4F785827EB656400FF4C0E /* PayPalWebCheckoutResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE4F784727EB629100FF4C0E /* PayPalWebCheckoutResult.swift */; }; BEA100E726EF9EDA0036A6A5 /* NetworkingClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEA100E626EF9EDA0036A6A5 /* NetworkingClient.swift */; }; @@ -119,7 +113,6 @@ BEA100F026EFA7C20036A6A5 /* NetworkingClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEA100EF26EFA7C20036A6A5 /* NetworkingClientError.swift */; }; BEA100F226EFA7DE0036A6A5 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEA100F126EFA7DE0036A6A5 /* Environment.swift */; }; CB16E6D8285B7A2B00FD6F52 /* FakeConfirmPaymentResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB16E6D7285B7A2B00FD6F52 /* FakeConfirmPaymentResponse.swift */; }; - CB16E6DA285B7B7300FD6F52 /* MockCardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB16E6D9285B7B7300FD6F52 /* MockCardDelegate.swift */; }; CB1A47F22820AFED00BD8184 /* PayPalPayLaterButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1A47F12820AFED00BD8184 /* PayPalPayLaterButton.swift */; }; CB1A47F42820BA5D00BD8184 /* PaymentButtonEdges.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1A47F32820BA5D00BD8184 /* PaymentButtonEdges.swift */; }; CB1A47F62820BAA600BD8184 /* PaymentButtonLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1A47F52820BAA600BD8184 /* PaymentButtonLabel.swift */; }; @@ -130,7 +123,6 @@ CB22C018291049500097E592 /* PayPalPayLaterButton_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB22C017291049500097E592 /* PayPalPayLaterButton_Tests.swift */; }; CB4BE27D2847AF6F00EA2DD1 /* SCA.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB4BE27C2847AF6F00EA2DD1 /* SCA.swift */; }; CB4BE27E2847EA7D00EA2DD1 /* WebAuthenticationSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE4F784827EB629100FF4C0E /* WebAuthenticationSession.swift */; }; - CB4BE2802847F01000EA2DD1 /* CardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB4BE27F2847F01000EA2DD1 /* CardDelegate.swift */; }; CB4BE28A28512A9800EA2DD1 /* MockQuededURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB4BE28928512A9800EA2DD1 /* MockQuededURLSession.swift */; }; CB4BE28B2851423900EA2DD1 /* MockWebAuthenticationSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE8A7F627EA54A000AC301B /* MockWebAuthenticationSession.swift */; }; CB4BE28C2851427600EA2DD1 /* MockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE8A7F427EA544000AC301B /* MockViewController.swift */; }; @@ -188,12 +180,8 @@ 06CE009A26F3D5A40000CC46 /* CardClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardClient.swift; sourceTree = ""; }; 06CE009F26F3DF100000CC46 /* ConfirmPaymentSourceRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmPaymentSourceRequest.swift; sourceTree = ""; }; 06CE00A226F3E32A0000CC46 /* ConfirmPaymentSourceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmPaymentSourceResponse.swift; sourceTree = ""; }; - 3B109B3A2A85B54800D8135F /* MockCardVaultDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCardVaultDelegate.swift; sourceTree = ""; }; - 3B22E8B52A840ECF00962E34 /* CardVaultDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardVaultDelegate.swift; sourceTree = ""; }; 3B22E8B72A841AEA00962E34 /* CardVaultResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardVaultResult.swift; sourceTree = ""; }; 3B29C3962B9148F70077741D /* PayPalVaultRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalVaultRequest.swift; sourceTree = ""; }; - 3B3C51112B20C962009125FE /* PayPalVaultDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalVaultDelegate.swift; sourceTree = ""; }; - 3B3C51172B2221C9009125FE /* MockPayPalVaultDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPayPalVaultDelegate.swift; sourceTree = ""; }; 3B3C511D2B2395B5009125FE /* PayPalVaultResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalVaultResult.swift; sourceTree = ""; }; 3B783DC22B7A69C2004623DB /* FakeUpdateSetupTokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeUpdateSetupTokenResponse.swift; sourceTree = ""; }; 3B79E4F62A8503C900C01D06 /* UpdateVaultVariables.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateVaultVariables.swift; sourceTree = ""; }; @@ -255,7 +243,6 @@ BC9C18DD27D2A1610019B541 /* DeviceInspector_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInspector_Tests.swift; sourceTree = ""; }; BCD5C48527E9200800B074D5 /* PayPalWebPayments.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PayPalWebPayments.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BCD5C49427E9201400B074D5 /* PayPalWebCheckoutTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PayPalWebCheckoutTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - BCE8A7F227EA531C00AC301B /* MockPayPalWebDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPayPalWebDelegate.swift; sourceTree = ""; }; BCE8A7F427EA544000AC301B /* MockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockViewController.swift; sourceTree = ""; }; BCE8A7F627EA54A000AC301B /* MockWebAuthenticationSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockWebAuthenticationSession.swift; sourceTree = ""; }; BCE8A7F827EA555800AC301B /* PayPalWebCheckoutClient_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalWebCheckoutClient_Tests.swift; sourceTree = ""; }; @@ -283,7 +270,6 @@ BE4F784627EB629100FF4C0E /* PayPalWebCheckoutClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayPalWebCheckoutClient.swift; sourceTree = ""; }; BE4F784727EB629100FF4C0E /* PayPalWebCheckoutResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayPalWebCheckoutResult.swift; sourceTree = ""; }; BE4F784827EB629100FF4C0E /* WebAuthenticationSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebAuthenticationSession.swift; sourceTree = ""; }; - BE4F784927EB629100FF4C0E /* PayPalWebCheckoutDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayPalWebCheckoutDelegate.swift; sourceTree = ""; }; BE9F36DE274859A000AFC7DA /* PaymentButtonColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentButtonColor.swift; sourceTree = ""; }; BE9F36E3275520E700AFC7DA /* PayPalCreditButton_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalCreditButton_Tests.swift; sourceTree = ""; }; BEA100E626EF9EDA0036A6A5 /* NetworkingClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkingClient.swift; sourceTree = ""; }; @@ -294,7 +280,6 @@ BEDB7FE32788AB8E00CEA554 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; BEF3FF1627AC5DF3006B4B69 /* Coordinator_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator_Tests.swift; sourceTree = ""; }; CB16E6D7285B7A2B00FD6F52 /* FakeConfirmPaymentResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeConfirmPaymentResponse.swift; sourceTree = ""; }; - CB16E6D9285B7B7300FD6F52 /* MockCardDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCardDelegate.swift; sourceTree = ""; }; CB1A47F12820AFED00BD8184 /* PayPalPayLaterButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalPayLaterButton.swift; sourceTree = ""; }; CB1A47F32820BA5D00BD8184 /* PaymentButtonEdges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentButtonEdges.swift; sourceTree = ""; }; CB1A47F52820BAA600BD8184 /* PaymentButtonLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentButtonLabel.swift; sourceTree = ""; }; @@ -304,7 +289,6 @@ CB1A48042822BCED00BD8184 /* PaymentButton+ImageAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaymentButton+ImageAsset.swift"; sourceTree = ""; }; CB22C017291049500097E592 /* PayPalPayLaterButton_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalPayLaterButton_Tests.swift; sourceTree = ""; }; CB4BE27C2847AF6F00EA2DD1 /* SCA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SCA.swift; sourceTree = ""; }; - CB4BE27F2847F01000EA2DD1 /* CardDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardDelegate.swift; sourceTree = ""; }; CB4BE285284802C200EA2DD1 /* PaymentSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSource.swift; sourceTree = ""; }; CB4BE28928512A9800EA2DD1 /* MockQuededURLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockQuededURLSession.swift; sourceTree = ""; }; CB51FF7127FCB947001A97F5 /* PayPalWebCheckoutFundingSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalWebCheckoutFundingSource.swift; sourceTree = ""; }; @@ -488,8 +472,6 @@ isa = PBXGroup; children = ( CB16E6D7285B7A2B00FD6F52 /* FakeConfirmPaymentResponse.swift */, - CB16E6D9285B7B7300FD6F52 /* MockCardDelegate.swift */, - 3B109B3A2A85B54800D8135F /* MockCardVaultDelegate.swift */, 808E3EDC2A981F240017FE46 /* MockCheckoutOrdersAPI.swift */, 808E3EDE2A981F390017FE46 /* MockVaultPaymentTokensAPI.swift */, 3B783DC22B7A69C2004623DB /* FakeUpdateSetupTokenResponse.swift */, @@ -554,8 +536,6 @@ children = ( 06CE009A26F3D5A40000CC46 /* CardClient.swift */, 80DCC59D2719DB6F00EC7C5A /* CardClientError.swift */, - CB4BE27F2847F01000EA2DD1 /* CardDelegate.swift */, - 3B22E8B52A840ECF00962E34 /* CardVaultDelegate.swift */, 80DBC9D829C336D500462539 /* APIRequests */, 065A4DBD26FCDA270007014A /* Models */, 3BE738622B9A482800598F05 /* PrivacyInfo.xcprivacy */, @@ -589,8 +569,6 @@ BCE8A7F127EA52EB00AC301B /* Mocks */ = { isa = PBXGroup; children = ( - BCE8A7F227EA531C00AC301B /* MockPayPalWebDelegate.swift */, - 3B3C51172B2221C9009125FE /* MockPayPalVaultDelegate.swift */, ); path = Mocks; sourceTree = ""; @@ -662,11 +640,9 @@ BE4F784327EB629100FF4C0E /* Environment+PayPalWebCheckout.swift */, BE4F784627EB629100FF4C0E /* PayPalWebCheckoutClient.swift */, BE4F784427EB629100FF4C0E /* PayPalWebCheckoutClientError.swift */, - BE4F784927EB629100FF4C0E /* PayPalWebCheckoutDelegate.swift */, BE4F784527EB629100FF4C0E /* PayPalWebCheckoutRequest.swift */, BE4F784727EB629100FF4C0E /* PayPalWebCheckoutResult.swift */, CB51FF7127FCB947001A97F5 /* PayPalWebCheckoutFundingSource.swift */, - 3B3C51112B20C962009125FE /* PayPalVaultDelegate.swift */, 3B3C511D2B2395B5009125FE /* PayPalVaultResult.swift */, 3B29C3962B9148F70077741D /* PayPalVaultRequest.swift */, 3BE738662B9A593100598F05 /* PrivacyInfo.xcprivacy */, @@ -1229,13 +1205,11 @@ 8048D28C270B9DE00072214A /* ConfirmPaymentSourceResponse.swift in Sources */, 3D1763A22720722A00652E1C /* CardResult.swift in Sources */, BC0A82A5270B9533006E9A21 /* ConfirmPaymentSourceRequest.swift in Sources */, - CB4BE2802847F01000EA2DD1 /* CardDelegate.swift in Sources */, 3BD82DBB2A835AF900CBE764 /* UpdateSetupTokenResponse.swift in Sources */, 80E8DAE126B8784600FAFC3F /* Card.swift in Sources */, 3B22E8B82A841AEA00962E34 /* CardVaultResult.swift in Sources */, CBC16DD529E99B4600307117 /* PaymentSource.swift in Sources */, 80DCC59E2719DB6F00EC7C5A /* CardClientError.swift in Sources */, - 3B22E8B62A840ECF00962E34 /* CardVaultDelegate.swift in Sources */, 3BDB34942A80CE6E008100D7 /* CardVaultRequest.swift in Sources */, 80B8B2FC2A8EBBFD00AB60CD /* VaultPaymentTokensAPI.swift in Sources */, CB4BE27D2847AF6F00EA2DD1 /* SCA.swift in Sources */, @@ -1250,10 +1224,8 @@ files = ( 065A4DBF26FCDA5B0007014A /* CardClient_Tests.swift in Sources */, 8008D2052A9E54FF0003CAF4 /* CheckoutOrdersAPI_Tests.swift in Sources */, - 3B109B3C2A85CC6200D8135F /* MockCardVaultDelegate.swift in Sources */, 80B27AF12A9E9EE60008EA45 /* VaultPaymentTokensAPI_Tests.swift in Sources */, CB16E6D8285B7A2B00FD6F52 /* FakeConfirmPaymentResponse.swift in Sources */, - CB16E6DA285B7B7300FD6F52 /* MockCardDelegate.swift in Sources */, 3B783DC32B7A69C2004623DB /* FakeUpdateSetupTokenResponse.swift in Sources */, BC0A82A6270B9954006E9A21 /* ConfirmPaymentSourceRequest_Tests.swift in Sources */, 808E3EDD2A981F240017FE46 /* MockCheckoutOrdersAPI.swift in Sources */, @@ -1269,9 +1241,7 @@ 3B3C511E2B2395B5009125FE /* PayPalVaultResult.swift in Sources */, BE4F785327EB656400FF4C0E /* Environment+PayPalWebCheckout.swift in Sources */, BE4F785427EB656400FF4C0E /* PayPalWebCheckoutClient.swift in Sources */, - 3B3C51122B20C962009125FE /* PayPalVaultDelegate.swift in Sources */, BE4F785527EB656400FF4C0E /* PayPalWebCheckoutClientError.swift in Sources */, - BE4F785627EB656400FF4C0E /* PayPalWebCheckoutDelegate.swift in Sources */, BE4F785727EB656400FF4C0E /* PayPalWebCheckoutRequest.swift in Sources */, BE4F785827EB656400FF4C0E /* PayPalWebCheckoutResult.swift in Sources */, CB51FF7227FCB947001A97F5 /* PayPalWebCheckoutFundingSource.swift in Sources */, @@ -1282,10 +1252,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 0; files = ( - 3B3C51182B2221C9009125FE /* MockPayPalVaultDelegate.swift in Sources */, BCE8A7FB27EA5B1D00AC301B /* Environment+PayPalWebCheckout_Tests.swift in Sources */, BCE8A7F927EA555800AC301B /* PayPalWebCheckoutClient_Tests.swift in Sources */, - BCE8A7F327EA531C00AC301B /* MockPayPalWebDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/CardPayments/CardClient.swift b/Sources/CardPayments/CardClient.swift index 8c77236d4..ab042b1d5 100644 --- a/Sources/CardPayments/CardClient.swift +++ b/Sources/CardPayments/CardClient.swift @@ -5,9 +5,6 @@ import CorePayments #endif public class CardClient: NSObject { - - public weak var delegate: CardDelegate? - public weak var vaultDelegate: CardVaultDelegate? private let checkoutOrdersAPI: CheckoutOrdersAPI private let vaultAPI: VaultPaymentTokensAPI @@ -43,7 +40,10 @@ public class CardClient: NSObject { /// If `didAttempt3DSecureVerification` is `true`, check verification status with `/v3/vault/setup-token/{id}` in your server. /// - Parameters: /// - vaultRequest: The request containing setupTokenID and card - public func vault(_ vaultRequest: CardVaultRequest) { + /// - completion: A completion block that is invoked when the request is completed. If the request succeeds, + /// a `CardVaultResult` with `setupTokenID` and `status` are returned and `error` will be `nil`; + /// if it fails, `CardVaultResult will be `nil` and `error` will describe the failure + public func vault(_ vaultRequest: CardVaultRequest, completion: @escaping (CardVaultResult?, Error?) -> Void) { analyticsService = AnalyticsService(coreConfig: config, setupToken: vaultRequest.setupTokenID) analyticsService?.sendEvent("card-payments:vault-wo-purchase:started") Task { @@ -53,29 +53,51 @@ public class CardClient: NSObject { if result.status == "PAYER_ACTION_REQUIRED", let urlString = result.links.first(where: { $0.rel == "approve" })?.href { guard urlString.contains("helios"), let url = URL(string: urlString) else { - self.notifyVaultFailure(with: CardClientError.threeDSecureURLError) + self.notifyVaultFailure(with: CardClientError.threeDSecureURLError, completion: completion) return } analyticsService?.sendEvent("card-payments:vault-wo-purchase:challenge-required") - startVaultThreeDSecureChallenge(url: url, setupTokenID: vaultRequest.setupTokenID) + startVaultThreeDSecureChallenge(url: url, setupTokenID: vaultRequest.setupTokenID, completion: completion) } else { let vaultResult = CardVaultResult(setupTokenID: result.id, status: result.status, didAttemptThreeDSecureAuthentication: false) - notifyVaultSuccess(for: vaultResult) + notifyVaultSuccess(for: vaultResult, completion: completion) } } catch let error as CoreSDKError { - notifyVaultFailure(with: error) + notifyVaultFailure(with: error, completion: completion) } catch { - notifyVaultFailure(with: CardClientError.vaultTokenError) + notifyVaultFailure(with: CardClientError.vaultTokenError, completion: completion) } } } - + + /// Updates a setup token with a payment method. Performs + /// 3DS verification if required. If verification is performed, SDK returns a property `didAttemptThreeDSecureAuthentication`. + /// If `didAttempt3DSecureVerification` is `true`, check verification status with `/v3/vault/setup-token/{id}` in your server. + /// - Parameters: + /// - vaultRequest: The request containing setupTokenID and card + /// - Returns: `CardVaultResult` if successful + /// - Throws: An `Error` describing failure + public func vault(_ vaultRequest: CardVaultRequest) async throws -> CardVaultResult { + try await withCheckedThrowingContinuation { continuation in + vault(vaultRequest) { result, error in + if let error { + continuation.resume(throwing: error) + } else if let result { + continuation.resume(returning: result) + } + } + } + } + /// Approve an order with a card, which validates buyer's card, and if valid, attaches the card as the payment source to the order. /// After the order has been successfully approved, you will need to handle capturing/authorizing the order in your server. /// - Parameters: /// - orderId: Order id for approval /// - request: The request containing the card - public func approveOrder(request: CardRequest) { + /// - completion: A completion block that is invoked when the request is completed. If the request succeeds, + /// a `CardResult` with `orderID` , `status` and `didAttemptThreeDSecureAuthentication` are returned and `error` will be `nil`; + /// if it fails, `CardResult will be `nil` and `error` will describe the failure + public func approveOrder(request: CardRequest, completion: @escaping (CardResult?, Error?) -> Void) { analyticsService = AnalyticsService(coreConfig: config, orderID: request.orderID) analyticsService?.sendEvent("card-payments:3ds:started") Task { @@ -87,33 +109,52 @@ public class CardClient: NSObject { guard getQueryStringParameter(url: url, param: "flow") == "3ds", url.contains("helios"), let url = URL(string: url) else { - self.notifyFailure(with: CardClientError.threeDSecureURLError) + self.notifyCheckoutFailure(with: CardClientError.threeDSecureURLError, completion: completion) return } analyticsService?.sendEvent("card-payments:3ds:confirm-payment-source:challenge-required") - startThreeDSecureChallenge(url: url, orderId: result.id) + startThreeDSecureChallenge(url: url, orderId: result.id, completion: completion) } else { analyticsService?.sendEvent("card-payments:3ds:confirm-payment-source:succeeded") let cardResult = CardResult(orderID: result.id, status: result.status, didAttemptThreeDSecureAuthentication: false) - notifySuccess(for: cardResult) + notifyCheckoutSuccess(for: cardResult, completion: completion) } } catch let error as CoreSDKError { analyticsService?.sendEvent("card-payments:3ds:confirm-payment-source:failed") - notifyFailure(with: error) + notifyCheckoutFailure(with: error, completion: completion) } catch { analyticsService?.sendEvent("card-payments:3ds:confirm-payment-source:failed") - notifyFailure(with: CardClientError.unknownError) + notifyCheckoutFailure(with: CardClientError.unknownError, completion: completion) + } + } + } + + /// Approve an order with a card, which validates buyer's card, and if valid, attaches the card as the payment source to the order. + /// After the order has been successfully approved, you will need to handle capturing/authorizing the order in your server. + /// - Parameters: + /// - orderId: Order id for approval + /// - request: The request containing the card + /// - Returns: `CardResult` if successful + /// - Throws: An `Error` describing failure + public func approveOrder(request: CardRequest) async throws -> CardResult { + try await withCheckedThrowingContinuation { continuation in + approveOrder(request: request) { result, error in + if let error { + continuation.resume(throwing: error) + } else if let result { + continuation.resume(returning: result) + } } } } private func startThreeDSecureChallenge( url: URL, - orderId: String + orderId: String, + completion: @escaping (CardResult?, Error?) -> Void ) { - delegate?.cardThreeDSecureWillLaunch(self) webAuthenticationSession.start( url: url, @@ -126,20 +167,19 @@ public class CardClient: NSObject { } }, sessionDidComplete: { _, error in - self.delegate?.cardThreeDSecureDidFinish(self) if let error = error { switch error { case ASWebAuthenticationSessionError.canceledLogin: - self.notifyCancellation() + self.notifyCheckoutCancelWithError(with: CardClientError.threeDSecureCancellation, completion: completion) return default: - self.notifyFailure(with: CardClientError.threeDSecureError(error)) + self.notifyCheckoutFailure(with: CardClientError.threeDSecureError(error), completion: completion) return } } let cardResult = CardResult(orderID: orderId, status: nil, didAttemptThreeDSecureAuthentication: true) - self.notifySuccess(for: cardResult) + self.notifyCheckoutSuccess(for: cardResult, completion: completion) } ) } @@ -149,8 +189,7 @@ public class CardClient: NSObject { return url.queryItems?.first { $0.name == param }?.value } - private func startVaultThreeDSecureChallenge(url: URL, setupTokenID: String) { - vaultDelegate?.cardThreeDSecureWillLaunch(self) + private func startVaultThreeDSecureChallenge(url: URL, setupTokenID: String, completion: @escaping (CardVaultResult?, Error?) -> Void) { webAuthenticationSession.start( url: url, @@ -164,52 +203,51 @@ public class CardClient: NSObject { } }, sessionDidComplete: { _, error in - self.vaultDelegate?.cardThreeDSecureDidFinish(self) if let error = error { switch error { case ASWebAuthenticationSessionError.canceledLogin: - self.notifyVaultCancellation() + self.notifyVaultCancelWithError(with: CardClientError.threeDSecureCancellation, completion: completion) return default: - self.notifyVaultFailure(with: CardClientError.threeDSecureError(error)) + self.notifyVaultFailure(with: CardClientError.threeDSecureError(error), completion: completion) return } } let cardVaultResult = CardVaultResult(setupTokenID: setupTokenID, status: nil, didAttemptThreeDSecureAuthentication: true) - self.notifyVaultSuccess(for: cardVaultResult) + self.notifyVaultSuccess(for: cardVaultResult, completion: completion) } ) } - private func notifySuccess(for result: CardResult) { + private func notifyCheckoutSuccess(for result: CardResult, completion: (CardResult?, Error?) -> Void) { analyticsService?.sendEvent("card-payments:3ds:succeeded") - delegate?.card(self, didFinishWithResult: result) + completion(result, nil) } - private func notifyFailure(with error: CoreSDKError) { + private func notifyCheckoutFailure(with error: CoreSDKError, completion: (CardResult?, Error?) -> Void) { analyticsService?.sendEvent("card-payments:3ds:failed") - delegate?.card(self, didFinishWithError: error) + completion(nil, error) } - - private func notifyVaultSuccess(for vaultResult: CardVaultResult) { - analyticsService?.sendEvent("card-payments:vault-wo-purchase:succeeded") - vaultDelegate?.card(self, didFinishWithVaultResult: vaultResult) + + private func notifyCheckoutCancelWithError(with error: CoreSDKError, completion: (CardResult?, Error?) -> Void) { + analyticsService?.sendEvent("card-payments:3ds:challenge:user-canceled") + completion(nil, error) } - private func notifyVaultFailure(with vaultError: CoreSDKError) { - analyticsService?.sendEvent("card-payments:vault-wo-purchase:failed") - vaultDelegate?.card(self, didFinishWithVaultError: vaultError) + private func notifyVaultSuccess(for vaultResult: CardVaultResult, completion: (CardVaultResult?, Error?) -> Void) { + analyticsService?.sendEvent("card-payments:vault-wo-purchase:succeeded") + completion(vaultResult, nil) } - private func notifyCancellation() { - analyticsService?.sendEvent("card-payments:3ds:challenge:user-canceled") - delegate?.cardDidCancel(self) + private func notifyVaultFailure(with vaultError: CoreSDKError, completion: (CardVaultResult?, Error?) -> Void) { + analyticsService?.sendEvent("card-payments:vault-wo-purchase:failed") + completion(nil, vaultError) } - private func notifyVaultCancellation() { + private func notifyVaultCancelWithError(with vaultError: CoreSDKError, completion: (CardVaultResult?, Error?) -> Void) { analyticsService?.sendEvent("card-payments:vault-wo-purchase:challenge:canceled") - vaultDelegate?.cardThreeDSecureDidCancel(self) + completion(nil, vaultError) } } diff --git a/Sources/CardPayments/CardClientError.swift b/Sources/CardPayments/CardClientError.swift index 3a6522626..d068833b4 100644 --- a/Sources/CardPayments/CardClientError.swift +++ b/Sources/CardPayments/CardClientError.swift @@ -37,6 +37,9 @@ enum CardClientError { /// 9. Malformed Deeplink URL from 3DS case malformedDeeplinkURLError + + /// 10. Cancellation from 3DS verification + case threeDSCancellation } static let unknownError = CoreSDKError( @@ -64,7 +67,13 @@ enum CardClientError { domain: domain, errorDescription: "An invalid 3DS URL was returned. Contact developer.paypal.com/support." ) - + + static let threeDSecureCancellation = CoreSDKError( + code: Code.threeDSCancellation.rawValue, + domain: domain, + errorDescription: "3DS verification has been cancelled by the user." + ) + static let noVaultTokenDataError = CoreSDKError( code: Code.noVaultTokenDataError.rawValue, domain: domain, diff --git a/Sources/CardPayments/CardDelegate.swift b/Sources/CardPayments/CardDelegate.swift deleted file mode 100644 index 77c5cace6..000000000 --- a/Sources/CardPayments/CardDelegate.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation -#if canImport(CorePayments) -import CorePayments -#endif - -/// Card delegate to handle events from CardClient -public protocol CardDelegate: AnyObject { - - /// Notify that the Card flow finished with a successful result - /// - Parameters: - /// - client: the CardClient associated with delegate - /// - didFinishWithResult: the successful result from the flow - func card(_ cardClient: CardClient, didFinishWithResult result: CardResult) - - /// Notify that an error occurred in the Cardl flow - /// - Parameters: - /// - client: the CardClient associated with delegate - /// - didFinishWithError: the error returned by the Card flow - func card(_ cardClient: CardClient, didFinishWithError error: CoreSDKError) - - /// Notify that the Card flow has been cancelled - /// - Parameters: - /// - client: the CardClient associated with delegate - func cardDidCancel(_ cardClient: CardClient) - - /// Notify that the 3DS challenge will be launched - /// - Parameters: - /// - client: the CardClient associated with delegate - func cardThreeDSecureWillLaunch(_ cardClient: CardClient) - - /// Notify that the 3DS challenge has finished - /// - Parameters: - /// - client: the CardClient associated with delegate - func cardThreeDSecureDidFinish(_ cardClient: CardClient) -} diff --git a/Sources/CardPayments/CardVaultDelegate.swift b/Sources/CardPayments/CardVaultDelegate.swift deleted file mode 100644 index 4a85cb2aa..000000000 --- a/Sources/CardPayments/CardVaultDelegate.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation -#if canImport(CorePayments) -import CorePayments -#endif - -/// CardVault delegate to handle events from CardClient -public protocol CardVaultDelegate: AnyObject { - - /// Notify that the Card vault flow finished with a successful result - /// - Parameters: - /// - client: the CardClient associated with delegate - /// - didFinishWithResult: the successful result from the flow - func card(_ cardClient: CardClient, didFinishWithVaultResult vaultResult: CardVaultResult) - - /// Notify that an error occurred in the Card vault flow - /// - Parameters: - /// - client: the CardClient associated with delegate - /// - didFinishWithError: the error returned by the Card vault flow - func card(_ cardClient: CardClient, didFinishWithVaultError vaultError: CoreSDKError) - - /// Notify that the ThreeDSecure has been cancelled - /// - Parameters: - /// - client: the CardClient associated with delegate - func cardThreeDSecureDidCancel(_ cardClient: CardClient) - - /// Notify that the 3DS challenge will be launched - /// - Parameters: - /// - client: the CardClient associated with delegate - func cardThreeDSecureWillLaunch(_ cardClient: CardClient) - - /// Notify that the 3DS challenge has finished - /// - Parameters: - /// - client: the CardClient associated with delegate - func cardThreeDSecureDidFinish(_ cardClient: CardClient) -} diff --git a/Sources/PayPalWebPayments/PayPalVaultDelegate.swift b/Sources/PayPalWebPayments/PayPalVaultDelegate.swift deleted file mode 100644 index f189b31fa..000000000 --- a/Sources/PayPalWebPayments/PayPalVaultDelegate.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Foundation -#if canImport(CorePayments) -import CorePayments -#endif - -/// PayPalVault delegate to vault results from PayPalWebCheckoutClient -public protocol PayPalVaultDelegate: AnyObject { - - /// Notify that the PayPal vault flow finished with a successful result - /// - Parameters: - /// - client: the PayPalWebCheckoutClient associated with delegate - /// - didFinishWithResult: the successful result from the flow - func paypal(_ paypalWebClient: PayPalWebCheckoutClient, didFinishWithVaultResult paypalVaultResult: PayPalVaultResult) - - /// Notify that an error occurred in the PayPal vault flow - /// - Parameters: - /// - client: the PayPalWebCheckoutClien associated with delegate - /// - didFinishWithError: the error returned by the PayPal vault flow - func paypal(_ paypalWebClient: PayPalWebCheckoutClient, didFinishWithVaultError vaultError: CoreSDKError) - - /// Notify that a cancellation occurred in the PayPal vault flow - /// - Parameters: - /// - client: the PayPalWebCheckoutClien associated with delegate - func paypalDidCancel(_ paypalWebClient: PayPalWebCheckoutClient) -} diff --git a/Sources/PayPalWebPayments/PayPalWebCheckoutClient.swift b/Sources/PayPalWebPayments/PayPalWebCheckoutClient.swift index bf6ff7d8c..4f1bde357 100644 --- a/Sources/PayPalWebPayments/PayPalWebCheckoutClient.swift +++ b/Sources/PayPalWebPayments/PayPalWebCheckoutClient.swift @@ -6,8 +6,6 @@ import CorePayments public class PayPalWebCheckoutClient: NSObject { - public weak var vaultDelegate: PayPalVaultDelegate? - public weak var delegate: PayPalWebCheckoutDelegate? let config: CoreConfig private let webAuthenticationSession: WebAuthenticationSession private let networkingClient: NetworkingClient @@ -32,7 +30,10 @@ public class PayPalWebCheckoutClient: NSObject { /// Launch the PayPal web flow /// - Parameters: /// - request: the PayPalRequest for the transaction - public func start(request: PayPalWebCheckoutRequest) { + /// - completion: A completion block that is invoked when the request is completed. If the request succeeds, + /// a `PayPalWebCheckoutResult` with `orderID` and `payerID` are returned and `error` will be `nil`; + /// if it fails, `PayPalWebCheckoutResult will be `nil` and `error` will describe the failure + public func start(request: PayPalWebCheckoutRequest, completion: @escaping (PayPalWebCheckoutResult?, Error?) -> Void) { analyticsService = AnalyticsService(coreConfig: config, orderID: request.orderID) analyticsService?.sendEvent("paypal-web-payments:started") @@ -44,7 +45,7 @@ public class PayPalWebCheckoutClient: NSObject { guard let payPalCheckoutURL = URL(string: payPalCheckoutURLString), let payPalCheckoutURLComponents = payPalCheckoutReturnURL(payPalCheckoutURL: payPalCheckoutURL) else { - self.notifyFailure(with: PayPalWebCheckoutClientError.payPalURLError) + self.notifyCheckoutFailure(with: PayPalWebCheckoutClientError.payPalURLError, completion: completion) return } @@ -62,10 +63,13 @@ public class PayPalWebCheckoutClient: NSObject { if let error = error { switch error { case ASWebAuthenticationSessionError.canceledLogin: - self.notifyCancellation() + self.notifyCheckoutCancelWithError( + with: PayPalWebCheckoutClientError.payPalCancellationError, + completion: completion + ) return default: - self.notifyFailure(with: PayPalWebCheckoutClientError.webSessionError(error)) + self.notifyCheckoutFailure(with: PayPalWebCheckoutClientError.webSessionError(error), completion: completion) return } } @@ -73,17 +77,34 @@ public class PayPalWebCheckoutClient: NSObject { if let url = url { guard let orderID = self.getQueryStringParameter(url: url.absoluteString, param: "token"), let payerID = self.getQueryStringParameter(url: url.absoluteString, param: "PayerID") else { - self.notifyFailure(with: PayPalWebCheckoutClientError.malformedResultError) + self.notifyCheckoutFailure(with: PayPalWebCheckoutClientError.malformedResultError, completion: completion) return } let result = PayPalWebCheckoutResult(orderID: orderID, payerID: payerID) - self.notifySuccess(for: result) + self.notifyCheckoutSuccess(for: result, completion: completion) } } ) } + /// Launch the PayPal web flow + /// - Parameters: + /// - request: the PayPalRequest for the transaction + /// - Returns: A `PayPalWebCheckoutResult` if successful + /// - Throws: An `Error` describing the failure + public func start(request: PayPalWebCheckoutRequest) async throws -> PayPalWebCheckoutResult { + try await withCheckedThrowingContinuation { continuation in + start(request: request) { result, error in + if let error { + continuation.resume(throwing: error) + } else if let result { + continuation.resume(returning: result) + } + } + } + } + func payPalCheckoutReturnURL(payPalCheckoutURL: URL) -> URL? { let bundleID = PayPalCoreConstants.callbackURLScheme let redirectURLString = "\(bundleID)://x-callback-url/paypal-sdk/paypal-checkout" @@ -101,7 +122,10 @@ public class PayPalWebCheckoutClient: NSObject { /// After setupToken successfullly attaches a payment method, you will need to create a payment token with the setup token /// - Parameters: /// - vaultRequest: Request created with url for vault approval and setupTokenID - public func vault(_ vaultRequest: PayPalVaultRequest) { + /// - completion: A completion block that is invoked when the request is completed. If the request succeeds, + /// a `PayPalVaultResult` with `tokenID` and `approvalSessionID` are returned and `error` will be `nil`; + /// if it fails, `PayPalVaultResult will be `nil` and `error` will describe the failure + public func vault(_ vaultRequest: PayPalVaultRequest, completion: @escaping (PayPalVaultResult?, Error?) -> Void) { analyticsService = AnalyticsService(coreConfig: config, setupToken: vaultRequest.setupTokenID) analyticsService?.sendEvent("paypal-web-payments:vault-wo-purchase:started") @@ -110,7 +134,7 @@ public class PayPalWebCheckoutClient: NSObject { vaultURLComponents?.queryItems = queryItems guard let vaultCheckoutURL = vaultURLComponents?.url else { - notifyVaultFailure(with: PayPalWebCheckoutClientError.payPalURLError) + notifyVaultFailure(with: PayPalWebCheckoutClientError.payPalURLError, completion: completion) return } @@ -128,10 +152,13 @@ public class PayPalWebCheckoutClient: NSObject { if let error = error { switch error { case ASWebAuthenticationSessionError.canceledLogin: - self.notifyVaultCancellation() + self.notifyVaultCancelWithError( + with: PayPalWebCheckoutClientError.payPalVaultCancellationError, + completion: completion + ) return default: - self.notifyVaultFailure(with: PayPalWebCheckoutClientError.webSessionError(error)) + self.notifyVaultFailure(with: PayPalWebCheckoutClientError.webSessionError(error), completion: completion) return } } @@ -141,51 +168,68 @@ public class PayPalWebCheckoutClient: NSObject { let approvalSessionID = self.getQueryStringParameter(url: url.absoluteString, param: "approval_session_id"), !tokenID.isEmpty, !approvalSessionID.isEmpty else { - self.notifyVaultFailure(with: PayPalWebCheckoutClientError.payPalVaultResponseError) + self.notifyVaultFailure(with: PayPalWebCheckoutClientError.payPalVaultResponseError, completion: completion) return } let paypalVaultResult = PayPalVaultResult(tokenID: tokenID, approvalSessionID: approvalSessionID) - self.notifyVaultSuccess(for: paypalVaultResult) + self.notifyVaultSuccess(for: paypalVaultResult, completion: completion) } } ) } + /// Starts a web session for vaulting PayPal Payment Method + /// After setupToken successfullly attaches a payment method, you will need to create a payment token with the setup token + /// - Parameters: + /// - vaultRequest: Request created with url for vault approval and setupTokenID + /// - Returns: `PayPalVaultResult`if successful + /// - Throws: An `Error` describing failure + public func vault(_ vaultRequest: PayPalVaultRequest) async throws -> PayPalVaultResult { + try await withCheckedThrowingContinuation { continuation in + vault(vaultRequest) { result, error in + if let error { + continuation.resume(throwing: error) + } else if let result { + continuation.resume(returning: result) + } + } + } + } + private func getQueryStringParameter(url: String, param: String) -> String? { guard let url = URLComponents(string: url) else { return nil } return url.queryItems?.first { $0.name == param }?.value } - private func notifySuccess(for result: PayPalWebCheckoutResult) { - let payPalResult = PayPalWebCheckoutResult(orderID: result.orderID, payerID: result.payerID) - analyticsService?.sendEvent("paypal-web-payments:succeeded") - delegate?.payPal(self, didFinishWithResult: payPalResult) + private func notifyCheckoutSuccess(for result: PayPalWebCheckoutResult, completion: (PayPalWebCheckoutResult?, Error?) -> Void) { + self.analyticsService?.sendEvent("paypal-web-payments:succeeded") + completion(result, nil) } - private func notifyFailure(with error: CoreSDKError) { - analyticsService?.sendEvent("paypal-web-payments:failed") - delegate?.payPal(self, didFinishWithError: error) + private func notifyCheckoutFailure(with error: CoreSDKError, completion: (PayPalWebCheckoutResult?, Error?) -> Void) { + self.analyticsService?.sendEvent("paypal-web-payments:failed") + completion(nil, error) } - private func notifyCancellation() { - analyticsService?.sendEvent("paypal-web-payments:browser-login:canceled") - delegate?.payPalDidCancel(self) + private func notifyCheckoutCancelWithError(with error: CoreSDKError, completion: (PayPalWebCheckoutResult?, Error?) -> Void) { + analyticsService?.sendEvent("paypal-web-payments:challenge:browser-login:canceled") + completion(nil, error) } - private func notifyVaultSuccess(for result: PayPalVaultResult) { + private func notifyVaultSuccess(for result: PayPalVaultResult, completion: (PayPalVaultResult?, Error?) -> Void) { analyticsService?.sendEvent("paypal-web-payments:vault-wo-purchase:succeeded") - vaultDelegate?.paypal(self, didFinishWithVaultResult: result) + completion(result, nil) } - private func notifyVaultFailure(with error: CoreSDKError) { + private func notifyVaultFailure(with error: CoreSDKError, completion: (PayPalVaultResult?, Error?) -> Void) { analyticsService?.sendEvent("paypal-web-payments:vault-wo-purchase:failed") - vaultDelegate?.paypal(self, didFinishWithVaultError: error) + completion(nil, error) } - private func notifyVaultCancellation() { + private func notifyVaultCancelWithError(with vaultError: CoreSDKError, completion: (PayPalVaultResult?, Error?) -> Void) { analyticsService?.sendEvent("paypal-web-payments:vault-wo-purchase:canceled") - vaultDelegate?.paypalDidCancel(self) + completion(nil, vaultError) } } diff --git a/Sources/PayPalWebPayments/PayPalWebCheckoutClientError.swift b/Sources/PayPalWebPayments/PayPalWebCheckoutClientError.swift index ccfd943fe..8fc0146c2 100644 --- a/Sources/PayPalWebPayments/PayPalWebCheckoutClientError.swift +++ b/Sources/PayPalWebPayments/PayPalWebCheckoutClientError.swift @@ -22,7 +22,13 @@ enum PayPalWebCheckoutClientError { case malformedResultError /// 4. Vault result did not return a token id - case paypalVaultResponseError + case payPalVaultResponseError + + /// 5. Websession is cancelled by the user + case payPalCancellationError + + /// 6. Websession is cancelled by the user + case payPalVaultCancellationError } static let webSessionError: (Error) -> CoreSDKError = { error in @@ -46,8 +52,20 @@ enum PayPalWebCheckoutClientError { ) static let payPalVaultResponseError = CoreSDKError( - code: Code.paypalVaultResponseError.rawValue, + code: Code.payPalVaultResponseError.rawValue, + domain: domain, + errorDescription: "Error parsing PayPal vault response" + ) + + static let payPalCancellationError = CoreSDKError( + code: Code.payPalCancellationError.rawValue, + domain: domain, + errorDescription: "PayPal checkout has been cancelled by the user" + ) + + static let payPalVaultCancellationError = CoreSDKError( + code: Code.payPalVaultCancellationError.rawValue, domain: domain, - errorDescription: "Error parsing paypal vault response" + errorDescription: "PayPal vault has been cancelled by the user" ) } diff --git a/Sources/PayPalWebPayments/PayPalWebCheckoutDelegate.swift b/Sources/PayPalWebPayments/PayPalWebCheckoutDelegate.swift deleted file mode 100644 index 5fcfc8ecc..000000000 --- a/Sources/PayPalWebPayments/PayPalWebCheckoutDelegate.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Foundation -#if canImport(CorePayments) -import CorePayments -#endif - -/// PayPal delegate to handle events from PayPalNativeCheckoutClient -public protocol PayPalWebCheckoutDelegate: AnyObject { - - /// Notify that the PayPal flow finished with a successful result - /// - Parameters: - /// - client: the PayPalClient associated with delegate - /// - didFinishWithResult: the successful result from the flow - func payPal(_ payPalClient: PayPalWebCheckoutClient, didFinishWithResult result: PayPalWebCheckoutResult) - - /// Notify that an error occurred in the PayPal flow - /// - Parameters: - /// - client: the PayPalClient associated with delegate - /// - didFinishWithError: the error returned by the PayPal flow - func payPal(_ payPalClient: PayPalWebCheckoutClient, didFinishWithError error: CoreSDKError) - - /// Notify that the PayPal flow has been cancelled - /// - Parameters: - /// - client: the PayPalClient associated with delegate - func payPalDidCancel(_ payPalClient: PayPalWebCheckoutClient) -} diff --git a/UnitTests/CardPaymentsTests/CardClient_Tests.swift b/UnitTests/CardPaymentsTests/CardClient_Tests.swift index e97295047..e23065c72 100644 --- a/UnitTests/CardPaymentsTests/CardClient_Tests.swift +++ b/UnitTests/CardPaymentsTests/CardClient_Tests.swift @@ -5,7 +5,7 @@ import AuthenticationServices @testable import CardPayments @testable import TestShared -// swiftlint:disable type_body_length file_length +// swiftlint:disable type_body_length class CardClient_Tests: XCTestCase { // MARK: - Helper Properties @@ -22,7 +22,6 @@ class CardClient_Tests: XCTestCase { let mockWebAuthSession = MockWebAuthenticationSession() var mockNetworkingClient: MockNetworkingClient! - var mockCardVaultDelegate: MockCardVaultDelegate! var mockCheckoutOrdersAPI: MockCheckoutOrdersAPI! var mockVaultAPI: MockVaultPaymentTokensAPI! @@ -60,18 +59,15 @@ class CardClient_Tests: XCTestCase { mockVaultAPI.stubSetupTokenResponse = updateSetupTokenResponse let expectation = expectation(description: "vault completed") - let cardVaultDelegate = MockCardVaultDelegate(success: {_, result in - XCTAssertEqual(result.setupTokenID, setupTokenID) - XCTAssertEqual(result.status, vaultStatus) - XCTAssertFalse(result.didAttemptThreeDSecureAuthentication) + + sut.vault(vaultRequest) { result, error in + XCTAssertEqual(result?.setupTokenID, setupTokenID) + XCTAssertEqual(result?.status, vaultStatus) + XCTAssertNil(error) expectation.fulfill() - }, error: {_, _ in - XCTFail("Invoked error() callback. Should invoke success().") - }) - sut.vaultDelegate = cardVaultDelegate - sut.vault(vaultRequest) - - waitForExpectations(timeout: 10) + } + + waitForExpectations(timeout: 2, handler: nil) } func testVault_withValid3DSURLResponse_returnsSuccess() { @@ -84,21 +80,23 @@ class CardClient_Tests: XCTestCase { mockVaultAPI.stubSetupTokenResponse = updateSetupTokenResponse let expectation = expectation(description: "vault completed") - let cardVaultDelegate = MockCardVaultDelegate(success: {_, result in - XCTAssertEqual(result.setupTokenID, setupTokenID) - XCTAssertNil(result.status) - XCTAssertTrue(result.didAttemptThreeDSecureAuthentication) + sut.vault(vaultRequest) { result, error in + if let result { + XCTAssertEqual(result.setupTokenID, setupTokenID) + XCTAssertNil(result.status) + XCTAssertTrue(result.didAttemptThreeDSecureAuthentication) + } else { + XCTFail("expected result not to be nil") + } + + XCTAssertNil(error) expectation.fulfill() - }, error: {_, _ in - XCTFail("Invoked error() callback. Should invoke success().") - }) - sut.vaultDelegate = cardVaultDelegate - sut.vault(vaultRequest) + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } - func testVault_withInvalid3DSURLResponse_returnsSuccess() { + func testVault_withInvalid3DSURLResponse_returnsError() { let setupTokenID = "testToken1" let vaultStatus = "PAYER_ACTION_REQUIRED" let vaultRequest = CardVaultRequest(card: card, setupTokenID: setupTokenID) @@ -108,18 +106,20 @@ class CardClient_Tests: XCTestCase { mockVaultAPI.stubSetupTokenResponse = updateSetupTokenResponse let expectation = expectation(description: "vault completed") - let cardVaultDelegate = MockCardVaultDelegate(success: {_, _ in - XCTFail("Invoked success() callback. Should invoke error().") - }, error: {_, error in - XCTAssertEqual(error.code, CardClientError.threeDSecureURLError.code) - XCTAssertEqual(error.domain, CardClientError.domain) - XCTAssertEqual(error.localizedDescription, CardClientError.threeDSecureURLError.localizedDescription) + + sut.vault(vaultRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.domain, CardClientError.domain) + XCTAssertEqual(error.code, CardClientError.threeDSecureURLError.code) + XCTAssertEqual(error.localizedDescription, CardClientError.threeDSecureURLError.localizedDescription) + } else { + XCTFail("Expected error to be of type CoreSDKError") + } expectation.fulfill() - }) - sut.vaultDelegate = cardVaultDelegate - sut.vault(vaultRequest) + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } func testVault_whenVaultAPIError_bubblesError() { @@ -129,18 +129,20 @@ class CardClient_Tests: XCTestCase { mockVaultAPI.stubError = CoreSDKError(code: 123, domain: "fake-domain", errorDescription: "api-error") let expectation = expectation(description: "vault completed") - let cardVaultDelegate = MockCardVaultDelegate(success: {_, _ in - XCTFail("Invoked success() callback. Should invoke error().") - }, error: {_, error in - XCTAssertEqual(error.domain, "fake-domain") - XCTAssertEqual(error.code, 123) - XCTAssertEqual(error.localizedDescription, "api-error") + + sut.vault(vaultRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.domain, "fake-domain") + XCTAssertEqual(error.code, 123) + XCTAssertEqual(error.localizedDescription, "api-error") + } else { + XCTFail("Expected error to be of type CoreSDKError") + } expectation.fulfill() - }) - sut.vaultDelegate = cardVaultDelegate - sut.vault(vaultRequest) + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } func testVault_whenUnknownError_returnsVaultError() { @@ -150,18 +152,19 @@ class CardClient_Tests: XCTestCase { mockVaultAPI.stubError = NSError(domain: "some-domain", code: 123, userInfo: [NSLocalizedDescriptionKey: "some-description"]) let expectation = expectation(description: "vault completed") - let cardVaultDelegate = MockCardVaultDelegate(success: {_, _ in - XCTFail("Invoked success() callback. Should invoke error().") - }, error: {_, error in - XCTAssertEqual(error.domain, CardClientError.domain) - XCTAssertEqual(error.code, CardClientError.Code.vaultTokenError.rawValue) - XCTAssertEqual(error.localizedDescription, "An error occurred while vaulting a card.") + sut.vault(vaultRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.domain, CardClientError.domain) + XCTAssertEqual(error.code, CardClientError.Code.vaultTokenError.rawValue) + XCTAssertEqual(error.localizedDescription, "An error occurred while vaulting a card.") + } else { + XCTFail("Expected error to be of type CoreSDKError") + } expectation.fulfill() - }) - sut.vaultDelegate = cardVaultDelegate - sut.vault(vaultRequest) + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } func test_vault_withThreeDSecure_browserSwitchLaunches_vaultReturnsSuccess() { @@ -171,26 +174,19 @@ class CardClient_Tests: XCTestCase { let expectation = expectation(description: "vault() completed") - let mockCardVaultDelegate = MockCardVaultDelegate( - success: {_, result in + sut.vault(cardVaultRequest) { result, error in + XCTAssertNil(error) + if let result { XCTAssertEqual(result.setupTokenID, "testSetupTokenId") XCTAssertNil(result.status) XCTAssertTrue(result.didAttemptThreeDSecureAuthentication) - expectation.fulfill() - }, - error: { _, error in - XCTFail(error.localizedDescription) - expectation.fulfill() - }, - cancel: { _ in XCTFail("Invoked cancel() callback. Should invoke success().") }, - threeDSWillLaunch: { _ in XCTAssert(true) }, - threeDSLaunched: { _ in XCTAssert(true) } - ) - - sut.vaultDelegate = mockCardVaultDelegate - sut.vault(cardVaultRequest) + } else { + XCTFail("Expected result not to be nil") + } + expectation.fulfill() + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } func testVault_withThreeDSecure_userCancelsBrowser() { @@ -203,27 +199,19 @@ class CardClient_Tests: XCTestCase { let expectation = expectation(description: "vault() completed") - let mockCardVaultDelegate = MockCardVaultDelegate( - success: {_, _ in - XCTFail("Invoked success() callback. Should invoke cancel().") - expectation.fulfill() - }, - error: { _, error in - XCTFail(error.localizedDescription) - expectation.fulfill() - }, - cancel: { _ in - XCTAssert(true) - expectation.fulfill() - }, - threeDSWillLaunch: { _ in XCTAssert(true) }, - threeDSLaunched: { _ in XCTAssert(true) } - ) - - sut.vaultDelegate = mockCardVaultDelegate - sut.vault(cardVaultRequest) + sut.vault(cardVaultRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.domain, CardClientError.domain) + XCTAssertEqual(error.code, CardClientError.Code.threeDSCancellation.rawValue) + XCTAssertEqual(error.localizedDescription, CardClientError.threeDSecureCancellation.localizedDescription) + } else { + XCTFail("Expected error to be of type CoreSDKError") + } + expectation.fulfill() + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } func testVault_withThreeDSecure_browserReturnsError() { @@ -237,29 +225,19 @@ class CardClient_Tests: XCTestCase { let expectation = expectation(description: "vault() completed") - let mockCardVaultDelegate = MockCardVaultDelegate( - success: {_, _ in - XCTFail("Invoked success() callback. Should invoke error().") - expectation.fulfill() - }, - error: { _, error in + sut.vault(cardVaultRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { XCTAssertEqual(error.domain, CardClientError.domain) XCTAssertEqual(error.code, CardClientError.Code.threeDSecureError.rawValue) XCTAssertEqual(error.localizedDescription, "Mock web session error description.") - expectation.fulfill() - }, - cancel: { _ in - XCTFail("Invoked cancel() callback. Should invoke error().") - expectation.fulfill() - }, - threeDSWillLaunch: { _ in XCTAssert(true) }, - threeDSLaunched: { _ in XCTAssert(true) } - ) - - sut.vaultDelegate = mockCardVaultDelegate - sut.vault(cardVaultRequest) + } else { + XCTFail("Expected error to be of type CoreSDKError") + } + expectation.fulfill() + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } // MARK: - approveOrder() tests @@ -269,21 +247,19 @@ class CardClient_Tests: XCTestCase { let expectation = expectation(description: "approveOrder() completed") - let mockCardDelegate = MockCardDelegate(success: {_, _ in - XCTFail("Invoked success() callback. Should invoke error().") - }, error: { _, err in - XCTAssertEqual(err.code, 3) - XCTAssertEqual(err.domain, "CardClientErrorDomain") - XCTAssertEqual(err.errorDescription, "An invalid 3DS URL was returned. Contact developer.paypal.com/support.") + sut.approveOrder(request: cardRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.code, 3) + XCTAssertEqual(error.domain, "CardClientErrorDomain") + XCTAssertEqual(error.errorDescription, "An invalid 3DS URL was returned. Contact developer.paypal.com/support.") + } else { + XCTFail("Expected error not to be nil") + } expectation.fulfill() - }, threeDSWillLaunch: { _ in - XCTFail("Invoked willLaunch() callback. Should invoke error().") - }) + } - sut.delegate = mockCardDelegate - sut.approveOrder(request: cardRequest) - - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } func testApproveOrder_withNoThreeDSecure_returnsOrderData() { @@ -291,21 +267,20 @@ class CardClient_Tests: XCTestCase { let expectation = expectation(description: "approveOrder() completed") - let mockCardDelegate = MockCardDelegate(success: {_, result in - XCTAssertEqual(result.orderID, "testOrderId") - XCTAssertEqual(result.status, "APPROVED") - XCTAssertFalse(result.didAttemptThreeDSecureAuthentication) - expectation.fulfill() - }, error: { _, _ in - XCTFail("Invoked error() callback. Should invoke success().") - }, threeDSWillLaunch: { _ in - XCTFail("Invoked willLaunch() callback. Should invoke success().") - }) + sut.approveOrder(request: cardRequest) { result, error in + if let result { + XCTAssertEqual(result.orderID, "testOrderId") + XCTAssertEqual(result.status, "APPROVED") + XCTAssertFalse(result.didAttemptThreeDSecureAuthentication) + } else { + XCTFail("expected result not to be nil") + } - sut.delegate = mockCardDelegate - sut.approveOrder(request: cardRequest) + XCTAssertNil(error) + expectation.fulfill() + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } func testApproveOrder_whenConfirmPaymentSDKError_bubblesError() { @@ -313,21 +288,19 @@ class CardClient_Tests: XCTestCase { let expectation = expectation(description: "approveOrder() completed") - let mockCardDelegate = MockCardDelegate(success: {_, _ in - XCTFail("Invoked success() callback. Should invoke error().") - }, error: { _, error in - XCTAssertEqual(error.domain, "sdk-domain") - XCTAssertEqual(error.code, 123) - XCTAssertEqual(error.localizedDescription, "sdk-error-desc") + sut.approveOrder(request: cardRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.code, 123) + XCTAssertEqual(error.domain, "sdk-domain") + XCTAssertEqual(error.errorDescription, "sdk-error-desc") + } else { + XCTFail("Expected error not to be nil") + } expectation.fulfill() - }, threeDSWillLaunch: { _ in - XCTFail("Invoked willLaunch() callback. Should invoke error().") - }) - - sut.delegate = mockCardDelegate - sut.approveOrder(request: cardRequest) + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } func testApproveOrder_whenConfirmPaymentGeneralError_returnsUnknownError() { @@ -339,21 +312,17 @@ class CardClient_Tests: XCTestCase { let expectation = expectation(description: "approveOrder() completed") - let mockCardDelegate = MockCardDelegate(success: {_, _ in - XCTFail("Invoked success() callback. Should invoke error().") - }, error: { _, error in - XCTAssertEqual(error.domain, CardClientError.domain) - XCTAssertEqual(error.code, CardClientError.Code.unknown.rawValue) - XCTAssertNotNil(error.localizedDescription) - expectation.fulfill() - }, threeDSWillLaunch: { _ in - XCTFail("Invoked willLaunch() callback. Should invoke error().") - }) - - sut.delegate = mockCardDelegate - sut.approveOrder(request: cardRequest) + sut.approveOrder(request: cardRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.domain, CardClientError.domain) + XCTAssertEqual(error.code, CardClientError.Code.unknown.rawValue) + XCTAssertNotNil(error.localizedDescription) + expectation.fulfill() + } + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } func testApproveOrder_withThreeDSecure_browserSwitchLaunches_getOrderReturnsSuccess() { @@ -363,25 +332,19 @@ class CardClient_Tests: XCTestCase { let expectation = expectation(description: "approveOrder() completed") - let mockCardDelegate = MockCardDelegate( - success: {_, result in + sut.approveOrder(request: cardRequest) { result, error in + XCTAssertNil(error) + if let result { XCTAssertEqual(result.orderID, "testOrderId") XCTAssertNil(result.status) XCTAssertTrue(result.didAttemptThreeDSecureAuthentication) - expectation.fulfill() - }, - error: { _, error in - XCTFail(error.localizedDescription) - expectation.fulfill() - }, - cancel: { _ in XCTFail("Invoked cancel() callback. Should invoke success().") }, - threeDSWillLaunch: { _ in XCTAssert(true) }, - threeDSLaunched: { _ in XCTAssert(true) }) - - sut.delegate = mockCardDelegate - sut.approveOrder(request: cardRequest) + } else { + XCTFail("Expected non-nil result") + } + expectation.fulfill() + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } func testApproveOrder_withThreeDSecure_userCancelsBrowser() { @@ -394,26 +357,19 @@ class CardClient_Tests: XCTestCase { let expectation = expectation(description: "approveOrder() completed") - let mockCardDelegate = MockCardDelegate( - success: {_, _ in - XCTFail("Invoked success() callback. Should invoke cancel().") - expectation.fulfill() - }, - error: { _, error in - XCTFail(error.localizedDescription) - expectation.fulfill() - }, - cancel: { _ in - XCTAssert(true) - expectation.fulfill() - }, - threeDSWillLaunch: { _ in XCTAssert(true) }, - threeDSLaunched: { _ in XCTAssert(true) }) - - sut.delegate = mockCardDelegate - sut.approveOrder(request: cardRequest) + sut.approveOrder(request: cardRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.domain, CardClientError.domain) + XCTAssertEqual(error.code, CardClientError.threeDSecureCancellation.code) + XCTAssertEqual(error.localizedDescription, CardClientError.threeDSecureCancellation.localizedDescription) + } else { + XCTFail("Expected error to be of type CoreSDKError") + } + expectation.fulfill() + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } func testApproveOrder_withThreeDSecure_browserReturnsError() { @@ -427,27 +383,18 @@ class CardClient_Tests: XCTestCase { let expectation = expectation(description: "approveOrder() completed") - let mockCardDelegate = MockCardDelegate( - success: {_, _ in - XCTFail("Invoked success() callback. Should invoke error().") - expectation.fulfill() - }, - error: { _, error in + sut.approveOrder(request: cardRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { XCTAssertEqual(error.domain, CardClientError.domain) XCTAssertEqual(error.code, CardClientError.Code.threeDSecureError.rawValue) XCTAssertEqual(error.localizedDescription, "Mock web session error description.") - expectation.fulfill() - }, - cancel: { _ in - XCTFail("Invoked cancel() callback. Should invoke error().") - expectation.fulfill() - }, - threeDSWillLaunch: { _ in XCTAssert(true) }, - threeDSLaunched: { _ in XCTAssert(true) }) - - sut.delegate = mockCardDelegate - sut.approveOrder(request: cardRequest) + } else { + XCTFail("Expected error to be of type CoreSDKError") + } + expectation.fulfill() + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } } diff --git a/UnitTests/CardPaymentsTests/Mocks/MockCardDelegate.swift b/UnitTests/CardPaymentsTests/Mocks/MockCardDelegate.swift deleted file mode 100644 index 2b9b5a71a..000000000 --- a/UnitTests/CardPaymentsTests/Mocks/MockCardDelegate.swift +++ /dev/null @@ -1,45 +0,0 @@ -@testable import CorePayments -@testable import CardPayments - -class MockCardDelegate: CardDelegate { - - private var success: ((CardClient, CardResult) -> Void)? - private var failure: ((CardClient, CoreSDKError) -> Void)? - private var cancel: ((CardClient) -> Void)? - private var threeDSWillLaunch: ((CardClient) -> Void)? - private var threeDSLaunched: ((CardClient) -> Void)? - - required init( - success: ((CardClient, CardResult) -> Void)? = nil, - error: ((CardClient, CoreSDKError) -> Void)? = nil, - cancel: ((CardClient) -> Void)? = nil, - threeDSWillLaunch: ((CardClient) -> Void)? = nil, - threeDSLaunched: ((CardClient) -> Void)? = nil - ) { - self.success = success - self.failure = error - self.cancel = cancel - self.threeDSWillLaunch = threeDSWillLaunch - self.threeDSLaunched = threeDSLaunched - } - - func card(_ cardClient: CardClient, didFinishWithResult result: CardResult) { - success?(cardClient, result) - } - - func card(_ cardClient: CardClient, didFinishWithError error: CoreSDKError) { - failure?(cardClient, error) - } - - func cardDidCancel(_ cardClient: CardClient) { - cancel?(cardClient) - } - - func cardThreeDSecureWillLaunch(_ cardClient: CardClient) { - threeDSWillLaunch?(cardClient) - } - - func cardThreeDSecureDidFinish(_ cardClient: CardClient) { - threeDSLaunched?(cardClient) - } -} diff --git a/UnitTests/CardPaymentsTests/Mocks/MockCardVaultDelegate.swift b/UnitTests/CardPaymentsTests/Mocks/MockCardVaultDelegate.swift deleted file mode 100644 index a27c8cd20..000000000 --- a/UnitTests/CardPaymentsTests/Mocks/MockCardVaultDelegate.swift +++ /dev/null @@ -1,45 +0,0 @@ -@testable import CorePayments -@testable import CardPayments - -class MockCardVaultDelegate: CardVaultDelegate { - - private var success: ((CardClient, CardVaultResult) -> Void)? - private var failure: ((CardClient, CoreSDKError) -> Void)? - private var cancel: ((CardClient) -> Void)? - private var threeDSWillLaunch: ((CardClient) -> Void)? - private var threeDSLaunched: ((CardClient) -> Void)? - - required init( - success: ((CardClient, CardVaultResult) -> Void)? = nil, - error: ((CardClient, CoreSDKError) -> Void)? = nil, - cancel: ((CardClient) -> Void)? = nil, - threeDSWillLaunch: ((CardClient) -> Void)? = nil, - threeDSLaunched: ((CardClient) -> Void)? = nil - ) { - self.success = success - self.failure = error - self.cancel = cancel - self.threeDSWillLaunch = threeDSWillLaunch - self.threeDSLaunched = threeDSLaunched - } - - func card(_ cardClient: CardClient, didFinishWithVaultResult vaultResult: CardPayments.CardVaultResult) { - success?(cardClient, vaultResult) - } - - func card(_ cardClient: CardClient, didFinishWithVaultError vaultError: CorePayments.CoreSDKError) { - failure?(cardClient, vaultError) - } - - func cardThreeDSecureDidCancel(_ cardClient: CardClient) { - cancel?(cardClient) - } - - func cardThreeDSecureWillLaunch(_ cardClient: CardClient) { - threeDSWillLaunch?(cardClient) - } - - func cardThreeDSecureDidFinish(_ cardClient: CardClient) { - threeDSLaunched?(cardClient) - } -} diff --git a/UnitTests/PayPalWebPaymentsTests/Mocks/MockPayPalVaultDelegate.swift b/UnitTests/PayPalWebPaymentsTests/Mocks/MockPayPalVaultDelegate.swift deleted file mode 100644 index 7b8fc6ea3..000000000 --- a/UnitTests/PayPalWebPaymentsTests/Mocks/MockPayPalVaultDelegate.swift +++ /dev/null @@ -1,37 +0,0 @@ -@testable import CorePayments -@testable import PayPalWebPayments - -class MockPayPalVaultDelegate: PayPalVaultDelegate { - - private var success: ((PayPalWebCheckoutClient, PayPalVaultResult) -> Void)? - private var failure: ((PayPalWebCheckoutClient, CoreSDKError) -> Void)? - private var cancel: ((PayPalWebCheckoutClient) -> Void)? - - required init( - success: ((PayPalWebCheckoutClient, PayPalVaultResult) -> Void)? = nil, - error: ((PayPalWebCheckoutClient, CoreSDKError) -> Void)? = nil, - cancel: ((PayPalWebCheckoutClient) -> Void)? = nil - ) { - self.success = success - self.failure = error - self.cancel = cancel - } - - func paypal( - _ paypalWebClient: PayPalWebPayments.PayPalWebCheckoutClient, - didFinishWithVaultResult paypalVaultResult: PayPalVaultResult - ) { - success?(paypalWebClient, paypalVaultResult) - } - - func paypal( - _ paypalWebClient: PayPalWebPayments.PayPalWebCheckoutClient, - didFinishWithVaultError vaultError: CorePayments.CoreSDKError - ) { - failure?(paypalWebClient, vaultError) - } - - func paypalDidCancel(_ paypalWebClient: PayPalWebPayments.PayPalWebCheckoutClient) { - cancel?(paypalWebClient) - } -} diff --git a/UnitTests/PayPalWebPaymentsTests/Mocks/MockPayPalWebDelegate.swift b/UnitTests/PayPalWebPaymentsTests/Mocks/MockPayPalWebDelegate.swift deleted file mode 100644 index a92bb94dc..000000000 --- a/UnitTests/PayPalWebPaymentsTests/Mocks/MockPayPalWebDelegate.swift +++ /dev/null @@ -1,21 +0,0 @@ -import CorePayments -import PayPalWebPayments - -class MockPayPalWebDelegate: PayPalWebCheckoutDelegate { - - var capturedResult: PayPalWebCheckoutResult? - var capturedError: CoreSDKError? - var paypalDidCancel = false - - func payPal(_ payPalClient: PayPalWebCheckoutClient, didFinishWithResult result: PayPalWebCheckoutResult) { - capturedResult = result - } - - func payPal(_ payPalClient: PayPalWebCheckoutClient, didFinishWithError error: CoreSDKError) { - capturedError = error - } - - func payPalDidCancel(_ payPalClient: PayPalWebCheckoutClient) { - paypalDidCancel = true - } -} diff --git a/UnitTests/PayPalWebPaymentsTests/PayPalWebCheckoutClient_Tests.swift b/UnitTests/PayPalWebPaymentsTests/PayPalWebCheckoutClient_Tests.swift index 8c4fe553b..6b43fa9b2 100644 --- a/UnitTests/PayPalWebPaymentsTests/PayPalWebCheckoutClient_Tests.swift +++ b/UnitTests/PayPalWebPaymentsTests/PayPalWebCheckoutClient_Tests.swift @@ -26,8 +26,8 @@ class PayPalClient_Tests: XCTestCase { func testVault_whenSandbox_launchesCorrectURLInWebSession() { let vaultRequest = PayPalVaultRequest(setupTokenID: "fake-token") - payPalClient.vault(vaultRequest) - + payPalClient.vault(vaultRequest) { _, _ in } + XCTAssertEqual(mockWebAuthenticationSession.lastLaunchedURL?.absoluteString, "https://sandbox.paypal.com/agreements/approve?approval_session_id=fake-token") } @@ -40,8 +40,8 @@ class PayPalClient_Tests: XCTestCase { ) let vaultRequest = PayPalVaultRequest(setupTokenID: "fake-token") - payPalClient.vault(vaultRequest) - + payPalClient.vault(vaultRequest) { _, _ in } + XCTAssertEqual(mockWebAuthenticationSession.lastLaunchedURL?.absoluteString, "https://paypal.com/agreements/approve?approval_session_id=fake-token") } @@ -53,18 +53,16 @@ class PayPalClient_Tests: XCTestCase { let expectedTokenIDResult = "fakeTokenID" let expectedSessionIDResult = "fakeSessionID" - let mockVaultDelegate = MockPayPalVaultDelegate(success: {_, result in - XCTAssertEqual(expectedTokenIDResult, result.tokenID) - XCTAssertEqual(expectedSessionIDResult, result.approvalSessionID) - expectation.fulfill() - }, error: {_, _ in - XCTFail("Invoked error() callback. Should invoke success().") - }) - payPalClient.vaultDelegate = mockVaultDelegate + let vaultRequest = PayPalVaultRequest(setupTokenID: "fakeTokenID") - payPalClient.vault(vaultRequest) + payPalClient.vault(vaultRequest) { result, error in + XCTAssertEqual(expectedTokenIDResult, result?.tokenID) + XCTAssertEqual(expectedSessionIDResult, result?.approvalSessionID) + XCTAssertNil(error) + expectation.fulfill() + } - waitForExpectations(timeout: 10) + waitForExpectations(timeout: 2, handler: nil) } func testVault_whenWebSession_cancelled() { @@ -76,17 +74,18 @@ class PayPalClient_Tests: XCTestCase { let expectation = expectation(description: "vault(url:) completed") - let mockVaultDelegate = MockPayPalVaultDelegate(success: {_, _ in - XCTFail("Invoked success callback. Should invoke cancel().") - }, error: {_, _ in - XCTFail("Invoked error() callback. Should invoke success().") - }, cancel: { _ in - XCTAssert(true) - expectation.fulfill() - }) - payPalClient.vaultDelegate = mockVaultDelegate let vaultRequest = PayPalVaultRequest(setupTokenID: "fakeTokenID") - payPalClient.vault(vaultRequest) + payPalClient.vault(vaultRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.domain, PayPalWebCheckoutClientError.domain) + XCTAssertEqual(error.code, PayPalWebCheckoutClientError.Code.payPalVaultCancellationError.rawValue) + XCTAssertEqual(error.localizedDescription, "PayPal vault has been cancelled by the user") + } else { + XCTFail("Expected error to be of type CoreSDKError") + } + expectation.fulfill() + } waitForExpectations(timeout: 10) } @@ -102,17 +101,17 @@ class PayPalClient_Tests: XCTestCase { let expectation = expectation(description: "vault(url:) completed") - let mockVaultDelegate = MockPayPalVaultDelegate(success: {_, _ in - XCTFail("Invoked success callback. Should invoke error().") - }, error: {_, vaultError in - XCTAssertEqual(vaultError.code, expectedError.code) - expectation.fulfill() - }, cancel: { _ in - XCTFail("Invoked cancel callback. Should invoke error().") - }) - payPalClient.vaultDelegate = mockVaultDelegate let vaultRequest = PayPalVaultRequest(setupTokenID: "fakeTokenID") - payPalClient.vault(vaultRequest) + payPalClient.vault(vaultRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.domain, expectedError.domain) + XCTAssertEqual(error.code, expectedError.code) + } else { + XCTFail("Expected error to be of type CoreSDKError") + } + expectation.fulfill() + } waitForExpectations(timeout: 10) } @@ -129,24 +128,24 @@ class PayPalClient_Tests: XCTestCase { errorDescription: PayPalWebCheckoutClientError.payPalVaultResponseError.errorDescription ) - let mockVaultDelegate = MockPayPalVaultDelegate(success: {_, _ in - XCTFail("Invoked success() callback. Should invoke error().") - }, error: {_, vaultError in - XCTAssertEqual(vaultError.code, expectedError.code) - expectation.fulfill() - }) - payPalClient.vaultDelegate = mockVaultDelegate let vaultRequest = PayPalVaultRequest(setupTokenID: "fakeTokenID") - payPalClient.vault(vaultRequest) + payPalClient.vault(vaultRequest) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.domain, expectedError.domain) + XCTAssertEqual(error.code, expectedError.code) + } else { + XCTFail("Expected error to be of type CoreSDKError") + } + expectation.fulfill() + } waitForExpectations(timeout: 10) } - func testStart_whenNativeSDKOnCancelCalled_returnsCancellationError() { + func testStart_whenWebAuthenticationSessionCancelCalled_returnsCancellationError() { let request = PayPalWebCheckoutRequest(orderID: "1234") - let delegate = MockPayPalWebDelegate() - payPalClient.delegate = delegate mockWebAuthenticationSession.cannedErrorResponse = ASWebAuthenticationSessionError( _bridgedNSError: NSError( domain: ASWebAuthenticationSessionError.errorDomain, @@ -155,58 +154,77 @@ class PayPalClient_Tests: XCTestCase { ) ) - payPalClient.start(request: request) + let expectation = self.expectation(description: "Call back invoked with error") + payPalClient.start(request: request) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.domain, PayPalWebCheckoutClientError.domain) + XCTAssertEqual(error.code, PayPalWebCheckoutClientError.payPalCancellationError.code) + XCTAssertEqual(error.localizedDescription, PayPalWebCheckoutClientError.payPalCancellationError.localizedDescription) + } else { + XCTFail("Expected error to be of type CoreSDKError") + } + expectation.fulfill() + } - XCTAssertTrue(delegate.paypalDidCancel) + waitForExpectations(timeout: 2, handler: nil) } func testStart_whenWebAuthenticationSessions_returnsWebSessionError() { let request = PayPalWebCheckoutRequest(orderID: "1234") - let delegate = MockPayPalWebDelegate() - payPalClient.delegate = delegate mockWebAuthenticationSession.cannedErrorResponse = CoreSDKError( code: PayPalWebCheckoutClientError.Code.webSessionError.rawValue, domain: PayPalWebCheckoutClientError.domain, errorDescription: "Mock web session error description." ) - payPalClient.start(request: request) - - let error = delegate.capturedError - - XCTAssertEqual(error?.domain, PayPalWebCheckoutClientError.domain) - XCTAssertEqual(error?.code, PayPalWebCheckoutClientError.Code.webSessionError.rawValue) - XCTAssertEqual(error?.localizedDescription, "Mock web session error description.") + let expectation = self.expectation(description: "Call back invoked with error") + payPalClient.start(request: request) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.domain, PayPalWebCheckoutClientError.domain) + XCTAssertEqual(error.code, PayPalWebCheckoutClientError.Code.webSessionError.rawValue) + XCTAssertEqual(error.localizedDescription, "Mock web session error description.") + } else { + XCTFail("Expected error to be of type CoreSDKError") + } + expectation.fulfill() + } + waitForExpectations(timeout: 2, handler: nil) } func testStart_whenResultURLMissingParameters_returnsMalformedResultError() { let request = PayPalWebCheckoutRequest(orderID: "1234") - let delegate = MockPayPalWebDelegate() - payPalClient.delegate = delegate mockWebAuthenticationSession.cannedResponseURL = URL(string: "https://fakeURL?PayerID=98765") - payPalClient.start(request: request) - - let error = delegate.capturedError - - XCTAssertEqual(error?.domain, PayPalWebCheckoutClientError.domain) - XCTAssertEqual(error?.code, PayPalWebCheckoutClientError.Code.malformedResultError.rawValue) - XCTAssertEqual(error?.localizedDescription, "Result did not contain the expected data.") + let expectation = self.expectation(description: "Call back invoked with error") + payPalClient.start(request: request) { result, error in + XCTAssertNil(result) + if let error = error as? CoreSDKError { + XCTAssertEqual(error.domain, PayPalWebCheckoutClientError.domain) + XCTAssertEqual(error.code, PayPalWebCheckoutClientError.Code.malformedResultError.rawValue) + XCTAssertEqual(error.localizedDescription, "Result did not contain the expected data.") + } else { + XCTFail("Expected error to be of type CoreSDKError") + } + expectation.fulfill() + } + waitForExpectations(timeout: 2, handler: nil) } func testStart_whenWebResultIsSuccessful_returnsSuccessfulResult() { let request = PayPalWebCheckoutRequest(orderID: "1234") - let delegate = MockPayPalWebDelegate() - payPalClient.delegate = delegate mockWebAuthenticationSession.cannedResponseURL = URL(string: "https://fakeURL?token=1234&PayerID=98765") - payPalClient.start(request: request) - - let result = delegate.capturedResult - - XCTAssertEqual(result?.orderID, "1234") - XCTAssertEqual(result?.payerID, "98765") + let expectation = self.expectation(description: "Call back invoked with error") + payPalClient.start(request: request) { result, error in + XCTAssertEqual(result?.orderID, "1234") + XCTAssertEqual(result?.payerID, "98765") + XCTAssertNil(error) + expectation.fulfill() + } + waitForExpectations(timeout: 2, handler: nil) } func testpayPalCheckoutReturnURL_returnsCorrectURL() {