From c7a789d12d503410393ca4995732e7e2547cf12c Mon Sep 17 00:00:00 2001 From: GMALKHA Date: Fri, 6 Dec 2024 13:13:48 -0800 Subject: [PATCH] [DTPP-142] PayPalWebPayments show stages of order (#311) * [DTPP-142] PayPalWebPayments show stages of order --- Demo/Demo.xcodeproj/project.pbxproj | 32 ++++- .../PayPalWebButtonsView.swift | 2 +- .../PayPalWebCreateOrderView.swift | 2 +- .../PayPalWebPaymentsView.swift | 33 ++--- .../PayPalApprovalResultView.swift | 39 ++++++ .../PayPalOrderCompletionResultView.swift | 51 ++++++++ .../PayPalOrderCreateResultView.swift} | 27 ++-- .../PayPalWebTransactionView.swift | 3 +- .../PayPalPaymentState.swift | 49 ++++++++ .../PayPalWebViewModel.swift | 116 ++++++++++++------ 10 files changed, 279 insertions(+), 75 deletions(-) create mode 100644 Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalApprovalResultView.swift create mode 100644 Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalOrderCompletionResultView.swift rename Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/{PayPalWebResultView.swift => PayPalWebResultViews/PayPalOrderCreateResultView.swift} (58%) rename Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/{ => PayPalWebResultViews}/PayPalWebTransactionView.swift (82%) create mode 100644 Demo/Demo/PayPalWebPayments/PayPalWebViewModel/PayPalPaymentState.swift diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index 01e44b68e..6b593e4ca 100644 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 1001E2C12CFFC72E0023A03C /* PayPalPaymentState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1001E2C02CFFC72E0023A03C /* PayPalPaymentState.swift */; }; + 1001E2C52CFFD2800023A03C /* PayPalApprovalResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1001E2C42CFFD2800023A03C /* PayPalApprovalResultView.swift */; }; + 1001E2C72CFFD2A30023A03C /* PayPalOrderCompletionResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1001E2C62CFFD2A20023A03C /* PayPalOrderCompletionResultView.swift */; }; 3B20273D2A89E3F00007907E /* CreateSetupTokenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B20273C2A89E3F00007907E /* CreateSetupTokenView.swift */; }; 3B20273F2A89F24E0007907E /* CardVaultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B20273E2A89F24E0007907E /* CardVaultViewModel.swift */; }; 3B2027412A8A72050007907E /* VaultState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2027402A8A72050007907E /* VaultState.swift */; }; @@ -67,7 +70,7 @@ BE1766B326F911A2007EF438 /* URLResponseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE1766B226F911A2007EF438 /* URLResponseError.swift */; }; BE1766D926FA7BC8007EF438 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = BE1766D826FA7BC8007EF438 /* Settings.bundle */; }; BE5898952B2B91F800AA196E /* LabelViewText.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5898942B2B91F800AA196E /* LabelViewText.swift */; }; - BE8117642B07E778009867B9 /* PayPalWebResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE8117632B07E778009867B9 /* PayPalWebResultView.swift */; }; + BE8117642B07E778009867B9 /* PayPalOrderCreateResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE8117632B07E778009867B9 /* PayPalOrderCreateResultView.swift */; }; BE8117682B080472009867B9 /* CurrentState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE8117672B080472009867B9 /* CurrentState.swift */; }; BE9F36D82745490400AFC7DA /* FloatingLabelTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE9F36D72745490400AFC7DA /* FloatingLabelTextField.swift */; }; BECD84A027036DC2007CCAE4 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = BECD849F27036DC2007CCAE4 /* Environment.swift */; }; @@ -118,6 +121,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 1001E2C02CFFC72E0023A03C /* PayPalPaymentState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalPaymentState.swift; sourceTree = ""; }; + 1001E2C42CFFD2800023A03C /* PayPalApprovalResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalApprovalResultView.swift; sourceTree = ""; }; + 1001E2C62CFFD2A20023A03C /* PayPalOrderCompletionResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalOrderCompletionResultView.swift; sourceTree = ""; }; 3B20273C2A89E3F00007907E /* CreateSetupTokenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSetupTokenView.swift; sourceTree = ""; }; 3B20273E2A89F24E0007907E /* CardVaultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardVaultViewModel.swift; sourceTree = ""; }; 3B2027402A8A72050007907E /* VaultState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultState.swift; sourceTree = ""; }; @@ -186,7 +192,7 @@ BE1766D826FA7BC8007EF438 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; BE420F3628189A7A00D8D66A /* PayPalUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PayPalUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BE5898942B2B91F800AA196E /* LabelViewText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelViewText.swift; sourceTree = ""; }; - BE8117632B07E778009867B9 /* PayPalWebResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalWebResultView.swift; sourceTree = ""; }; + BE8117632B07E778009867B9 /* PayPalOrderCreateResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalOrderCreateResultView.swift; sourceTree = ""; }; BE8117672B080472009867B9 /* CurrentState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentState.swift; sourceTree = ""; }; BE9F36D72745490400AFC7DA /* FloatingLabelTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingLabelTextField.swift; sourceTree = ""; }; BECD849F27036DC2007CCAE4 /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; @@ -267,6 +273,7 @@ isa = PBXGroup; children = ( 3BA56FFB2A9FEFE90081D14F /* PayPalWebViewModel.swift */, + 1001E2C02CFFC72E0023A03C /* PayPalPaymentState.swift */, ); path = PayPalWebViewModel; sourceTree = ""; @@ -314,6 +321,17 @@ path = Vault; sourceTree = ""; }; + 1001E2CB2D03836D0023A03C /* PayPalWebResultViews */ = { + isa = PBXGroup; + children = ( + BE8117632B07E778009867B9 /* PayPalOrderCreateResultView.swift */, + 1001E2C42CFFD2800023A03C /* PayPalApprovalResultView.swift */, + 3B6472A62AFAEB3A004745C4 /* PayPalWebTransactionView.swift */, + 1001E2C62CFFD2A20023A03C /* PayPalOrderCompletionResultView.swift */, + ); + path = PayPalWebResultViews; + sourceTree = ""; + }; 3B2501052B2679F000903EAB /* VaultViews */ = { isa = PBXGroup; children = ( @@ -353,11 +371,10 @@ 3BA56FFF2A9FF6630081D14F /* PayPalWebPaymentsView */ = { isa = PBXGroup; children = ( + 1001E2CB2D03836D0023A03C /* PayPalWebResultViews */, + 3BA570002AA052E80081D14F /* PayPalWebPaymentsView.swift */, 3BA570062AA0DF330081D14F /* PayPalWebButtonsView.swift */, 3BA570022AA053AE0081D14F /* PayPalWebCreateOrderView.swift */, - 3BA570002AA052E80081D14F /* PayPalWebPaymentsView.swift */, - BE8117632B07E778009867B9 /* PayPalWebResultView.swift */, - 3B6472A62AFAEB3A004745C4 /* PayPalWebTransactionView.swift */, ); path = PayPalWebPaymentsView; sourceTree = ""; @@ -642,7 +659,7 @@ 3B2027412A8A72050007907E /* VaultState.swift in Sources */, 80F33CED26F8E7A9006811B1 /* Order.swift in Sources */, 3B4DD9A02A892A7000F4A716 /* CardVaultView.swift in Sources */, - BE8117642B07E778009867B9 /* PayPalWebResultView.swift in Sources */, + BE8117642B07E778009867B9 /* PayPalOrderCreateResultView.swift in Sources */, 3BA56FF62A9E9AAB0081D14F /* CardOrderActionButton.swift in Sources */, 3BC622092A97198500251B85 /* LeadingText.swift in Sources */, 3B80D5102A291CB100D2EAC4 /* ClientIDResponse.swift in Sources */, @@ -657,6 +674,7 @@ 3B2027432A8A95EF0007907E /* SetupTokenResultView.swift in Sources */, 3BF999762A8AC093009CBDF2 /* UpdateSetupTokenResultView.swift in Sources */, BECD84A027036DC2007CCAE4 /* Environment.swift in Sources */, + 1001E2C12CFFC72E0023A03C /* PayPalPaymentState.swift in Sources */, 3BCCFE4B2A9D985F00C5102F /* FeatureSelectionView.swift in Sources */, CB9ED44C283FDA900081F4DE /* PaymentButtonEnums+Extension.swift in Sources */, 3BB60B552B1FA00C00A298CF /* PayPalVaultViewModel.swift in Sources */, @@ -664,10 +682,12 @@ 3B80D50E2A291C0800D2EAC4 /* ClientIDRequest.swift in Sources */, 3BA56FF02A9DCCFD0081D14F /* CardOrderApproveView.swift in Sources */, BE8117682B080472009867B9 /* CurrentState.swift in Sources */, + 1001E2C72CFFD2A30023A03C /* PayPalOrderCompletionResultView.swift in Sources */, 3BDB348E2A7CB02C008100D7 /* CreateSetupTokenParam.swift in Sources */, 3BA0A58B2B1E240300330681 /* VaultViewModel.swift in Sources */, 80F33CF326F8EA50006811B1 /* DemoSettings.swift in Sources */, 3BA56FE72A9DC9D70081D14F /* CardPaymentViewModel.swift in Sources */, + 1001E2C52CFFD2800023A03C /* PayPalApprovalResultView.swift in Sources */, 3BA5700B2AA13C1C0081D14F /* CoreConfigManager.swift in Sources */, 80F33CE826F8DE29006811B1 /* DemoMerchantAPI.swift in Sources */, 80F33CEF26F8E7CC006811B1 /* CreateOrderParams.swift in Sources */, diff --git a/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebButtonsView.swift b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebButtonsView.swift index 03ad1a02b..bac812076 100644 --- a/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebButtonsView.swift +++ b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebButtonsView.swift @@ -39,7 +39,7 @@ struct PayPalWebButtonsView: View { payPalWebViewModel.paymentButtonTapped(funding: .paypal) } } - if payPalWebViewModel.state == .loading && + if payPalWebViewModel.state.approveResultResponse == .loading && payPalWebViewModel.checkoutResult == nil && payPalWebViewModel.orderID != nil { CircularProgressView() diff --git a/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebCreateOrderView.swift b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebCreateOrderView.swift index 4b5adc2dd..821cdd6cb 100644 --- a/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebCreateOrderView.swift +++ b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebCreateOrderView.swift @@ -40,7 +40,7 @@ struct PayPalWebCreateOrderView: View { } } .buttonStyle(RoundedBlueButtonStyle()) - if payPalWebViewModel.state == .loading && payPalWebViewModel.checkoutResult == nil && payPalWebViewModel.orderID == nil { + if case .loading = payPalWebViewModel.state.createdOrderResponse { CircularProgressView() } } diff --git a/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebPaymentsView.swift b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebPaymentsView.swift index 3b47ac991..533eefd76 100644 --- a/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebPaymentsView.swift +++ b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebPaymentsView.swift @@ -1,31 +1,36 @@ import SwiftUI struct PayPalWebPaymentsView: View { - + @StateObject var payPalWebViewModel = PayPalWebViewModel() - + var body: some View { ScrollView { ScrollViewReader { scrollView in VStack(spacing: 16) { + PayPalWebCreateOrderView(payPalWebViewModel: payPalWebViewModel) - - if payPalWebViewModel.orderID != nil { + + if case .loaded = payPalWebViewModel.state.createdOrderResponse { + PayPalOrderCreateResultView(payPalWebViewModel: payPalWebViewModel) + PayPalWebButtonsView(payPalWebViewModel: payPalWebViewModel) } - - PayPalWebResultView(payPalWebViewModel: payPalWebViewModel) - - if payPalWebViewModel.checkoutResult != nil { + + if case .loaded = payPalWebViewModel.state.approveResultResponse { + PayPalApprovalResultView(payPalWebViewModel: payPalWebViewModel) + PayPalWebTransactionView(payPalWebViewModel: payPalWebViewModel) .padding(.bottom, 20) - .id("bottomView") - .onAppear { - withAnimation { - scrollView.scrollTo("bottomView") - } - } } + + if case .loaded = payPalWebViewModel.state.capturedOrderResponse { + PayPalOrderCompletionResultView(payPalWebViewModel: payPalWebViewModel) + } else if case .loaded = payPalWebViewModel.state.authorizedOrderResponse { + PayPalOrderCompletionResultView(payPalWebViewModel: payPalWebViewModel) + } + Text("") + .id("bottomView") } .onChange(of: payPalWebViewModel.state) { _ in withAnimation { diff --git a/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalApprovalResultView.swift b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalApprovalResultView.swift new file mode 100644 index 000000000..8971283d9 --- /dev/null +++ b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalApprovalResultView.swift @@ -0,0 +1,39 @@ +import SwiftUI + +struct PayPalApprovalResultView: View { + + @ObservedObject var payPalWebViewModel: PayPalWebViewModel + + var body: some View { + switch payPalWebViewModel.state.approveResultResponse { + case .idle, .loading: + EmptyView() + case .error(let message): + ErrorView(errorMessage: message) + case .loaded(let approvalResult): + getApprovalSuccessView(approvalResult: approvalResult) + } + } + + func getApprovalSuccessView(approvalResult: PayPalPaymentState.ApprovalResult) -> some View { + VStack(alignment: .leading, spacing: 16) { + Text("Approval Result") + .font(.headline) + LeadingText("Approval ID", weight: .bold) + .font(.system(size: 20)) + + LeadingText(approvalResult.id) + if let status = approvalResult.status { + LeadingText("Status", weight: .bold) + LeadingText(status) + } + } + .frame(maxWidth: .infinity) + .padding() + .background( + RoundedRectangle(cornerRadius: 10) + .stroke(.gray, lineWidth: 2) + .padding(5) + ) + } +} diff --git a/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalOrderCompletionResultView.swift b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalOrderCompletionResultView.swift new file mode 100644 index 000000000..2f1549196 --- /dev/null +++ b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalOrderCompletionResultView.swift @@ -0,0 +1,51 @@ +import SwiftUI + +struct PayPalOrderCompletionResultView: View { + + @ObservedObject var payPalWebViewModel: PayPalWebViewModel + + var body: some View { + VStack { + if case .loaded(let authorizedOrder) = payPalWebViewModel.state.authorizedOrderResponse { + getCompletionSuccessView(order: authorizedOrder, intent: "Authorized") + } + if case .loaded(let capturedOrder) = payPalWebViewModel.state.capturedOrderResponse { + getCompletionSuccessView(order: capturedOrder, intent: "Captured") + } + } + } + + func getCompletionSuccessView(order: Order, intent: String) -> some View { + VStack(alignment: .leading, spacing: 16) { + Text("Order \(intent) Successfully") + .font(.system(size: 20)) + + LabelViewText("Order ID:", bodyText: order.id) + + LabelViewText("Status:", bodyText: order.status) + + if let payerID = payPalWebViewModel.checkoutResult?.payerID { + LabelViewText("Payer ID:", bodyText: payerID) + } + + if let emailAddress = order.paymentSource?.paypal?.emailAddress { + LabelViewText("Email:", bodyText: emailAddress) + } + + if let vaultID = order.paymentSource?.paypal?.attributes?.vault.id { + LabelViewText("Payment Token:", bodyText: vaultID) + } + + if let customerID = order.paymentSource?.paypal?.attributes?.vault.customer?.id { + LabelViewText("Customer ID:", bodyText: customerID) + } + } + .frame(maxWidth: .infinity) + .padding() + .background( + RoundedRectangle(cornerRadius: 10) + .stroke(.gray, lineWidth: 2) + .padding(5) + ) + } +} diff --git a/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultView.swift b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalOrderCreateResultView.swift similarity index 58% rename from Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultView.swift rename to Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalOrderCreateResultView.swift index e1d9f744a..a9e693fb6 100644 --- a/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultView.swift +++ b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalOrderCreateResultView.swift @@ -1,48 +1,45 @@ import SwiftUI -struct PayPalWebResultView: View { +struct PayPalOrderCreateResultView: View { @ObservedObject var payPalWebViewModel: PayPalWebViewModel var body: some View { - switch payPalWebViewModel.state { + switch payPalWebViewModel.state.createdOrderResponse { case .idle, .loading: EmptyView() - case .success: - successView + case .loaded(let createOrderResponse): + getSuccessView(createOrderResponse: createOrderResponse) case .error(let errorMessage): ErrorView(errorMessage: errorMessage) } } - var successView: some View { + func getSuccessView(createOrderResponse: Order) -> some View { VStack(alignment: .leading, spacing: 16) { HStack { Text("Order Details") .font(.system(size: 20)) Spacer() } - if let orderID = payPalWebViewModel.orderID { - LabelViewText("Order ID:", bodyText: orderID) - } - - if let status = payPalWebViewModel.order?.status { - LabelViewText("Status:", bodyText: status) - } + + LabelViewText("Order ID:", bodyText: createOrderResponse.id) + + LabelViewText("Status:", bodyText: createOrderResponse.status) if let payerID = payPalWebViewModel.checkoutResult?.payerID { LabelViewText("Payer ID:", bodyText: payerID) } - if let emailAddress = payPalWebViewModel.order?.paymentSource?.paypal?.emailAddress { + if let emailAddress = createOrderResponse.paymentSource?.paypal?.emailAddress { LabelViewText("Email:", bodyText: emailAddress) } - if let vaultID = payPalWebViewModel.order?.paymentSource?.paypal?.attributes?.vault.id { + if let vaultID = createOrderResponse.paymentSource?.paypal?.attributes?.vault.id { LabelViewText("Payment Token:", bodyText: vaultID) } - if let customerID = payPalWebViewModel.order?.paymentSource?.paypal?.attributes?.vault.customer?.id { + if let customerID = createOrderResponse.paymentSource?.paypal?.attributes?.vault.customer?.id { LabelViewText("Customer ID:", bodyText: customerID) } } diff --git a/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebTransactionView.swift b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalWebTransactionView.swift similarity index 82% rename from Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebTransactionView.swift rename to Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalWebTransactionView.swift index e8b0a132e..965509ab9 100644 --- a/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebTransactionView.swift +++ b/Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalWebTransactionView.swift @@ -19,7 +19,8 @@ struct PayPalWebTransactionView: View { .buttonStyle(RoundedBlueButtonStyle()) .padding() - if payPalWebViewModel.state == .loading { + if payPalWebViewModel.state.capturedOrderResponse == .loading || + payPalWebViewModel.state.authorizedOrderResponse == .loading { CircularProgressView() } } diff --git a/Demo/Demo/PayPalWebPayments/PayPalWebViewModel/PayPalPaymentState.swift b/Demo/Demo/PayPalWebPayments/PayPalWebViewModel/PayPalPaymentState.swift new file mode 100644 index 000000000..7734ed63a --- /dev/null +++ b/Demo/Demo/PayPalWebPayments/PayPalWebViewModel/PayPalPaymentState.swift @@ -0,0 +1,49 @@ +import Foundation +import PayPalWebPayments + +struct PayPalPaymentState: Equatable { + + struct ApprovalResult: Decodable, Equatable { + + let id: String + let status: String? + } + + var createOrder: Order? + var authorizedOrder: Order? + var capturedOrder: Order? + var intent: Intent = .authorize + var approveResult: ApprovalResult? + + var createdOrderResponse: LoadingState = .idle { + didSet { + if case .loaded(let value) = createdOrderResponse { + createOrder = value + } + } + } + + var approveResultResponse: LoadingState = .idle { + didSet { + if case .loaded(let value) = approveResultResponse { + approveResult = value + } + } + } + + var capturedOrderResponse: LoadingState = .idle { + didSet { + if case .loaded(let value) = capturedOrderResponse { + capturedOrder = value + } + } + } + + var authorizedOrderResponse: LoadingState = .idle { + didSet { + if case .loaded(let value) = authorizedOrderResponse { + authorizedOrder = value + } + } + } +} diff --git a/Demo/Demo/PayPalWebPayments/PayPalWebViewModel/PayPalWebViewModel.swift b/Demo/Demo/PayPalWebPayments/PayPalWebViewModel/PayPalWebViewModel.swift index 0381d4338..77eeead06 100644 --- a/Demo/Demo/PayPalWebPayments/PayPalWebViewModel/PayPalWebViewModel.swift +++ b/Demo/Demo/PayPalWebPayments/PayPalWebViewModel/PayPalWebViewModel.swift @@ -5,7 +5,7 @@ import FraudProtection class PayPalWebViewModel: ObservableObject { - @Published var state: CurrentState = .idle + @Published var state = PayPalPaymentState() @Published var intent: Intent = .authorize @Published var order: Order? @Published var checkoutResult: PayPalWebCheckoutResult? @@ -27,7 +27,13 @@ class PayPalWebViewModel: ObservableObject { if shouldVault { let attributes = Attributes(vault: Vault(storeInVault: "ON_SUCCESS", usageType: "MERCHANT", customerType: "CONSUMER")) // The returnURL is not used in our mobile SDK, but a required field for create order with PayPal payment source. DTPPCPSDK-1492 to track this issue - let paypal = VaultPayPal(attributes: attributes, experienceContext: ExperienceContext(returnURL: "https://example.com/returnUrl", cancelURL: "https://example.com/cancelUrl")) + let paypal = VaultPayPal( + attributes: attributes, + experienceContext: ExperienceContext( + returnURL: "https://example.com/returnUrl", + cancelURL: "https://example.com/cancelUrl" + ) + ) vaultPayPalPaymentSource = VaultPayPalPaymentSource(paypal: paypal) } @@ -44,17 +50,22 @@ class PayPalWebViewModel: ObservableObject { ) do { - updateState(.loading) + DispatchQueue.main.async { + self.state.createdOrderResponse = .loading + } let order = try await DemoMerchantAPI.sharedService.createOrder( orderParams: orderRequestParams, selectedMerchantIntegration: DemoSettings.merchantIntegration ) - - updateOrder(order) - updateState(.success) + DispatchQueue.main.async { + self.order = order + self.state.createdOrderResponse = .loaded(order) + } print("✅ fetched orderID: \(order.id) with status: \(order.status)") } catch { - updateState(.error(message: error.localizedDescription)) + DispatchQueue.main.async { + self.state.createdOrderResponse = .error(message: error.localizedDescription) + } print("❌ failed to fetch orderID with error: \(error.localizedDescription)") } } @@ -62,33 +73,43 @@ class PayPalWebViewModel: ObservableObject { func paymentButtonTapped(funding: PayPalWebCheckoutFundingSource) { Task { do { - self.updateState(.loading) + DispatchQueue.main.async { + self.state.approveResultResponse = .loading + } payPalWebCheckoutClient = try await getPayPalClient() guard let payPalWebCheckoutClient else { print("Error initializing PayPalWebCheckoutClient") return } - if let orderID { + if let orderID = state.createOrder?.id { let payPalRequest = PayPalWebCheckoutRequest(orderID: orderID, fundingSource: funding) payPalWebCheckoutClient.start(request: payPalRequest) { result, error in if let error { - if error == PayPalError.checkoutCanceledError { - print("Canceled") - self.updateState(.idle) - } else { - self.updateState(.error(message: error.localizedDescription)) + DispatchQueue.main.async { + if error == PayPalError.checkoutCanceledError { + print("Canceled") + self.state.approveResultResponse = .idle + } else { + self.state.approveResultResponse = .error(message: error.localizedDescription) + } } } else { - self.updateState(.success) - self.checkoutResult = result + DispatchQueue.main.async { + self.state.approveResultResponse = .loaded( + PayPalPaymentState.ApprovalResult(id: orderID, status: "APPROVED") + ) + self.checkoutResult = result + print("✅ Checkout result: \(String(describing: result))") + } } } } - updateState(.success) } catch { print("Error starting PayPalWebCheckoutClient") - updateState(.error(message: error.localizedDescription)) + DispatchQueue.main.async { + self.state.createdOrderResponse = .error(message: error.localizedDescription) + } } } } @@ -100,7 +121,9 @@ class PayPalWebViewModel: ObservableObject { payPalDataCollector = PayPalDataCollector(config: config) return payPalClient } catch { - updateState(.error(message: error.localizedDescription)) + DispatchQueue.main.async { + self.state.createdOrderResponse = .error(message: error.localizedDescription) + } print("❌ failed to create PayPalWebCheckoutClient with error: \(error.localizedDescription)") return nil } @@ -108,39 +131,58 @@ class PayPalWebViewModel: ObservableObject { func completeTransaction() async throws { do { - updateState(.loading) - - let payPalClientMetadataID = payPalDataCollector?.collectDeviceData() - if let orderID { + setLoadingState() + if let orderID = state.createOrder?.id { + let payPalClientMetadataID = payPalDataCollector?.collectDeviceData() let order = try await DemoMerchantAPI.sharedService.completeOrder( intent: intent, orderID: orderID, payPalClientMetadataID: payPalClientMetadataID ) - updateOrder(order) - updateState(.success) + setOrderCompletionLoadedState(order: order) } } catch { - updateState(.error(message: error.localizedDescription)) + setErrorState(message: error.localizedDescription) print("Error with \(intent) order: \(error.localizedDescription)") } } - - func resetState() { - updateState(.idle) - order = nil - checkoutResult = nil + + private func setLoadingState() { + DispatchQueue.main.async { + switch self.intent { + case .authorize: + self.state.authorizedOrderResponse = .loading + case .capture: + self.state.capturedOrderResponse = .loading + } + } } - - private func updateOrder(_ order: Order) { + + private func setOrderCompletionLoadedState(order: Order) { DispatchQueue.main.async { - self.order = order + switch self.intent { + case .authorize: + self.state.authorizedOrderResponse = .loaded(order) + case .capture: + self.state.capturedOrderResponse = .loaded(order) + } } } - - private func updateState(_ state: CurrentState) { + + private func setErrorState(message: String) { DispatchQueue.main.async { - self.state = state + switch self.intent { + case .authorize: + self.state.authorizedOrderResponse = .error(message: message) + case .capture: + self.state.capturedOrderResponse = .error(message: message) + } } } + + func resetState() { + self.state = PayPalPaymentState() + order = nil + checkoutResult = nil + } }