From ae1adc3788995995adb677f39826d49bbdd3ffeb Mon Sep 17 00:00:00 2001 From: Victoria Park Date: Thu, 30 Nov 2023 13:35:31 -0800 Subject: [PATCH] Vault with Purchase PayPalWeb (#221) * vault with purchase create order PayPal * remove unnecessary decoding of links field * remove customerID entry for PP vault with purchase * add initializer with default nil for customer * change Customer to Codable for reuse for Vault customer decoding * Add comments that required experienceContext for PayPal vault option in order create not used in SDK * Jax, Sammy PR feedback --- Demo/Demo/Models/CreateOrderParams.swift | 54 ++++++++++++++++--- Demo/Demo/Models/Order.swift | 5 +- Demo/Demo/Models/PaymentTokenResponse.swift | 4 +- .../CreateOrderPayPalWebView.swift | 9 +++- .../PayPalWebOrderCompletionResultView.swift | 8 +++ .../ViewModels/CardPaymentViewModel.swift | 13 +++-- Demo/Demo/ViewModels/PayPalWebViewModel.swift | 24 ++++++++- 7 files changed, 99 insertions(+), 18 deletions(-) diff --git a/Demo/Demo/Models/CreateOrderParams.swift b/Demo/Demo/Models/CreateOrderParams.swift index 82f39e2a7..58075fed6 100644 --- a/Demo/Demo/Models/CreateOrderParams.swift +++ b/Demo/Demo/Models/CreateOrderParams.swift @@ -3,7 +3,7 @@ struct CreateOrderParams: Encodable { let applicationContext: ApplicationContext? let intent: String var purchaseUnits: [PurchaseUnit]? - var paymentSource: VaultCardPaymentSource? + var paymentSource: VaultPaymentSource? } struct ApplicationContext: Codable { @@ -17,6 +17,39 @@ struct ApplicationContext: Codable { } } +enum VaultPaymentSource: Encodable { + case card(VaultCardPaymentSource) + case paypal(VaultPayPalPaymentSource) + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .card(let cardSource): + try container.encode(cardSource) + case .paypal(let paypalSource): + try container.encode(paypalSource) + } + } +} + +struct VaultPayPalPaymentSource: Encodable { + + let paypal: VaultPayPal +} + +struct VaultPayPal: Encodable { + + let attributes: Attributes + let experienceContext: ExperienceContext +} + +struct ExperienceContext: Encodable { + + // these fields are not encoded for our SDK but are required for create order with PayPal vault option + let returnURL: String + let cancelURL: String +} + struct VaultCardPaymentSource: Encodable { let card: VaultCard @@ -30,20 +63,27 @@ struct VaultCard: Encodable { struct Attributes: Encodable { let vault: Vault - let customer: CardVaultCustomer? + let customer: Customer? + + init(vault: Vault, customer: Customer? = nil) { + self.vault = vault + self.customer = customer + } } struct Vault: Encodable { let storeInVault: String -} - -struct CardVaultCustomer: Encodable { + let usageType: String? + let customerType: String? - let id: String + init(storeInVault: String, usageType: String? = nil, customerType: String? = nil) { + self.storeInVault = storeInVault + self.usageType = usageType + self.customerType = customerType + } } - struct PurchaseUnit: Encodable { var shipping: Shipping? diff --git a/Demo/Demo/Models/Order.swift b/Demo/Demo/Models/Order.swift index ff225dbce..e72afe99a 100644 --- a/Demo/Demo/Models/Order.swift +++ b/Demo/Demo/Models/Order.swift @@ -3,7 +3,7 @@ struct Order: Codable, Equatable { let id: String let status: String var paymentSource: PaymentSource? - + struct PaymentSource: Codable, Equatable { let card: Card? @@ -25,7 +25,8 @@ struct Order: Codable, Equatable { struct PayPal: Codable, Equatable { - let emailAddress: String + let emailAddress: String? + let attributes: Attributes? } struct Attributes: Codable, Equatable { diff --git a/Demo/Demo/Models/PaymentTokenResponse.swift b/Demo/Demo/Models/PaymentTokenResponse.swift index 8003c3c39..407714a6b 100644 --- a/Demo/Demo/Models/PaymentTokenResponse.swift +++ b/Demo/Demo/Models/PaymentTokenResponse.swift @@ -7,8 +7,8 @@ struct PaymentTokenResponse: Decodable, Equatable { let paymentSource: PaymentSource } -struct Customer: Decodable, Equatable { - +struct Customer: Codable, Equatable { + let id: String } diff --git a/Demo/Demo/SwiftUIComponents/PayPalWebViews/CreateOrderPayPalWebView.swift b/Demo/Demo/SwiftUIComponents/PayPalWebViews/CreateOrderPayPalWebView.swift index 1286a53ae..fa52ea3e7 100644 --- a/Demo/Demo/SwiftUIComponents/PayPalWebViews/CreateOrderPayPalWebView.swift +++ b/Demo/Demo/SwiftUIComponents/PayPalWebViews/CreateOrderPayPalWebView.swift @@ -5,6 +5,7 @@ struct CreateOrderPayPalWebView: View { @ObservedObject var paypalWebViewModel: PayPalWebViewModel @State private var selectedIntent: Intent = .authorize + @State var shouldVaultSelected = false let selectedMerchantIntegration: MerchantIntegration @@ -22,6 +23,10 @@ struct CreateOrderPayPalWebView: View { Text("CAPTURE").tag(Intent.capture) } .pickerStyle(SegmentedPickerStyle()) + HStack { + Toggle("Should Vault with Purchase", isOn: $shouldVaultSelected) + Spacer() + } ZStack { Button("Create an Order") { Task { @@ -30,7 +35,9 @@ struct CreateOrderPayPalWebView: View { try await paypalWebViewModel.createOrder( amount: "10.00", selectedMerchantIntegration: DemoSettings.merchantIntegration, - intent: selectedIntent.rawValue) + intent: selectedIntent.rawValue, + shouldVault: shouldVaultSelected + ) } catch { print("Error in getting setup token. \(error.localizedDescription)") } diff --git a/Demo/Demo/SwiftUIComponents/PayPalWebViews/PayPalWebOrderCompletionResultView.swift b/Demo/Demo/SwiftUIComponents/PayPalWebViews/PayPalWebOrderCompletionResultView.swift index 86431e208..5a00ee8ef 100644 --- a/Demo/Demo/SwiftUIComponents/PayPalWebViews/PayPalWebOrderCompletionResultView.swift +++ b/Demo/Demo/SwiftUIComponents/PayPalWebViews/PayPalWebOrderCompletionResultView.swift @@ -39,6 +39,14 @@ struct PayPalWebOrderCompletionResultView: View { LeadingText("Email", weight: .bold) LeadingText("\(email)") } + if let vaultID = orderResponse.paymentSource?.paypal?.attributes?.vault.id { + LeadingText("Vault ID / Payment Token", weight: .bold) + LeadingText("\(vaultID)") + } + if let customerID = orderResponse.paymentSource?.paypal?.attributes?.vault.customer.id { + LeadingText("Customer ID", weight: .bold) + LeadingText("\(customerID)") + } Text("") .id("bottomView") } diff --git a/Demo/Demo/ViewModels/CardPaymentViewModel.swift b/Demo/Demo/ViewModels/CardPaymentViewModel.swift index 6dd032105..1cd9ccc31 100644 --- a/Demo/Demo/ViewModels/CardPaymentViewModel.swift +++ b/Demo/Demo/ViewModels/CardPaymentViewModel.swift @@ -21,15 +21,20 @@ class CardPaymentViewModel: ObservableObject, CardDelegate { let amountRequest = Amount(currencyCode: "USD", value: amount) // TODO: might need to pass in payee as payee object or as auth header - var vaultPaymentSource: VaultCardPaymentSource? + var vaultCardPaymentSource: VaultCardPaymentSource? if shouldVault { - var customer: CardVaultCustomer? + var customer: Customer? if let customerID { - customer = CardVaultCustomer(id: customerID) + customer = Customer(id: customerID) } let attributes = Attributes(vault: Vault(storeInVault: "ON_SUCCESS"), customer: customer) let card = VaultCard(attributes: attributes) - vaultPaymentSource = VaultCardPaymentSource(card: card) + vaultCardPaymentSource = VaultCardPaymentSource(card: card) + } + + var vaultPaymentSource: VaultPaymentSource? + if let vaultCardPaymentSource { + vaultPaymentSource = .card(vaultCardPaymentSource) } let orderRequestParams = CreateOrderParams( diff --git a/Demo/Demo/ViewModels/PayPalWebViewModel.swift b/Demo/Demo/ViewModels/PayPalWebViewModel.swift index 4019517a1..87ce856c8 100644 --- a/Demo/Demo/ViewModels/PayPalWebViewModel.swift +++ b/Demo/Demo/ViewModels/PayPalWebViewModel.swift @@ -10,15 +10,35 @@ class PayPalWebViewModel: ObservableObject, PayPalWebCheckoutDelegate { let configManager = CoreConfigManager(domain: "PayPalWeb Payments") - func createOrder(amount: String, selectedMerchantIntegration: MerchantIntegration, intent: String) async throws { + func createOrder( + amount: String, + selectedMerchantIntegration: MerchantIntegration, + intent: String, + shouldVault: Bool + ) async throws { // might need to pass in payee as payee object or as auth header let amountRequest = Amount(currencyCode: "USD", value: amount) // TODO: might need to pass in payee as payee object or as auth header + + var vaultPayPalPaymentSource: VaultPayPalPaymentSource? + 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")) + vaultPayPalPaymentSource = VaultPayPalPaymentSource(paypal: paypal) + } + + var vaultPaymentSource: VaultPaymentSource? + if let vaultPayPalPaymentSource { + vaultPaymentSource = .paypal(vaultPayPalPaymentSource) + } + let orderRequestParams = CreateOrderParams( applicationContext: nil, intent: intent, - purchaseUnits: [PurchaseUnit(amount: amountRequest)] + purchaseUnits: [PurchaseUnit(amount: amountRequest)], + paymentSource: vaultPaymentSource ) do {