Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancel error handling #297

Merged
merged 7 commits into from
Nov 13, 2024
14 changes: 9 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,25 @@
* 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:completion(PayPalWebCheckoutResult?, CoreSDKError?) -> Void)` to `PayPalWebCheckoutClient`
* Add `vault(vaultRequest:completion(PayPalVaultResult?, CoreSDKError?) -> Void)` to `PayPalWebCheckoutClient`
* Add `start(request:) async throws -> PayPalCheckoutResult`
* Add `vault(vaultRequest:) async throws -> PayPalVaultResult`
* Add `.checkoutCanceled` and `.vaultCanceled` to `PayPalWebCheckoutClientError`
* Rename `PayPalWebCheckoutClientError` to `PayPalError`
* Add public static functions `isCheckoutCanceled(Error)` and `isVaultCanceled(Error)` to `PayPalError` to distinguish cancellation errors in PayPal flows.
* 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:completion:(CardResult?, CoreSDKError?) -> Void)` to `CardClient`
* Add `vault(request:completion:(CardVaultResult?, CoreSDKError?) -> Void)` to `CardClient`
* Add `approveOrder(request:) async throws -> CardResult`
* Add `vault(vaultRequest:) async throws -> CardVaultResult`
* Add `.threeDSecureCanceled` to `CardClientError`
* Add `.threeDSecureCanceled` to `CardClientError`
* Rename `PayPalClientError` to `PayPalError`
* Add public static function `isThreeDSecureCanceled(Error)` to `CardClientError` to distinguish cancellation error from threeDSecure verification
* PayPalWebPayments
* Deprecate `PayPalVaultRequest(url:setupTokenID:)`
* Add `PayPalVaultRequest(setupTokenID:)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,25 @@ struct PayPalWebButtonsView: View {
Text("Pay Later").tag(PayPalWebCheckoutFundingSource.paylater)
}
.pickerStyle(SegmentedPickerStyle())

switch selectedFundingSource {
case .paypalCredit:
PayPalCreditButton.Representable(color: .black, size: .full) {
payPalWebViewModel.paymentButtonTapped(funding: .paypalCredit)
}
case .paylater:
PayPalPayLaterButton.Representable(color: .silver, edges: .softEdges, size: .full) {
payPalWebViewModel.paymentButtonTapped(funding: .paylater)
ZStack {
switch selectedFundingSource {
case .paypalCredit:
PayPalCreditButton.Representable(color: .black, size: .full) {
payPalWebViewModel.paymentButtonTapped(funding: .paypalCredit)
}
case .paylater:
PayPalPayLaterButton.Representable(color: .silver, edges: .softEdges, size: .full) {
payPalWebViewModel.paymentButtonTapped(funding: .paylater)
}
case .paypal:
PayPalButton.Representable(color: .blue, size: .full) {
payPalWebViewModel.paymentButtonTapped(funding: .paypal)
}
}
case .paypal:
PayPalButton.Representable(color: .blue, size: .full) {
payPalWebViewModel.paymentButtonTapped(funding: .paypal)
if payPalWebViewModel.state == .loading &&
payPalWebViewModel.checkoutResult == nil &&
payPalWebViewModel.orderID != nil {
CircularProgressView()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct PayPalWebCreateOrderView: View {
}
}
.buttonStyle(RoundedBlueButtonStyle())
if payPalWebViewModel.state == .loading && payPalWebViewModel.checkoutResult == nil {
if payPalWebViewModel.state == .loading && payPalWebViewModel.checkoutResult == nil && payPalWebViewModel.orderID == nil {
CircularProgressView()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class PayPalWebViewModel: ObservableObject {
func paymentButtonTapped(funding: PayPalWebCheckoutFundingSource) {
Task {
do {
self.updateState(.loading)
payPalWebCheckoutClient = try await getPayPalClient()
guard let payPalWebCheckoutClient else {
print("Error initializing PayPalWebCheckoutClient")
Expand All @@ -72,7 +73,12 @@ class PayPalWebViewModel: ObservableObject {
let payPalRequest = PayPalWebCheckoutRequest(orderID: orderID, fundingSource: funding)
payPalWebCheckoutClient.start(request: payPalRequest) { result, error in
if let error {
self.updateState(.error(message: error.localizedDescription))
if PayPalError.isCheckoutCanceled(error) {
print("Canceled")
self.updateState(.idle)
} else {
self.updateState(.error(message: error.localizedDescription))
}
} else {
self.updateState(.success)
self.checkoutResult = result
Expand Down Expand Up @@ -137,22 +143,4 @@ class PayPalWebViewModel: ObservableObject {
self.state = state
}
}

// MARK: - PayPalWeb Checkout Delegate

func payPal(
_ payPalClient: PayPalWebCheckoutClient,
didFinishWithResult result: PayPalWebCheckoutResult
) {
updateState(.success)
checkoutResult = result
}

func payPal(_ payPalClient: PayPalWebCheckoutClient, didFinishWithError error: CoreSDKError) {
updateState(.error(message: error.localizedDescription))
}

func payPalDidCancel(_ payPalClient: PayPalWebCheckoutClient) {
print("PayPal Checkout Canceled")
}
}
46 changes: 11 additions & 35 deletions Demo/Demo/ViewModels/CardPaymentViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,13 @@ class CardPaymentViewModel: ObservableObject {
let cardRequest = CardRequest(orderID: orderID, card: card, sca: sca)
cardClient?.approveOrder(request: cardRequest) { result, error in
if let error {
self.setUpdateSetupTokenFailureResult(vaultError: error)
if CardError.isThreeDSecureCanceled(error) {
self.setApprovalCancelResult()
} else {
self.setApprovalFailureResult(vaultError: error)
}
} else if let result {
self.approveResultSuccessResult(
self.setApprovalSuccessResult(
approveResult: CardPaymentState.CardResult(
id: result.orderID,
status: result.status,
Expand All @@ -132,57 +136,29 @@ class CardPaymentViewModel: ObservableObject {
}
}
} catch {
setUpdateSetupTokenFailureResult(vaultError: error)
setApprovalFailureResult(vaultError: error)
print("failed in checkout with card. \(error.localizedDescription)")
}
}

func approveResultSuccessResult(approveResult: CardPaymentState.CardResult) {
func setApprovalSuccessResult(approveResult: CardPaymentState.CardResult) {
DispatchQueue.main.async {
self.state.approveResultResponse = .loaded(
approveResult
)
}
}

func setUpdateSetupTokenFailureResult(vaultError: Error) {
func setApprovalFailureResult(vaultError: Error) {
DispatchQueue.main.async {
self.state.approveResultResponse = .error(message: vaultError.localizedDescription)
}
}

// MARK: - Card Delegate

func card(_ cardClient: CardPayments.CardClient, didFinishWithResult result: CardPayments.CardResult) {
approveResultSuccessResult(
approveResult: CardPaymentState.CardResult(
id: result.orderID,
status: result.status,
didAttemptThreeDSecureAuthentication: result.didAttemptThreeDSecureAuthentication
)
)
}

func card(_ cardClient: CardPayments.CardClient, didFinishWithError error: CorePayments.CoreSDKError) {
print("Error here")
DispatchQueue.main.async {
self.state.approveResultResponse = .error(message: error.localizedDescription)
}
}

func cardDidCancel(_ cardClient: CardPayments.CardClient) {
print("Card Payment Canceled")
func setApprovalCancelResult() {
print("Canceled")
DispatchQueue.main.async {
self.state.approveResultResponse = .idle
self.state.approveResult = nil
}
}

func cardThreeDSecureWillLaunch(_ cardClient: CardPayments.CardClient) {
print("About to launch 3DS")
}

func cardThreeDSecureDidFinish(_ cardClient: CardPayments.CardClient) {
print("Finished 3DS")
}
}
7 changes: 6 additions & 1 deletion Demo/Demo/ViewModels/CardVaultViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ class CardVaultViewModel: VaultViewModel {
)
)
} else if let vaultError {
self.state.updateSetupTokenResponse = .error(message: vaultError.localizedDescription)
if CardError.isThreeDSecureCanceled(vaultError) {
print("Canceled")
self.state.updateSetupTokenResponse = .idle
} else {
self.state.updateSetupTokenResponse = .error(message: vaultError.localizedDescription)
}
}
}
}
Expand Down
11 changes: 9 additions & 2 deletions Demo/Demo/ViewModels/PayPalVaultViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ class PayPalVaultViewModel: VaultViewModel {
let vaultRequest = PayPalVaultRequest(setupTokenID: setupTokenID)
paypalClient.vault(vaultRequest) { result, error in
KunJeongPark marked this conversation as resolved.
Show resolved Hide resolved
if let error {
DispatchQueue.main.async {
self.state.paypalVaultTokenResponse = .error(message: error.localizedDescription)
if PayPalError.isVaultCanceled(error) {
DispatchQueue.main.async {
print("Canceled")
self.state.paypalVaultTokenResponse = .idle
}
} else {
DispatchQueue.main.async {
self.state.paypalVaultTokenResponse = .error(message: error.localizedDescription)
}
}
} else if let result {
DispatchQueue.main.async {
Expand Down
16 changes: 8 additions & 8 deletions PayPal.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
80B8B2FC2A8EBBFD00AB60CD /* VaultPaymentTokensAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B8B2FB2A8EBBFD00AB60CD /* VaultPaymentTokensAPI.swift */; };
80B96AAE2A980F6B00C62916 /* MockTrackingEventsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 802EFBD72A9685DF00AB709D /* MockTrackingEventsAPI.swift */; };
80DB2F762980795D00CFB86A /* CorePaymentsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DB2F752980795D00CFB86A /* CorePaymentsError.swift */; };
80DCC59E2719DB6F00EC7C5A /* CardClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DCC59D2719DB6F00EC7C5A /* CardClientError.swift */; };
80DCC59E2719DB6F00EC7C5A /* CardError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DCC59D2719DB6F00EC7C5A /* CardError.swift */; };
80E237DF2A84434B00FF18CA /* HTTPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E237DE2A84434B00FF18CA /* HTTPRequest.swift */; };
80E2FDBE2A83528B0045593D /* CheckoutOrdersAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E2FDBD2A83528B0045593D /* CheckoutOrdersAPI.swift */; };
80E2FDC12A83535A0045593D /* TrackingEventsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E2FDBF2A8353550045593D /* TrackingEventsAPI.swift */; };
Expand Down Expand Up @@ -104,7 +104,7 @@
BCFAC71F27ED04C800C3AF00 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEDB7FE32788AB8E00CEA554 /* Coordinator.swift */; };
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 */; };
BE4F785527EB656400FF4C0E /* PayPalError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE4F784427EB629100FF4C0E /* PayPalError.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 */; };
Expand Down Expand Up @@ -221,7 +221,7 @@
80B8B2FB2A8EBBFD00AB60CD /* VaultPaymentTokensAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultPaymentTokensAPI.swift; sourceTree = "<group>"; };
80B9F85126B8750000D67843 /* CorePayments.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CorePayments.framework; sourceTree = BUILT_PRODUCTS_DIR; };
80DB2F752980795D00CFB86A /* CorePaymentsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CorePaymentsError.swift; sourceTree = "<group>"; };
80DCC59D2719DB6F00EC7C5A /* CardClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardClientError.swift; sourceTree = "<group>"; };
80DCC59D2719DB6F00EC7C5A /* CardError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardError.swift; sourceTree = "<group>"; };
80E237DE2A84434B00FF18CA /* HTTPRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPRequest.swift; sourceTree = "<group>"; };
80E2FDBD2A83528B0045593D /* CheckoutOrdersAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckoutOrdersAPI.swift; sourceTree = "<group>"; };
80E2FDBF2A8353550045593D /* TrackingEventsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingEventsAPI.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -265,7 +265,7 @@
BE00B7AA2743FD9F00758C63 /* PaymentButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentButton.swift; sourceTree = "<group>"; };
BE00B7AC27444FE900758C63 /* PayPalButton_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalButton_Tests.swift; sourceTree = "<group>"; };
BE4F784327EB629100FF4C0E /* Environment+PayPalWebCheckout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Environment+PayPalWebCheckout.swift"; sourceTree = "<group>"; };
BE4F784427EB629100FF4C0E /* PayPalWebCheckoutClientError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayPalWebCheckoutClientError.swift; sourceTree = "<group>"; };
BE4F784427EB629100FF4C0E /* PayPalError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayPalError.swift; sourceTree = "<group>"; };
BE4F784527EB629100FF4C0E /* PayPalWebCheckoutRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayPalWebCheckoutRequest.swift; sourceTree = "<group>"; };
BE4F784627EB629100FF4C0E /* PayPalWebCheckoutClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayPalWebCheckoutClient.swift; sourceTree = "<group>"; };
BE4F784727EB629100FF4C0E /* PayPalWebCheckoutResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayPalWebCheckoutResult.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -535,7 +535,7 @@
isa = PBXGroup;
children = (
06CE009A26F3D5A40000CC46 /* CardClient.swift */,
80DCC59D2719DB6F00EC7C5A /* CardClientError.swift */,
80DCC59D2719DB6F00EC7C5A /* CardError.swift */,
80DBC9D829C336D500462539 /* APIRequests */,
065A4DBD26FCDA270007014A /* Models */,
3BE738622B9A482800598F05 /* PrivacyInfo.xcprivacy */,
Expand Down Expand Up @@ -639,7 +639,7 @@
children = (
BE4F784327EB629100FF4C0E /* Environment+PayPalWebCheckout.swift */,
BE4F784627EB629100FF4C0E /* PayPalWebCheckoutClient.swift */,
BE4F784427EB629100FF4C0E /* PayPalWebCheckoutClientError.swift */,
BE4F784427EB629100FF4C0E /* PayPalError.swift */,
BE4F784527EB629100FF4C0E /* PayPalWebCheckoutRequest.swift */,
BE4F784727EB629100FF4C0E /* PayPalWebCheckoutResult.swift */,
CB51FF7127FCB947001A97F5 /* PayPalWebCheckoutFundingSource.swift */,
Expand Down Expand Up @@ -1209,7 +1209,7 @@
80E8DAE126B8784600FAFC3F /* Card.swift in Sources */,
3B22E8B82A841AEA00962E34 /* CardVaultResult.swift in Sources */,
CBC16DD529E99B4600307117 /* PaymentSource.swift in Sources */,
80DCC59E2719DB6F00EC7C5A /* CardClientError.swift in Sources */,
80DCC59E2719DB6F00EC7C5A /* CardError.swift in Sources */,
3BDB34942A80CE6E008100D7 /* CardVaultRequest.swift in Sources */,
80B8B2FC2A8EBBFD00AB60CD /* VaultPaymentTokensAPI.swift in Sources */,
CB4BE27D2847AF6F00EA2DD1 /* SCA.swift in Sources */,
Expand Down Expand Up @@ -1241,7 +1241,7 @@
3B3C511E2B2395B5009125FE /* PayPalVaultResult.swift in Sources */,
BE4F785327EB656400FF4C0E /* Environment+PayPalWebCheckout.swift in Sources */,
BE4F785427EB656400FF4C0E /* PayPalWebCheckoutClient.swift in Sources */,
BE4F785527EB656400FF4C0E /* PayPalWebCheckoutClientError.swift in Sources */,
BE4F785527EB656400FF4C0E /* PayPalError.swift in Sources */,
BE4F785727EB656400FF4C0E /* PayPalWebCheckoutRequest.swift in Sources */,
BE4F785827EB656400FF4C0E /* PayPalWebCheckoutResult.swift in Sources */,
CB51FF7227FCB947001A97F5 /* PayPalWebCheckoutFundingSource.swift in Sources */,
Expand Down
Loading
Loading