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

PayPal Web Demo App Refactor #223

Merged
merged 32 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b34ed3f
consolidate order states in PayPal Web views
jaxdesmarais Nov 17, 2023
32c4b9b
add generic SuccessView
jaxdesmarais Nov 17, 2023
e327686
WIP - shared result view
jaxdesmarais Nov 17, 2023
9d956dd
WIP - start consolidating result views
jaxdesmarais Nov 17, 2023
bc4a9e3
WIP - continue refactoring state for PayPal Web
jaxdesmarais Nov 17, 2023
d685080
WIP - continued paypal web demo app refactor
jaxdesmarais Nov 20, 2023
8aed081
WIP - start cleaning up file names; method signatures; etc for PayPal…
jaxdesmarais Nov 20, 2023
4474449
cleanup how data is passed
jaxdesmarais Nov 20, 2023
72aaa7a
add new state and general cleanup
jaxdesmarais Nov 20, 2023
5a02090
cleanup funding source name, remove redundant types
jaxdesmarais Nov 20, 2023
9e8019b
cleanup views and remove order completion view
jaxdesmarais Nov 21, 2023
d96b216
rename PayPalWebCompleteTransactionView to PayPalWebTransactionView; …
jaxdesmarais Nov 21, 2023
994a3ac
update funding source
jaxdesmarais Nov 21, 2023
3dd128e
extract intent from view model directly
jaxdesmarais Nov 21, 2023
c01d5c5
minor cleanup
jaxdesmarais Nov 21, 2023
664f56c
sort paypal web files by name
jaxdesmarais Nov 21, 2023
e1faf50
cleanup authorize/capture order into singular method
jaxdesmarais Nov 21, 2023
a7b9b64
add logic to reset state; combine loaded and success state; extract u…
jaxdesmarais Dec 1, 2023
af90a4e
Merge branch 'main' into paypal-web-demo-app-refactor
jaxdesmarais Dec 1, 2023
5f95aa4
merge main into demo-app-refactor
jaxdesmarais Dec 1, 2023
ee4f5da
add ScrollView to PayPalWebTransactionView
jaxdesmarais Dec 1, 2023
6c30414
PR feedback: rename Status enum to OrderStatus
jaxdesmarais Dec 1, 2023
94f8f9b
PR feedback: rely on status instead or resetting state
jaxdesmarais Dec 1, 2023
bb5596f
update name of payPalViewModel to payPalWebViewModel
jaxdesmarais Dec 1, 2023
c2785ae
add scroll to bottom of PayPalWebTransactionView on vault with purchase
jaxdesmarais Dec 1, 2023
fa6cd08
update order status from started to created to more accurately refect…
jaxdesmarais Dec 1, 2023
0a3305e
update PayPalWebDemoView to PayPalWebPaymentsView
jaxdesmarais Dec 1, 2023
c137eb7
add navigationViewStyle
jaxdesmarais Dec 1, 2023
8cd8cd2
update delegate to use updateState
jaxdesmarais Dec 1, 2023
5a95765
move PayPalWebViewModel into PayPalWebPayments feature directory
jaxdesmarais Dec 1, 2023
af04b83
remove OrderStatus and add cases to CurrentState
jaxdesmarais Dec 4, 2023
518e8ec
refactor current state; add back in OrderStatus; fix bug with ErrorView
jaxdesmarais Dec 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 32 additions & 40 deletions Demo/Demo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Demo/Demo/Models/Order.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ struct Order: Codable, Equatable {

let id: String
let status: String
var paymentSource: PaymentSource?
let paymentSource: PaymentSource?
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't related to this PR but at one point I was poking around at this file and noticed we had this one as a variable - should be a constant to match all other structs in this file


struct PaymentSource: Codable, Equatable {

Expand Down
16 changes: 15 additions & 1 deletion Demo/Demo/Networking/DemoMerchantAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,21 @@ final class DemoMerchantAPI {
throw error
}
}


func completeOrder(intent: Intent, orderID: String) async throws -> Order {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we refactor card we should use this singular method - the only difference between authorize and capture is the intent so we don't need 2 separate methods for that

let intent = intent == .authorize ? "authorize" : "capture"
guard let url = buildBaseURL(
with: "/orders/\(orderID)/\(intent)",
selectedMerchantIntegration: DemoSettings.merchantIntegration
) else {
throw URLResponseError.invalidURL
}

let urlRequest = buildURLRequest(method: "POST", url: url, body: EmptyBodyParams())
let data = try await data(for: urlRequest)
return try parse(from: data)
}

func captureOrder(orderID: String, selectedMerchantIntegration: MerchantIntegration) async throws -> Order {
guard let url = buildBaseURL(with: "/orders/\(orderID)/capture", selectedMerchantIntegration: selectedMerchantIntegration) else {
throw URLResponseError.invalidURL
Expand Down
8 changes: 8 additions & 0 deletions Demo/Demo/SwiftUIComponents/CurrentState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

enum CurrentState: Equatable {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we can use this across the other demo features as well vs the individual states per feature that exist today

case idle
case loading
case success
case error(message: String)
}
6 changes: 3 additions & 3 deletions Demo/Demo/SwiftUIComponents/FeatureSelectionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ struct FeatureSelectionView: View {
Text("Card Vaulting")
}
NavigationLink {
PayPalWebView()
.navigationTitle("PayPalWeb Payment")
PayPalWebPaymentsView()
.navigationTitle("PayPal Web")
} label: {
Text("PayPalWeb Payment")
Text("PayPal Web")
}
NavigationLink {
SwiftUINativeCheckoutDemo()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import SwiftUI
import PaymentButtons

struct PayPalTransactionView: View {
struct PayPalWebButtonsView: View {

@ObservedObject var paypalWebViewModel: PayPalWebViewModel
let orderID: String
@ObservedObject var payPalWebViewModel: PayPalWebViewModel

var body: some View {
VStack {
VStack(alignment: .center, spacing: 40) {
PayPalButton.Representable(color: .blue, size: .mini) {
paypalWebViewModel.paymentButtonTapped(orderID: orderID, funding: .paypal)
payPalWebViewModel.paymentButtonTapped(funding: .paypal)
}
.frame(maxWidth: .infinity, maxHeight: 40)
PayPalCreditButton.Representable(color: .black, edges: .softEdges, size: .expanded) {
paypalWebViewModel.paymentButtonTapped(orderID: orderID, funding: .paypalCredit)
payPalWebViewModel.paymentButtonTapped(funding: .paypalCredit)
}
.frame(maxWidth: .infinity, maxHeight: 40)
PayPalPayLaterButton.Representable(color: .silver, edges: .rounded, size: .full) {
paypalWebViewModel.paymentButtonTapped(orderID: orderID, funding: .paylater)
payPalWebViewModel.paymentButtonTapped(funding: .paylater)
}
.frame(maxWidth: .infinity, maxHeight: 40)
}
Expand All @@ -29,13 +28,16 @@ struct PayPalTransactionView: View {
.stroke(.gray, lineWidth: 2)
.padding(5)
)
PayPalWebApprovalResultView(paypalWebViewModel: paypalWebViewModel)
if paypalWebViewModel.state.checkoutResult != nil {

if payPalWebViewModel.state == .success && payPalWebViewModel.checkoutResult != nil {
PayPalWebResultView(payPalWebViewModel: payPalWebViewModel, status: .approved)
NavigationLink {
PayPalWebOrderCompletionView(orderID: orderID, payPalWebViewModel: paypalWebViewModel)
PayPalWebTransactionView(payPalWebViewModel: payPalWebViewModel)
.navigationTitle("Complete Transaction")
} label: {
Text("Complete Order Transaction")
Text("Complete Transaction")
}
.navigationViewStyle(StackNavigationViewStyle())
.buttonStyle(RoundedBlueButtonStyle())
.padding()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import SwiftUI

struct CreateOrderPayPalWebView: View {
struct PayPalWebCreateOrderView: View {

@ObservedObject var paypalWebViewModel: PayPalWebViewModel
@ObservedObject var payPalWebViewModel: PayPalWebViewModel

@State private var selectedIntent: Intent = .authorize
@State var shouldVaultSelected = false

let selectedMerchantIntegration: MerchantIntegration

var body: some View {
VStack(spacing: 16) {
HStack {
Expand All @@ -31,20 +29,15 @@ struct CreateOrderPayPalWebView: View {
Button("Create an Order") {
Task {
do {
paypalWebViewModel.state.intent = selectedIntent
try await paypalWebViewModel.createOrder(
amount: "10.00",
selectedMerchantIntegration: DemoSettings.merchantIntegration,
intent: selectedIntent.rawValue,
shouldVault: shouldVaultSelected
)
payPalWebViewModel.intent = selectedIntent
try await payPalWebViewModel.createOrder(shouldVault: shouldVaultSelected)
} catch {
print("Error in getting setup token. \(error.localizedDescription)")
}
}
}
.buttonStyle(RoundedBlueButtonStyle())
if case .loading = paypalWebViewModel.state.createdOrderResponse {
if payPalWebViewModel.state == .loading {
CircularProgressView()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import SwiftUI

struct PayPalWebPaymentsView: View {

@StateObject var payPalWebViewModel = PayPalWebViewModel()

var body: some View {
ScrollView {
VStack(spacing: 16) {
PayPalWebCreateOrderView(payPalWebViewModel: payPalWebViewModel)
if payPalWebViewModel.order != nil {
PayPalWebResultView(payPalWebViewModel: payPalWebViewModel, status: .created)
NavigationLink {
jaxdesmarais marked this conversation as resolved.
Show resolved Hide resolved
PayPalWebButtonsView(payPalWebViewModel: payPalWebViewModel)
.navigationTitle("Checkout with PayPal")
} label: {
Text("Checkout with PayPal")
}
.buttonStyle(RoundedBlueButtonStyle())
.padding()
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import SwiftUI

enum OrderStatus {
case created
case approved
case completed
}

struct PayPalWebResultView: View {

@ObservedObject var payPalWebViewModel: PayPalWebViewModel

var status: OrderStatus

var body: some View {
switch payPalWebViewModel.state {
case .idle, .loading:
EmptyView()
case .success:
PayPalWebStatusView(status: status, payPalWebViewModel: payPalWebViewModel)
case .error(let errorMessage):
ErrorView(errorMessage: errorMessage)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import SwiftUI

struct PayPalWebStatusView: View {

var status: OrderStatus
var payPalWebViewModel: PayPalWebViewModel

var body: some View {
VStack(spacing: 16) {
switch status {
case .created:
HStack {
Text("Order Created")
.font(.system(size: 20))
Spacer()
}
if let order = payPalWebViewModel.order {
LeadingText("Order ID", weight: .bold)
LeadingText("\(order.id)")
LeadingText("Status", weight: .bold)
LeadingText("\(order.status)")
}
case .approved:
HStack {
Text("Order Approved")
.font(.system(size: 20))
Spacer()
}
if let order = payPalWebViewModel.order {
LeadingText("Intent", weight: .bold)
LeadingText("\(payPalWebViewModel.intent)")
LeadingText("Order ID", weight: .bold)
LeadingText("\(order.id)")
LeadingText("Payer ID", weight: .bold)
LeadingText("\(payPalWebViewModel.checkoutResult?.payerID ?? "")")
}
case .completed:
if let order = payPalWebViewModel.order {
HStack {
Text("Order \(payPalWebViewModel.intent.rawValue.capitalized)d")
.font(.system(size: 20))
Spacer()
}
LeadingText("Order ID", weight: .bold)
LeadingText("\(order.id)")
LeadingText("Status", weight: .bold)
LeadingText("\(order.status)")

if let emailAddress = payPalWebViewModel.order?.paymentSource?.paypal?.emailAddress {
LeadingText("Email", weight: .bold)
LeadingText("\(emailAddress)")
}

if let vaultID = payPalWebViewModel.order?.paymentSource?.paypal?.attributes?.vault.id {
LeadingText("Vault ID / Payment Token", weight: .bold)
LeadingText("\(vaultID)")
}

if let customerID = payPalWebViewModel.order?.paymentSource?.paypal?.attributes?.vault.customer.id {
LeadingText("Customer ID", weight: .bold)
LeadingText("\(customerID)")
}
}
}
}
.frame(maxWidth: .infinity)
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.stroke(.gray, lineWidth: 2)
.padding(5)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import SwiftUI

struct PayPalWebTransactionView: View {

@ObservedObject var payPalWebViewModel: PayPalWebViewModel

var body: some View {
ScrollView {
ScrollViewReader { scrollView in
VStack {
PayPalWebStatusView(status: .approved, payPalWebViewModel: payPalWebViewModel)
ZStack {
Button("\(payPalWebViewModel.intent.rawValue.capitalized) Order") {
Task {
do {
try await payPalWebViewModel.completeTransaction()
} catch {
print("Error capturing order: \(error.localizedDescription)")
}
}
}
.buttonStyle(RoundedBlueButtonStyle())
.padding()

if payPalWebViewModel.state == .loading {
CircularProgressView()
}
}

if payPalWebViewModel.state == .success && payPalWebViewModel.order?.status == "COMPLETED" {
PayPalWebResultView(payPalWebViewModel: payPalWebViewModel, status: .completed)
jaxdesmarais marked this conversation as resolved.
Show resolved Hide resolved
.id("bottomView")
}
}
.onChange(of: payPalWebViewModel.order) { _ in
withAnimation {
scrollView.scrollTo("bottomView")
}
}
Spacer()
}
}
}
}

This file was deleted.

This file was deleted.

Loading
Loading