From d53299c6475154b8ba0bee9683072bf77ece1b52 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 30 Dec 2024 14:05:32 -0600 Subject: [PATCH 01/49] Add file with paypal request properties --- Braintree.xcodeproj/project.pbxproj | 12 +++ .../Models/BTPayPalCheckoutPOSTBody.swift | 98 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index 7d602e309..75dd5b955 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ 458570782C34A699009CEF7A /* ConfigurationLoader_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458570772C34A699009CEF7A /* ConfigurationLoader_Tests.swift */; }; 4585707A2C34B1E1009CEF7A /* MockClientAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458570792C34B1E1009CEF7A /* MockClientAuthorization.swift */; }; 4585707C2C34B7B5009CEF7A /* MockConfigurationLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4585707B2C34B7B5009CEF7A /* MockConfigurationLoader.swift */; }; + 45E8CE4C2D1F29BA00D7A2DC /* BTPayPalCheckoutPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE4B2D1F29BA00D7A2DC /* BTPayPalCheckoutPOSTBody.swift */; }; 45EFC3972C2DBF32005E7F5B /* ConfigurationLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45EFC3962C2DBF32005E7F5B /* ConfigurationLoader.swift */; }; 460C0C220F594AE8EE205E57 /* Pods_Tests_BraintreeCoreTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9239C9FE850C3587DE61A3A2 /* Pods_Tests_BraintreeCoreTests.framework */; }; 5708E0A628809AD9007946B9 /* BTJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5708E0A528809AD9007946B9 /* BTJSON.swift */; }; @@ -725,6 +726,7 @@ 458570772C34A699009CEF7A /* ConfigurationLoader_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationLoader_Tests.swift; sourceTree = ""; }; 458570792C34B1E1009CEF7A /* MockClientAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockClientAuthorization.swift; sourceTree = ""; }; 4585707B2C34B7B5009CEF7A /* MockConfigurationLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockConfigurationLoader.swift; sourceTree = ""; }; + 45E8CE4B2D1F29BA00D7A2DC /* BTPayPalCheckoutPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalCheckoutPOSTBody.swift; sourceTree = ""; }; 45EFC3962C2DBF32005E7F5B /* ConfigurationLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationLoader.swift; sourceTree = ""; }; 463DED22C0F426A474E6D7E2 /* Pods-Tests-BraintreeCoreTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-BraintreeCoreTests.release.xcconfig"; path = "Target Support Files/Pods-Tests-BraintreeCoreTests/Pods-Tests-BraintreeCoreTests.release.xcconfig"; sourceTree = ""; }; 541AEE40A1F01913E0638CC9 /* Pods-Tests-BraintreeCoreTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-BraintreeCoreTests.debug.xcconfig"; path = "Target Support Files/Pods-Tests-BraintreeCoreTests/Pods-Tests-BraintreeCoreTests.debug.xcconfig"; sourceTree = ""; }; @@ -1298,6 +1300,7 @@ BE349112294B798300D2CF68 /* BTPayPalRequest.swift */, BE6BC22D2BA9CFFC00C3E321 /* BTPayPalReturnURL.swift */, BE349110294B77E100D2CF68 /* BTPayPalVaultRequest.swift */, + 45E8CE4A2D1F291400D7A2DC /* Models */, 62A659A32B98CB23008DFD67 /* PrivacyInfo.xcprivacy */, 807D22F32C29ADA8009FFEA4 /* RecurringBillingMetadata */, 62B811872CC002470024A688 /* BTPayPalPhoneNumber.swift */, @@ -1313,6 +1316,14 @@ path = Models; sourceTree = ""; }; + 45E8CE4A2D1F291400D7A2DC /* Models */ = { + isa = PBXGroup; + children = ( + 45E8CE4B2D1F29BA00D7A2DC /* BTPayPalCheckoutPOSTBody.swift */, + ); + path = Models; + sourceTree = ""; + }; 570B93AE285397D20041BAFE /* BraintreeCore */ = { isa = PBXGroup; children = ( @@ -3108,6 +3119,7 @@ 807D22F02C29A93A009FFEA4 /* BTPayPalBillingCycle.swift in Sources */, 5754481E294A2A1D00DEB7B0 /* BTPayPalCreditFinancingAmount.swift in Sources */, 57D9436E2968A8080079EAB1 /* BTPayPalLocaleCode.swift in Sources */, + 45E8CE4C2D1F29BA00D7A2DC /* BTPayPalCheckoutPOSTBody.swift in Sources */, 57544F582952298900DEB7B0 /* BTPayPalAccountNonce.swift in Sources */, 8014221C2BAE935B009F9999 /* BTPayPalApprovalURLParser.swift in Sources */, BE349111294B77E100D2CF68 /* BTPayPalVaultRequest.swift in Sources */, diff --git a/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift b/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift new file mode 100644 index 000000000..4ba6679af --- /dev/null +++ b/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift @@ -0,0 +1,98 @@ +import Foundation + +#if canImport(BraintreeCore) +import BraintreeCore +#endif + +/// The POST body for v1/paypal_hermes/create_payment_resource +struct BTPayPalCheckoutPOSTBody: Encodable { + + // MARK: - Private Properties + + private let userPhoneNumber: BTPayPalPhoneNumber? + private let returnURL: String + private let cancelURL: String + private let experienceProfile: ExperienceProfile + + private var merchantAccountID: String? + private var riskCorrelationID: String? + private var lineItems: [[String: String]]? + private var userAuthenticationEmail: String? + + // MARK: - Initializer + + init(payPalRequest: BTPayPalRequest, configuration: BTConfiguration) { + if let merchantAccountID = payPalRequest.merchantAccountID { + self.merchantAccountID = merchantAccountID + } + + if let riskCorrelationID = payPalRequest.riskCorrelationID { + self.riskCorrelationID = riskCorrelationID + } + + if let lineItems = payPalRequest.lineItems, !lineItems.isEmpty { + let lineItemsArray = lineItems.compactMap { $0.requestParameters() } + self.lineItems = lineItemsArray + } + + if let userAuthenticationEmail = payPalRequest.userAuthenticationEmail, !userAuthenticationEmail.isEmpty { + self.userAuthenticationEmail = userAuthenticationEmail + } + + self.userPhoneNumber = payPalRequest.userPhoneNumber + self.returnURL = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)success" + self.cancelURL = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)cancel" + self.experienceProfile = ExperienceProfile(payPalRequest: payPalRequest, configuration: configuration) + } + + enum CodingKeys: String, CodingKey { + case merchantAccountID = "merchant_account_id" + case riskCorrelationID = "correlation_id" + case lineItems = "line_items" + case userAuthenticationEmail = "payer_email" + case userPhoneNumber = "phone_number" + case returnURL = "return_url" + case cancelURL = "cancel_url" + case experienceProfile = "experience_profile" + } +} + +extension BTPayPalCheckoutPOSTBody { + + struct ExperienceProfile: Encodable { + + // MARK: - Private Properties + + private let displayName: String? + private let isShippingAddressRequired: Bool + private let shippingAddressOverride: Bool + + private var landingPageType: String? + private var localeCode: String? + + // MARK: - Initializer + + init(payPalRequest: BTPayPalRequest, configuration: BTConfiguration) { + self.displayName = payPalRequest.displayName != nil ? payPalRequest.displayName : configuration.displayName + self.isShippingAddressRequired = !payPalRequest.isShippingAddressRequired + + if let landingPageType = payPalRequest.landingPageType?.stringValue { + self.landingPageType = landingPageType + } + + if let localeCode = payPalRequest.localeCode?.stringValue { + self.localeCode = localeCode + } + + self.shippingAddressOverride = payPalRequest.shippingAddressOverride != nil ? !payPalRequest.isShippingAddressEditable : false + } + + enum CodingKeys: String, CodingKey { + case isShippingAddressRequired = "no_shipping" + case displayName = "brand_name" + case landingPageType = "landing_page_type" + case localeCode = "locale_code" + case shippingAddressOverride = "address_override" + } + } +} From cf7ad1eed7a707efb9287d7e948b18b05b1796b7 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 30 Dec 2024 14:05:51 -0600 Subject: [PATCH 02/49] Add var to BTConfiguration extension --- Sources/BraintreePayPal/BTConfiguration+PayPal.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/BraintreePayPal/BTConfiguration+PayPal.swift b/Sources/BraintreePayPal/BTConfiguration+PayPal.swift index cf5658792..530dedd8b 100644 --- a/Sources/BraintreePayPal/BTConfiguration+PayPal.swift +++ b/Sources/BraintreePayPal/BTConfiguration+PayPal.swift @@ -17,4 +17,9 @@ extension BTConfiguration { var isBillingAgreementsEnabled: Bool { json?["paypal"]["billingAgreementsEnabled"].isTrue ?? false } + + /// Retrieves the display name associated with the PayPal account. + var displayName: String? { + json?["paypal"]["displayName"].asString() + } } From 964d7e83bac8b71482a9a80646cdeb05837a83be Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 30 Dec 2024 14:42:58 -0600 Subject: [PATCH 03/49] Add country iso code var --- Sources/BraintreePayPal/BTConfiguration+PayPal.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/BraintreePayPal/BTConfiguration+PayPal.swift b/Sources/BraintreePayPal/BTConfiguration+PayPal.swift index 530dedd8b..b028e6750 100644 --- a/Sources/BraintreePayPal/BTConfiguration+PayPal.swift +++ b/Sources/BraintreePayPal/BTConfiguration+PayPal.swift @@ -22,4 +22,9 @@ extension BTConfiguration { var displayName: String? { json?["paypal"]["displayName"].asString() } + + /// Retrieves the currencyIsoCode + var currencyIsoCode: String? { + json?["paypal"]["currencyIsoCode"].asString() + } } From 7460d9d7f5114390543912c19cb8e34a7a6af600 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 30 Dec 2024 15:21:48 -0600 Subject: [PATCH 04/49] Add checkout properties to POST body model --- .../Models/BTPayPalCheckoutPOSTBody.swift | 91 +++++++++++++++++-- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift b/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift index 4ba6679af..fd94c71dc 100644 --- a/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift @@ -9,19 +9,62 @@ struct BTPayPalCheckoutPOSTBody: Encodable { // MARK: - Private Properties + private let amount: String + private let intent: String + private let offerPayLater: Bool private let userPhoneNumber: BTPayPalPhoneNumber? private let returnURL: String private let cancelURL: String private let experienceProfile: ExperienceProfile + private var billingAgreementDescription: BillingAgreemeentDescription? + private var currencyCode: String? + private var lineItems: [[String: String]]? private var merchantAccountID: String? + private var requestBillingAgreement: Bool? private var riskCorrelationID: String? - private var lineItems: [[String: String]]? private var userAuthenticationEmail: String? + // Address properties + private var streetAddress: String? + private var extendedAddress: String? + private var locality: String? + private var countryCodeAlpha2: String? + private var postalCode: String? + private var region: String? + private var recipientName: String? + // MARK: - Initializer - init(payPalRequest: BTPayPalRequest, configuration: BTConfiguration) { + init(payPalRequest: BTPayPalCheckoutRequest, configuration: BTConfiguration) { + self.amount = payPalRequest.amount + self.intent = payPalRequest.intent.stringValue + self.offerPayLater = payPalRequest.offerPayLater + + let currencyIsoCode = payPalRequest.currencyCode != nil ? payPalRequest.currencyCode : configuration.currencyIsoCode + + if let currencyIsoCode { + self.currencyCode = currencyIsoCode + } + + if payPalRequest.requestBillingAgreement { + self.requestBillingAgreement = payPalRequest.requestBillingAgreement + + if let billingAgreementDescription = payPalRequest.billingAgreementDescription { + self.billingAgreementDescription = BillingAgreemeentDescription(description: billingAgreementDescription) + } + } + + if let shippingAddressOverride = payPalRequest.shippingAddressOverride { + self.streetAddress = shippingAddressOverride.streetAddress + self.extendedAddress = shippingAddressOverride.extendedAddress + self.locality = shippingAddressOverride.locality + self.countryCodeAlpha2 = shippingAddressOverride.countryCodeAlpha2 + self.postalCode = shippingAddressOverride.postalCode + self.region = shippingAddressOverride.region + self.recipientName = shippingAddressOverride.recipientName + } + if let merchantAccountID = payPalRequest.merchantAccountID { self.merchantAccountID = merchantAccountID } @@ -46,19 +89,47 @@ struct BTPayPalCheckoutPOSTBody: Encodable { } enum CodingKeys: String, CodingKey { + case amount + case billingAgreementDescription = "billing_agreement_details" + case cancelURL = "cancel_url" + case currencyCode = "currency_iso_code" + case experienceProfile = "experience_profile" + case intent + case lineItems = "line_items" case merchantAccountID = "merchant_account_id" + case offerPayLater = "offer_pay_later" + case requestBillingAgreement = "request_billing_agreement" + case returnURL = "return_url" case riskCorrelationID = "correlation_id" - case lineItems = "line_items" case userAuthenticationEmail = "payer_email" case userPhoneNumber = "phone_number" - case returnURL = "return_url" - case cancelURL = "cancel_url" - case experienceProfile = "experience_profile" + + // Address keys + case streetAddress = "line1" + case extendedAddress = "line2" + case locality = "city" + case countryCodeAlpha2 = "country_code" + case postalCode = "postal_code" + case region = "state" + case recipientName = "recipient_name" } } extension BTPayPalCheckoutPOSTBody { + struct BillingAgreemeentDescription: Encodable { + + // MARK: - Private Properties + + private let description: String + + // MARK: - Initializer + + init(description: String) { + self.description = description + } + } + struct ExperienceProfile: Encodable { // MARK: - Private Properties @@ -69,10 +140,11 @@ extension BTPayPalCheckoutPOSTBody { private var landingPageType: String? private var localeCode: String? + private var userAction: String? // MARK: - Initializer - init(payPalRequest: BTPayPalRequest, configuration: BTConfiguration) { + init(payPalRequest: BTPayPalCheckoutRequest, configuration: BTConfiguration) { self.displayName = payPalRequest.displayName != nil ? payPalRequest.displayName : configuration.displayName self.isShippingAddressRequired = !payPalRequest.isShippingAddressRequired @@ -85,6 +157,10 @@ extension BTPayPalCheckoutPOSTBody { } self.shippingAddressOverride = payPalRequest.shippingAddressOverride != nil ? !payPalRequest.isShippingAddressEditable : false + + if payPalRequest.userAction != .none { + self.userAction = payPalRequest.userAction.stringValue + } } enum CodingKeys: String, CodingKey { @@ -93,6 +169,7 @@ extension BTPayPalCheckoutPOSTBody { case landingPageType = "landing_page_type" case localeCode = "locale_code" case shippingAddressOverride = "address_override" + case userAction = "user_action" } } } From 0a3dbf4c35234bd6f0623490220323afd34e4fb6 Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 2 Jan 2025 11:30:58 -0600 Subject: [PATCH 05/49] Disable nesting lint --- Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift b/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift index fd94c71dc..30a76cc04 100644 --- a/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift @@ -163,6 +163,7 @@ extension BTPayPalCheckoutPOSTBody { } } + // swiftlint:disable nesting enum CodingKeys: String, CodingKey { case isShippingAddressRequired = "no_shipping" case displayName = "brand_name" From f5952e674476375649d4cb1e3c44fb8dd7f8b8f0 Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 2 Jan 2025 11:58:30 -0600 Subject: [PATCH 06/49] Address some lints --- Sources/BraintreePayPal/BTPayPalRequest.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalRequest.swift b/Sources/BraintreePayPal/BTPayPalRequest.swift index 8ceef4f95..93bcd6ff7 100644 --- a/Sources/BraintreePayPal/BTPayPalRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalRequest.swift @@ -80,11 +80,11 @@ import BraintreeCore /// - hermesPath: Required :nodoc: The hermes path or endpoint URI path. This property is not covered by semantic versioning. /// - paymentType: Required :nodoc: The payment type, either checkout or vault. This property is not covered by semantic versioning. /// - isShippingAddressRequired: Defaults to false. When set to true, the shipping address selector will be displayed. - /// - isShippingAddressEditable: Defaults to false. Set to true to enable user editing of the shipping address. + /// - isShippingAddressEditable: Defaults to false. Set to true to enable user editing of the shipping address. /// - Note: Only applies when `shippingAddressOverride` is set. /// - localeCode: Optional: A locale code to use for the transaction. /// - shippingAddressOverride: Optional: A valid shipping address to be displayed in the transaction flow. An error will occur if this address is not valid. - /// - landingPageType: Optional: Landing page type. Defaults to `.none`. + /// - landingPageType: Optional: Landing page type. Defaults to `.none`. /// - Note: Setting the BTPayPalRequest's landingPageType changes the PayPal page to display when a user lands on the PayPal site to complete the payment. /// `.login` specifies a PayPal account login page is used. /// `.billing` specifies a non-PayPal account landing page is used. From 3168bf50f52f7ed95a1a63c596de59e7a8eb976a Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 2 Jan 2025 12:05:32 -0600 Subject: [PATCH 07/49] Add dot --- Sources/BraintreePayPal/BTConfiguration+PayPal.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraintreePayPal/BTConfiguration+PayPal.swift b/Sources/BraintreePayPal/BTConfiguration+PayPal.swift index b028e6750..4920b5965 100644 --- a/Sources/BraintreePayPal/BTConfiguration+PayPal.swift +++ b/Sources/BraintreePayPal/BTConfiguration+PayPal.swift @@ -23,7 +23,7 @@ extension BTConfiguration { json?["paypal"]["displayName"].asString() } - /// Retrieves the currencyIsoCode + /// Retrieves the currencyIsoCode. var currencyIsoCode: String? { json?["paypal"]["currencyIsoCode"].asString() } From 623c6817c1c03d9365a62e554edcb309819c2720 Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 2 Jan 2025 12:12:54 -0600 Subject: [PATCH 08/49] Rename struct --- Braintree.xcodeproj/project.pbxproj | 8 ++++---- ...heckoutPOSTBody.swift => PayPalCheckoutPOSTBody.swift} | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) rename Sources/BraintreePayPal/Models/{BTPayPalCheckoutPOSTBody.swift => PayPalCheckoutPOSTBody.swift} (98%) diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index 75dd5b955..3ccea2080 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -44,7 +44,7 @@ 458570782C34A699009CEF7A /* ConfigurationLoader_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458570772C34A699009CEF7A /* ConfigurationLoader_Tests.swift */; }; 4585707A2C34B1E1009CEF7A /* MockClientAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458570792C34B1E1009CEF7A /* MockClientAuthorization.swift */; }; 4585707C2C34B7B5009CEF7A /* MockConfigurationLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4585707B2C34B7B5009CEF7A /* MockConfigurationLoader.swift */; }; - 45E8CE4C2D1F29BA00D7A2DC /* BTPayPalCheckoutPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE4B2D1F29BA00D7A2DC /* BTPayPalCheckoutPOSTBody.swift */; }; + 45E8CE4C2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */; }; 45EFC3972C2DBF32005E7F5B /* ConfigurationLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45EFC3962C2DBF32005E7F5B /* ConfigurationLoader.swift */; }; 460C0C220F594AE8EE205E57 /* Pods_Tests_BraintreeCoreTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9239C9FE850C3587DE61A3A2 /* Pods_Tests_BraintreeCoreTests.framework */; }; 5708E0A628809AD9007946B9 /* BTJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5708E0A528809AD9007946B9 /* BTJSON.swift */; }; @@ -726,7 +726,7 @@ 458570772C34A699009CEF7A /* ConfigurationLoader_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationLoader_Tests.swift; sourceTree = ""; }; 458570792C34B1E1009CEF7A /* MockClientAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockClientAuthorization.swift; sourceTree = ""; }; 4585707B2C34B7B5009CEF7A /* MockConfigurationLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockConfigurationLoader.swift; sourceTree = ""; }; - 45E8CE4B2D1F29BA00D7A2DC /* BTPayPalCheckoutPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalCheckoutPOSTBody.swift; sourceTree = ""; }; + 45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalCheckoutPOSTBody.swift; sourceTree = ""; }; 45EFC3962C2DBF32005E7F5B /* ConfigurationLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationLoader.swift; sourceTree = ""; }; 463DED22C0F426A474E6D7E2 /* Pods-Tests-BraintreeCoreTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-BraintreeCoreTests.release.xcconfig"; path = "Target Support Files/Pods-Tests-BraintreeCoreTests/Pods-Tests-BraintreeCoreTests.release.xcconfig"; sourceTree = ""; }; 541AEE40A1F01913E0638CC9 /* Pods-Tests-BraintreeCoreTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-BraintreeCoreTests.debug.xcconfig"; path = "Target Support Files/Pods-Tests-BraintreeCoreTests/Pods-Tests-BraintreeCoreTests.debug.xcconfig"; sourceTree = ""; }; @@ -1319,7 +1319,7 @@ 45E8CE4A2D1F291400D7A2DC /* Models */ = { isa = PBXGroup; children = ( - 45E8CE4B2D1F29BA00D7A2DC /* BTPayPalCheckoutPOSTBody.swift */, + 45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */, ); path = Models; sourceTree = ""; @@ -3119,7 +3119,7 @@ 807D22F02C29A93A009FFEA4 /* BTPayPalBillingCycle.swift in Sources */, 5754481E294A2A1D00DEB7B0 /* BTPayPalCreditFinancingAmount.swift in Sources */, 57D9436E2968A8080079EAB1 /* BTPayPalLocaleCode.swift in Sources */, - 45E8CE4C2D1F29BA00D7A2DC /* BTPayPalCheckoutPOSTBody.swift in Sources */, + 45E8CE4C2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift in Sources */, 57544F582952298900DEB7B0 /* BTPayPalAccountNonce.swift in Sources */, 8014221C2BAE935B009F9999 /* BTPayPalApprovalURLParser.swift in Sources */, BE349111294B77E100D2CF68 /* BTPayPalVaultRequest.swift in Sources */, diff --git a/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift similarity index 98% rename from Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift rename to Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift index 30a76cc04..b8e6f4013 100644 --- a/Sources/BraintreePayPal/Models/BTPayPalCheckoutPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift @@ -5,7 +5,7 @@ import BraintreeCore #endif /// The POST body for v1/paypal_hermes/create_payment_resource -struct BTPayPalCheckoutPOSTBody: Encodable { +struct PayPalCheckoutPOSTBody: Encodable { // MARK: - Private Properties @@ -115,7 +115,7 @@ struct BTPayPalCheckoutPOSTBody: Encodable { } } -extension BTPayPalCheckoutPOSTBody { +extension PayPalCheckoutPOSTBody { struct BillingAgreemeentDescription: Encodable { From 8006e187bd936d51b35a81e6d0b30458bfafecd9 Mon Sep 17 00:00:00 2001 From: richherrera Date: Fri, 3 Jan 2025 13:41:39 -0600 Subject: [PATCH 09/49] Add Encodable protocol to BTPostalAddress class --- Sources/BraintreeCore/BTPostalAddress.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Sources/BraintreeCore/BTPostalAddress.swift b/Sources/BraintreeCore/BTPostalAddress.swift index 30a9ba617..103ada17d 100644 --- a/Sources/BraintreeCore/BTPostalAddress.swift +++ b/Sources/BraintreeCore/BTPostalAddress.swift @@ -1,7 +1,7 @@ import Foundation /// Generic postal address -@objcMembers public class BTPostalAddress: NSObject { +@objcMembers public class BTPostalAddress: NSObject, Encodable { // Property names follow the `Braintree_Address` convention as documented at: // https://developer.paypal.com/braintree/docs/reference/request/address/create @@ -27,4 +27,14 @@ import Foundation /// Either a two-letter state code (for the US), or an ISO-3166-2 country subdivision code of up to three letters. public var region: String? + + enum CodingKeys: String, CodingKey { + case countryCodeAlpha2 = "country_code" + case extendedAddress = "line2" + case locality = "city" + case postalCode = "postal_code" + case region = "state" + case recipientName = "recipient_name" + case streetAddress = "line1" + } } From d1c1cd1c0b7a3cd3faaee516b4894211a1cfae8f Mon Sep 17 00:00:00 2001 From: richherrera Date: Fri, 3 Jan 2025 13:42:07 -0600 Subject: [PATCH 10/49] Add Encodable protocol to BTPayPalBillingCycle struct --- .../BTPayPalBillingCycle.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingCycle.swift b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingCycle.swift index e67b45b6b..5012d7e6c 100644 --- a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingCycle.swift +++ b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingCycle.swift @@ -1,12 +1,12 @@ import Foundation /// PayPal recurring billing cycle details. -public struct BTPayPalBillingCycle { +public struct BTPayPalBillingCycle: Encodable { // MARK: - Public Types /// The interval at which the payment is charged or billed. - public enum BillingInterval: String { + public enum BillingInterval: String, Encodable { case day = "DAY" case week = "WEEK" case month = "MONTH" @@ -52,6 +52,16 @@ public struct BTPayPalBillingCycle { self.pricing = pricing } + enum CodingKeys: String, CodingKey { + case numberOfExecutions = "number_of_executions" + case isTrial = "trial" + case interval = "billing_frequency_unit" + case intervalCount = "billing_frequency" + case sequence + case startDate = "start_date" + case pricing = "pricing_scheme" + } + // MARK: - Internal Methods func parameters() -> [String: Any] { From 14390fd27288a4a0f9de22dfcfb61663b2591bc9 Mon Sep 17 00:00:00 2001 From: richherrera Date: Fri, 3 Jan 2025 13:42:18 -0600 Subject: [PATCH 11/49] Add Encodable protocol to BTPayPalBillingPricing struct --- .../BTPayPalBillingPricing.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingPricing.swift b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingPricing.swift index 48c6876ff..2d18f7b0e 100644 --- a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingPricing.swift +++ b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingPricing.swift @@ -1,12 +1,12 @@ import Foundation /// PayPal Recurring Billing Agreement pricing details. -public struct BTPayPalBillingPricing { +public struct BTPayPalBillingPricing: Encodable { // MARK: - Public Types /// Recurring Billing Agreement pricing model types. - public enum PricingModel: String { + public enum PricingModel: String, Encodable { case fixed = "FIXED" case variable = "VARIABLE" case autoReload = "AUTO_RELOAD" @@ -31,6 +31,12 @@ public struct BTPayPalBillingPricing { self.reloadThresholdAmount = reloadThresholdAmount } + enum CodingKeys: String, CodingKey { + case amount = "price" + case pricingModel = "pricing_model" + case reloadThresholdAmount = "reload_threshold_amount" + } + // MARK: - Internal Methods func parameters() -> [String: Any] { From 06fd2946d9e1aef90409de92d61fdf3867ff973e Mon Sep 17 00:00:00 2001 From: richherrera Date: Fri, 3 Jan 2025 13:42:48 -0600 Subject: [PATCH 12/49] Add Encodable protocol to BTPayPalRecurringBillingDetails struct --- .../BTPayPalRecurringBillingDetails.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingDetails.swift b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingDetails.swift index 312ca1963..2a4f0b4a0 100644 --- a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingDetails.swift +++ b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingDetails.swift @@ -1,7 +1,7 @@ import Foundation /// PayPal recurring billing product details. -public struct BTPayPalRecurringBillingDetails { +public struct BTPayPalRecurringBillingDetails: Encodable { // MARK: - Private Properties @@ -54,6 +54,19 @@ public struct BTPayPalRecurringBillingDetails { self.taxAmount = taxAmount } + enum CodingKeys: String, CodingKey { + case billingCycles = "billing_cycles" + case currencyISOCode = "currency_iso_code" + case oneTimeFeeAmount = "one_time_fee_amount" + case productAmount = "product_price" + case productDescription = "product_description" + case productName = "name" + case productQuantity = "product_quantity" + case shippingAmount = "shipping_amount" + case taxAmount = "tax_amount" + case totalAmount = "total_amount" + } + // MARK: - Internal Methods func parameters() -> [String: Any] { From 6f0daad1fddb356f47ed6ed1a13d604fccfb4769 Mon Sep 17 00:00:00 2001 From: richherrera Date: Fri, 3 Jan 2025 13:43:12 -0600 Subject: [PATCH 13/49] Add Encodable protocol to BTPayPalRecurringBillingPlanType enum --- .../BTPayPalRecurringBillingPlanType.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingPlanType.swift b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingPlanType.swift index 5b3326e3b..9b1ed69a1 100644 --- a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingPlanType.swift +++ b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingPlanType.swift @@ -1,5 +1,5 @@ /// PayPal recurring billing plan type, or charge pattern. -public enum BTPayPalRecurringBillingPlanType: String { +public enum BTPayPalRecurringBillingPlanType: String, Encodable { /// Variable amount, fixed frequency, no defined duration. (E.g., utility bills, insurance). case recurring = "RECURRING" From 428b0a4ad0185b518cfab94c8e095c63aba1951e Mon Sep 17 00:00:00 2001 From: richherrera Date: Fri, 3 Jan 2025 13:43:49 -0600 Subject: [PATCH 14/49] Add PayPalVaultPOSTBody file --- Braintree.xcodeproj/project.pbxproj | 4 ++++ Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift | 6 ++++++ 2 files changed, 10 insertions(+) create mode 100644 Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index 3ccea2080..a943bccc2 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ 4585707A2C34B1E1009CEF7A /* MockClientAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458570792C34B1E1009CEF7A /* MockClientAuthorization.swift */; }; 4585707C2C34B7B5009CEF7A /* MockConfigurationLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4585707B2C34B7B5009CEF7A /* MockConfigurationLoader.swift */; }; 45E8CE4C2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */; }; + 45E8CE4E2D28611700D7A2DC /* PayPalVaultPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE4D2D28611700D7A2DC /* PayPalVaultPOSTBody.swift */; }; 45EFC3972C2DBF32005E7F5B /* ConfigurationLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45EFC3962C2DBF32005E7F5B /* ConfigurationLoader.swift */; }; 460C0C220F594AE8EE205E57 /* Pods_Tests_BraintreeCoreTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9239C9FE850C3587DE61A3A2 /* Pods_Tests_BraintreeCoreTests.framework */; }; 5708E0A628809AD9007946B9 /* BTJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5708E0A528809AD9007946B9 /* BTJSON.swift */; }; @@ -727,6 +728,7 @@ 458570792C34B1E1009CEF7A /* MockClientAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockClientAuthorization.swift; sourceTree = ""; }; 4585707B2C34B7B5009CEF7A /* MockConfigurationLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockConfigurationLoader.swift; sourceTree = ""; }; 45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalCheckoutPOSTBody.swift; sourceTree = ""; }; + 45E8CE4D2D28611700D7A2DC /* PayPalVaultPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalVaultPOSTBody.swift; sourceTree = ""; }; 45EFC3962C2DBF32005E7F5B /* ConfigurationLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationLoader.swift; sourceTree = ""; }; 463DED22C0F426A474E6D7E2 /* Pods-Tests-BraintreeCoreTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-BraintreeCoreTests.release.xcconfig"; path = "Target Support Files/Pods-Tests-BraintreeCoreTests/Pods-Tests-BraintreeCoreTests.release.xcconfig"; sourceTree = ""; }; 541AEE40A1F01913E0638CC9 /* Pods-Tests-BraintreeCoreTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-BraintreeCoreTests.debug.xcconfig"; path = "Target Support Files/Pods-Tests-BraintreeCoreTests/Pods-Tests-BraintreeCoreTests.debug.xcconfig"; sourceTree = ""; }; @@ -1320,6 +1322,7 @@ isa = PBXGroup; children = ( 45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */, + 45E8CE4D2D28611700D7A2DC /* PayPalVaultPOSTBody.swift */, ); path = Models; sourceTree = ""; @@ -3117,6 +3120,7 @@ 3B7A261129C0CAA40087059D /* BTPayPalAnalytics.swift in Sources */, BE8E5CEF294B6937001BF017 /* BTPayPalCheckoutRequest.swift in Sources */, 807D22F02C29A93A009FFEA4 /* BTPayPalBillingCycle.swift in Sources */, + 45E8CE4E2D28611700D7A2DC /* PayPalVaultPOSTBody.swift in Sources */, 5754481E294A2A1D00DEB7B0 /* BTPayPalCreditFinancingAmount.swift in Sources */, 57D9436E2968A8080079EAB1 /* BTPayPalLocaleCode.swift in Sources */, 45E8CE4C2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift in Sources */, diff --git a/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift new file mode 100644 index 000000000..9ed6d5b94 --- /dev/null +++ b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift @@ -0,0 +1,6 @@ +import Foundation +import UIKit + +#if canImport(BraintreeCore) +import BraintreeCore +#endif From 2b072dabc2822d256569fff54c9b09f5310e690f Mon Sep 17 00:00:00 2001 From: richherrera Date: Fri, 3 Jan 2025 13:45:14 -0600 Subject: [PATCH 15/49] Add encodable properties --- .../Models/PayPalVaultPOSTBody.swift | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift index 9ed6d5b94..0ce2aec35 100644 --- a/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift @@ -4,3 +4,146 @@ import UIKit #if canImport(BraintreeCore) import BraintreeCore #endif + +/// The POST body for v1/paypal_hermes/setup_billing_agreement +struct PayPalVaultPOSTBody: Encodable { + + // MARK: - Private Properties + + private let userPhoneNumber: BTPayPalPhoneNumber? + private let returnURL: String + private let cancelURL: String + private let experienceProfile: ExperienceProfile + + private var billingAgreementDescription: String? + private var enablePayPalAppSwitch = false + private var lineItems: [[String: String]]? + private var merchantAccountID: String? + private var offerCredit = false + private var osType: String? + private var osVersion: String? + private var recurringBillingPlanType: BTPayPalRecurringBillingPlanType? + private var recurringBillingDetails: BTPayPalRecurringBillingDetails? + private var riskCorrelationID: String? + private var shippingAddressOverride: BTPostalAddress? + private var universalLink: String? + private var userAuthenticationEmail: String? + + // MARK: - Initializer + + init( + payPalRequest: BTPayPalVaultRequest, + configuration: BTConfiguration, + isPayPalAppInstalled: Bool, + universalLink: URL? + ) { + if let merchantAccountID = payPalRequest.merchantAccountID { + self.merchantAccountID = merchantAccountID + } + + if let riskCorrelationID = payPalRequest.riskCorrelationID { + self.riskCorrelationID = riskCorrelationID + } + + if let lineItems = payPalRequest.lineItems, !lineItems.isEmpty { + let lineItemsArray = lineItems.compactMap { $0.requestParameters() } + self.lineItems = lineItemsArray + } + + if let userAuthenticationEmail = payPalRequest.userAuthenticationEmail, !userAuthenticationEmail.isEmpty { + self.userAuthenticationEmail = userAuthenticationEmail + } + + self.userPhoneNumber = payPalRequest.userPhoneNumber + self.returnURL = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)success" + self.cancelURL = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)cancel" + self.experienceProfile = ExperienceProfile(payPalRequest: payPalRequest, configuration: configuration) + + if let universalLink, payPalRequest.enablePayPalAppSwitch, isPayPalAppInstalled { + self.enablePayPalAppSwitch = payPalRequest.enablePayPalAppSwitch + self.osType = UIDevice.current.systemName + self.osVersion = UIDevice.current.systemVersion + self.universalLink = universalLink.absoluteString + return + } + + if let recurringBillingPlanType = payPalRequest.recurringBillingPlanType { + self.recurringBillingPlanType = recurringBillingPlanType + } + + if let recurringBillingDetails = payPalRequest.recurringBillingDetails { + self.recurringBillingDetails = recurringBillingDetails + } + + self.offerCredit = payPalRequest.offerCredit + + if let billingAgreementDescription = payPalRequest.billingAgreementDescription { + self.billingAgreementDescription = billingAgreementDescription + } + + if let shippingAddressOverride = payPalRequest.shippingAddressOverride { + self.shippingAddressOverride = shippingAddressOverride + } + } + + enum CodingKeys: String, CodingKey { + case billingAgreementDescription = "description" + case cancelURL = "cancel_url" + case enablePayPalAppSwitch = "launch_paypal_app" + case experienceProfile = "experience_profile" + case lineItems = "line_items" + case merchantAccountID = "merchant_account_id" + case offerCredit = "offer_paypal_credit" + case osType = "os_type" + case osVersion = "os_version" + case recurringBillingDetails = "plan_metadata" + case recurringBillingPlanType = "plan_type" + case returnURL = "return_url" + case riskCorrelationID = "correlation_id" + case shippingAddressOverride = "shipping_address" + case universalLink = "merchant_app_return_url" + case userAuthenticationEmail = "payer_email" + case userPhoneNumber = "phone_number" + } +} + +extension PayPalVaultPOSTBody { + + struct ExperienceProfile: Encodable { + + // MARK: - Private Properties + + private let displayName: String? + private let isShippingAddressRequired: Bool + private let shippingAddressOverride: Bool + + private var landingPageType: String? + private var localeCode: String? + + // MARK: - Initializer + + init(payPalRequest: BTPayPalVaultRequest, configuration: BTConfiguration) { + self.displayName = payPalRequest.displayName != nil ? payPalRequest.displayName : configuration.displayName + self.isShippingAddressRequired = !payPalRequest.isShippingAddressRequired + + if let landingPageType = payPalRequest.landingPageType?.stringValue { + self.landingPageType = landingPageType + } + + if let localeCode = payPalRequest.localeCode?.stringValue { + self.localeCode = localeCode + } + + self.shippingAddressOverride = payPalRequest.shippingAddressOverride != nil ? !payPalRequest.isShippingAddressEditable : false + } + + // swiftlint:disable nesting + enum CodingKeys: String, CodingKey { + case isShippingAddressRequired = "no_shipping" + case displayName = "brand_name" + case landingPageType = "landing_page_type" + case localeCode = "locale_code" + case shippingAddressOverride = "address_override" + } + } +} From 09d964d2a8dc5cdf6a2d5b24e81f8e613311daf9 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 6 Jan 2025 12:38:20 -0600 Subject: [PATCH 16/49] Add Encodable protocol to BTPayPalLineItem class --- .../BraintreePayPal/BTPayPalLineItem.swift | 44 ++++++++++++++++++- .../Models/PayPalCheckoutPOSTBody.swift | 5 +-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalLineItem.swift b/Sources/BraintreePayPal/BTPayPalLineItem.swift index dc7bc4e73..f3e1fe0b0 100644 --- a/Sources/BraintreePayPal/BTPayPalLineItem.swift +++ b/Sources/BraintreePayPal/BTPayPalLineItem.swift @@ -7,6 +7,15 @@ import Foundation /// Credit case credit + + var stringValue: String { + switch self { + case .debit: + return "debit" + case .credit: + return "credit" + } + } } // swiftlint:disable identifier_name @@ -61,7 +70,7 @@ import Foundation } /// A PayPal line item to be displayed in the PayPal checkout flow. -@objcMembers public class BTPayPalLineItem: NSObject { +@objcMembers public class BTPayPalLineItem: NSObject, Encodable { // MARK: - Public Properties @@ -114,6 +123,39 @@ import Foundation self.kind = kind } + enum CodingKeys: String, CodingKey { + case imageURL = "image_url" + case itemDescription = "description" + case kind + case name + case productCode = "product_code" + case quantity + case unitAmount = "unit_amount" + case unitTaxAmount = "unit_tax_amount" + case upcCode = "upc_code" + case upcType = "upc_type" + case url + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(quantity, forKey: .quantity) + try container.encode(unitAmount, forKey: .unitAmount) + try container.encode(name, forKey: .name) + try container.encode(kind.stringValue, forKey: .kind) + + if let unitTaxAmount, !unitTaxAmount.isEmpty { + try container.encode(unitAmount, forKey: .unitTaxAmount) + } + + try container.encodeIfPresent(itemDescription, forKey: .itemDescription) + try container.encodeIfPresent(productCode, forKey: .productCode) + try container.encodeIfPresent(url?.absoluteString, forKey: .url) + try container.encodeIfPresent(imageURL?.absoluteString, forKey: .imageURL) + try container.encodeIfPresent(upcCode, forKey: .upcCode) + try container.encodeIfPresent(upcType.stringValue, forKey: .upcType) + } + // MARK: - Internal Methods /// Returns the line item in a dictionary. diff --git a/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift index b8e6f4013..8b17ae7d8 100644 --- a/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift @@ -19,7 +19,7 @@ struct PayPalCheckoutPOSTBody: Encodable { private var billingAgreementDescription: BillingAgreemeentDescription? private var currencyCode: String? - private var lineItems: [[String: String]]? + private var lineItems: [BTPayPalLineItem]? private var merchantAccountID: String? private var requestBillingAgreement: Bool? private var riskCorrelationID: String? @@ -74,8 +74,7 @@ struct PayPalCheckoutPOSTBody: Encodable { } if let lineItems = payPalRequest.lineItems, !lineItems.isEmpty { - let lineItemsArray = lineItems.compactMap { $0.requestParameters() } - self.lineItems = lineItemsArray + self.lineItems = lineItems } if let userAuthenticationEmail = payPalRequest.userAuthenticationEmail, !userAuthenticationEmail.isEmpty { From 95434691b395af9f7f4f5104ee0e2c4f0710f175 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 6 Jan 2025 14:24:46 -0600 Subject: [PATCH 17/49] Use encodable for BTLineItems --- Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift index 0ce2aec35..d37ce473b 100644 --- a/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift @@ -16,8 +16,8 @@ struct PayPalVaultPOSTBody: Encodable { private let experienceProfile: ExperienceProfile private var billingAgreementDescription: String? - private var enablePayPalAppSwitch = false - private var lineItems: [[String: String]]? + private var enablePayPalAppSwitch: Bool? + private var lineItems: [BTPayPalLineItem]? private var merchantAccountID: String? private var offerCredit = false private var osType: String? @@ -46,8 +46,7 @@ struct PayPalVaultPOSTBody: Encodable { } if let lineItems = payPalRequest.lineItems, !lineItems.isEmpty { - let lineItemsArray = lineItems.compactMap { $0.requestParameters() } - self.lineItems = lineItemsArray + self.lineItems = lineItems } if let userAuthenticationEmail = payPalRequest.userAuthenticationEmail, !userAuthenticationEmail.isEmpty { From 27c0a4422e6bb5a5475590cfba54a0e890c06ea0 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 6 Jan 2025 14:25:29 -0600 Subject: [PATCH 18/49] Add error to catch encodable exception --- Sources/BraintreePayPal/BTPayPalError.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/BraintreePayPal/BTPayPalError.swift b/Sources/BraintreePayPal/BTPayPalError.swift index 3a947c3dd..9e0bb031a 100644 --- a/Sources/BraintreePayPal/BTPayPalError.swift +++ b/Sources/BraintreePayPal/BTPayPalError.swift @@ -44,6 +44,9 @@ public enum BTPayPalError: Error, CustomNSError, LocalizedError, Equatable { /// 13. Missing PayPal Request case missingPayPalRequest + + /// 14. Unable to create Encodable + case failedToCreateEncodable public static var errorDomain: String { "com.braintreepayments.BTPayPalErrorDomain" @@ -79,6 +82,8 @@ public enum BTPayPalError: Error, CustomNSError, LocalizedError, Equatable { return 12 case .missingPayPalRequest: return 13 + case .failedToCreateEncodable: + return 14 } } @@ -114,6 +119,8 @@ public enum BTPayPalError: Error, CustomNSError, LocalizedError, Equatable { return "Missing BA Token for PayPal App Switch." case .missingPayPalRequest: return "The PayPal Request was missing or invalid." + case .failedToCreateEncodable: + return "Unable to create Encodable object" } } From 4a6e7d85f243ad926cca5ccddbc29bf1662834b1 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 6 Jan 2025 14:34:51 -0600 Subject: [PATCH 19/49] Use encoded objects --- Sources/BraintreePayPal/BTPayPalClient.swift | 27 ++++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index bced82fac..1be1ba20f 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -356,13 +356,15 @@ import BraintreeDataCollector } self.payPalRequest = request + + guard let parameters = self.encodedPostBody(fromRequest: request, configuration: configuration) else { + self.notifyFailure(with: BTPayPalError.failedToCreateEncodable, completion: completion) + return + } + self.apiClient.post( request.hermesPath, - parameters: request.parameters( - with: configuration, - universalLink: self.universalLink, - isPayPalAppInstalled: self.application.isPayPalAppInstalled() - ) + parameters: parameters ) { body, _, error in if let error = error as? NSError { guard let jsonResponseBody = error.userInfo[BTCoreConstants.jsonResponseBodyKey] as? BTJSON else { @@ -401,6 +403,21 @@ import BraintreeDataCollector } } } + + private func encodedPostBody(fromRequest payPalRequest: BTPayPalRequest, configuration: BTConfiguration) -> Encodable? { + if let checkoutRequest = payPalRequest as? BTPayPalCheckoutRequest { + return PayPalCheckoutPOSTBody(payPalRequest: checkoutRequest, configuration: configuration) + } else if let vaultRequest = payPalRequest as? BTPayPalVaultRequest { + return PayPalVaultPOSTBody( + payPalRequest: vaultRequest, + configuration: configuration, + isPayPalAppInstalled: application.isPayPalAppInstalled(), + universalLink: universalLink + ) + } else { + return nil + } + } private func launchPayPalApp( with payPalAppRedirectURL: URL, From 42af3f79c38f79857f7ee5602a6775570a111061 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 6 Jan 2025 14:36:16 -0600 Subject: [PATCH 20/49] Use Configuration.isPayPalEnabled instead of json format --- Sources/BraintreePayPal/BTPayPalClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index 1be1ba20f..ce13e4487 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -350,7 +350,7 @@ import BraintreeDataCollector self.isConfigFromCache = configuration.isFromCache - guard json["paypalEnabled"].isTrue else { + guard configuration.isPayPalEnabled else { self.notifyFailure(with: BTPayPalError.disabled, completion: completion) return } From 2200e5c6d315715937c4105f867733405745fd53 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 6 Jan 2025 14:49:30 -0600 Subject: [PATCH 21/49] Add PayPalExperienceProfile as a shared object to be use for Checkout and Vault encodable objects --- Braintree.xcodeproj/project.pbxproj | 4 ++ .../Models/PayPalCheckoutPOSTBody.swift | 48 +------------- .../PayPalExperienceProfilePOSTBody.swift | 63 +++++++++++++++++++ .../Models/PayPalVaultPOSTBody.swift | 45 +------------ 4 files changed, 71 insertions(+), 89 deletions(-) create mode 100644 Sources/BraintreePayPal/Models/PayPalExperienceProfilePOSTBody.swift diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index a943bccc2..f872bbde0 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -46,6 +46,7 @@ 4585707C2C34B7B5009CEF7A /* MockConfigurationLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4585707B2C34B7B5009CEF7A /* MockConfigurationLoader.swift */; }; 45E8CE4C2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */; }; 45E8CE4E2D28611700D7A2DC /* PayPalVaultPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE4D2D28611700D7A2DC /* PayPalVaultPOSTBody.swift */; }; + 45E8CE502D2C773600D7A2DC /* PayPalExperienceProfilePOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE4F2D2C772000D7A2DC /* PayPalExperienceProfilePOSTBody.swift */; }; 45EFC3972C2DBF32005E7F5B /* ConfigurationLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45EFC3962C2DBF32005E7F5B /* ConfigurationLoader.swift */; }; 460C0C220F594AE8EE205E57 /* Pods_Tests_BraintreeCoreTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9239C9FE850C3587DE61A3A2 /* Pods_Tests_BraintreeCoreTests.framework */; }; 5708E0A628809AD9007946B9 /* BTJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5708E0A528809AD9007946B9 /* BTJSON.swift */; }; @@ -729,6 +730,7 @@ 4585707B2C34B7B5009CEF7A /* MockConfigurationLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockConfigurationLoader.swift; sourceTree = ""; }; 45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalCheckoutPOSTBody.swift; sourceTree = ""; }; 45E8CE4D2D28611700D7A2DC /* PayPalVaultPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalVaultPOSTBody.swift; sourceTree = ""; }; + 45E8CE4F2D2C772000D7A2DC /* PayPalExperienceProfilePOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalExperienceProfilePOSTBody.swift; sourceTree = ""; }; 45EFC3962C2DBF32005E7F5B /* ConfigurationLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationLoader.swift; sourceTree = ""; }; 463DED22C0F426A474E6D7E2 /* Pods-Tests-BraintreeCoreTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-BraintreeCoreTests.release.xcconfig"; path = "Target Support Files/Pods-Tests-BraintreeCoreTests/Pods-Tests-BraintreeCoreTests.release.xcconfig"; sourceTree = ""; }; 541AEE40A1F01913E0638CC9 /* Pods-Tests-BraintreeCoreTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-BraintreeCoreTests.debug.xcconfig"; path = "Target Support Files/Pods-Tests-BraintreeCoreTests/Pods-Tests-BraintreeCoreTests.debug.xcconfig"; sourceTree = ""; }; @@ -1323,6 +1325,7 @@ children = ( 45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */, 45E8CE4D2D28611700D7A2DC /* PayPalVaultPOSTBody.swift */, + 45E8CE4F2D2C772000D7A2DC /* PayPalExperienceProfilePOSTBody.swift */, ); path = Models; sourceTree = ""; @@ -3116,6 +3119,7 @@ BEF5D2E6294A18B300FFD56D /* BTPayPalLineItem.swift in Sources */, BE349113294B798300D2CF68 /* BTPayPalRequest.swift in Sources */, BE549F112BF5445F00B6F441 /* BTPayPalReturnURL.swift in Sources */, + 45E8CE502D2C773600D7A2DC /* PayPalExperienceProfilePOSTBody.swift in Sources */, 57544F5C295254A500DEB7B0 /* BTJSON+PayPal.swift in Sources */, 3B7A261129C0CAA40087059D /* BTPayPalAnalytics.swift in Sources */, BE8E5CEF294B6937001BF017 /* BTPayPalCheckoutRequest.swift in Sources */, diff --git a/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift index 8b17ae7d8..4bd815722 100644 --- a/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift @@ -15,7 +15,7 @@ struct PayPalCheckoutPOSTBody: Encodable { private let userPhoneNumber: BTPayPalPhoneNumber? private let returnURL: String private let cancelURL: String - private let experienceProfile: ExperienceProfile + private let experienceProfile: PayPalExperienceProfile private var billingAgreementDescription: BillingAgreemeentDescription? private var currencyCode: String? @@ -84,7 +84,7 @@ struct PayPalCheckoutPOSTBody: Encodable { self.userPhoneNumber = payPalRequest.userPhoneNumber self.returnURL = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)success" self.cancelURL = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)cancel" - self.experienceProfile = ExperienceProfile(payPalRequest: payPalRequest, configuration: configuration) + self.experienceProfile = PayPalExperienceProfile(payPalRequest: payPalRequest, configuration: configuration) } enum CodingKeys: String, CodingKey { @@ -128,48 +128,4 @@ extension PayPalCheckoutPOSTBody { self.description = description } } - - struct ExperienceProfile: Encodable { - - // MARK: - Private Properties - - private let displayName: String? - private let isShippingAddressRequired: Bool - private let shippingAddressOverride: Bool - - private var landingPageType: String? - private var localeCode: String? - private var userAction: String? - - // MARK: - Initializer - - init(payPalRequest: BTPayPalCheckoutRequest, configuration: BTConfiguration) { - self.displayName = payPalRequest.displayName != nil ? payPalRequest.displayName : configuration.displayName - self.isShippingAddressRequired = !payPalRequest.isShippingAddressRequired - - if let landingPageType = payPalRequest.landingPageType?.stringValue { - self.landingPageType = landingPageType - } - - if let localeCode = payPalRequest.localeCode?.stringValue { - self.localeCode = localeCode - } - - self.shippingAddressOverride = payPalRequest.shippingAddressOverride != nil ? !payPalRequest.isShippingAddressEditable : false - - if payPalRequest.userAction != .none { - self.userAction = payPalRequest.userAction.stringValue - } - } - - // swiftlint:disable nesting - enum CodingKeys: String, CodingKey { - case isShippingAddressRequired = "no_shipping" - case displayName = "brand_name" - case landingPageType = "landing_page_type" - case localeCode = "locale_code" - case shippingAddressOverride = "address_override" - case userAction = "user_action" - } - } } diff --git a/Sources/BraintreePayPal/Models/PayPalExperienceProfilePOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalExperienceProfilePOSTBody.swift new file mode 100644 index 000000000..9a0b0082d --- /dev/null +++ b/Sources/BraintreePayPal/Models/PayPalExperienceProfilePOSTBody.swift @@ -0,0 +1,63 @@ +import Foundation + +#if canImport(BraintreeCore) +import BraintreeCore +#endif + +struct PayPalExperienceProfile: Encodable { + + // MARK: - Private Properties + + private let displayName: String? + private let isShippingAddressRequired: Bool + private let shippingAddressOverride: Bool + + private var landingPageType: String? + private var localeCode: String? + private var userAction: String? + + // MARK: - Initializer + + init(payPalRequest: BTPayPalCheckoutRequest, configuration: BTConfiguration) { + self.displayName = payPalRequest.displayName != nil ? payPalRequest.displayName : configuration.displayName + self.isShippingAddressRequired = !payPalRequest.isShippingAddressRequired + + if let landingPageType = payPalRequest.landingPageType?.stringValue { + self.landingPageType = landingPageType + } + + if let localeCode = payPalRequest.localeCode?.stringValue { + self.localeCode = localeCode + } + + self.shippingAddressOverride = payPalRequest.shippingAddressOverride != nil ? !payPalRequest.isShippingAddressEditable : false + + if payPalRequest.userAction != .none { + self.userAction = payPalRequest.userAction.stringValue + } + } + + init(payPalRequest: BTPayPalVaultRequest, configuration: BTConfiguration) { + self.displayName = payPalRequest.displayName != nil ? payPalRequest.displayName : configuration.displayName + self.isShippingAddressRequired = !payPalRequest.isShippingAddressRequired + + if let landingPageType = payPalRequest.landingPageType?.stringValue { + self.landingPageType = landingPageType + } + + if let localeCode = payPalRequest.localeCode?.stringValue { + self.localeCode = localeCode + } + + self.shippingAddressOverride = payPalRequest.shippingAddressOverride != nil ? !payPalRequest.isShippingAddressEditable : false + } + + enum CodingKeys: String, CodingKey { + case isShippingAddressRequired = "no_shipping" + case displayName = "brand_name" + case landingPageType = "landing_page_type" + case localeCode = "locale_code" + case shippingAddressOverride = "address_override" + case userAction = "user_action" + } +} diff --git a/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift index d37ce473b..6022454f0 100644 --- a/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift @@ -13,7 +13,7 @@ struct PayPalVaultPOSTBody: Encodable { private let userPhoneNumber: BTPayPalPhoneNumber? private let returnURL: String private let cancelURL: String - private let experienceProfile: ExperienceProfile + private let experienceProfile: PayPalExperienceProfile private var billingAgreementDescription: String? private var enablePayPalAppSwitch: Bool? @@ -56,7 +56,7 @@ struct PayPalVaultPOSTBody: Encodable { self.userPhoneNumber = payPalRequest.userPhoneNumber self.returnURL = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)success" self.cancelURL = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)cancel" - self.experienceProfile = ExperienceProfile(payPalRequest: payPalRequest, configuration: configuration) + self.experienceProfile = PayPalExperienceProfile(payPalRequest: payPalRequest, configuration: configuration) if let universalLink, payPalRequest.enablePayPalAppSwitch, isPayPalAppInstalled { self.enablePayPalAppSwitch = payPalRequest.enablePayPalAppSwitch @@ -105,44 +105,3 @@ struct PayPalVaultPOSTBody: Encodable { case userPhoneNumber = "phone_number" } } - -extension PayPalVaultPOSTBody { - - struct ExperienceProfile: Encodable { - - // MARK: - Private Properties - - private let displayName: String? - private let isShippingAddressRequired: Bool - private let shippingAddressOverride: Bool - - private var landingPageType: String? - private var localeCode: String? - - // MARK: - Initializer - - init(payPalRequest: BTPayPalVaultRequest, configuration: BTConfiguration) { - self.displayName = payPalRequest.displayName != nil ? payPalRequest.displayName : configuration.displayName - self.isShippingAddressRequired = !payPalRequest.isShippingAddressRequired - - if let landingPageType = payPalRequest.landingPageType?.stringValue { - self.landingPageType = landingPageType - } - - if let localeCode = payPalRequest.localeCode?.stringValue { - self.localeCode = localeCode - } - - self.shippingAddressOverride = payPalRequest.shippingAddressOverride != nil ? !payPalRequest.isShippingAddressEditable : false - } - - // swiftlint:disable nesting - enum CodingKeys: String, CodingKey { - case isShippingAddressRequired = "no_shipping" - case displayName = "brand_name" - case landingPageType = "landing_page_type" - case localeCode = "locale_code" - case shippingAddressOverride = "address_override" - } - } -} From 1553b225ec7ca3e261076427c2225ef446603fae Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 10:50:11 -0600 Subject: [PATCH 22/49] Add PayPalRequest protocol, BTPayPalRequest conforms PayPalRequest --- Sources/BraintreePayPal/BTPayPalRequest.swift | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalRequest.swift b/Sources/BraintreePayPal/BTPayPalRequest.swift index 93bcd6ff7..be2b90dee 100644 --- a/Sources/BraintreePayPal/BTPayPalRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalRequest.swift @@ -48,9 +48,43 @@ import BraintreeCore } } +protocol PayPalRequest { + var hermesPath: String { get } + var paymentType: BTPayPalPaymentType { get } + var billingAgreementDescription: String? { get } + var displayName: String? { get } + var isShippingAddressEditable: Bool { get } + var isShippingAddressRequired: Bool { get } + var landingPageType: BTPayPalRequestLandingPageType? { get } + var lineItems: [BTPayPalLineItem]? { get } + var localeCode: BTPayPalLocaleCode? { get } + var merchantAccountID: String? { get } + var riskCorrelationID: String? { get } + var shippingAddressOverride: BTPostalAddress? { get } + var userAuthenticationEmail: String? { get } + var userPhoneNumber: BTPayPalPhoneNumber? { get } + + // MARK: - Static Properties + + static var callbackURLHostAndPath: String { get } + + func parameters( + with configuration: BTConfiguration, + universalLink: URL?, + isPayPalAppInstalled: Bool + ) -> [String: Any] +} + +extension PayPalRequest { + + static var callbackURLHostAndPath: String { + "onetouch/v1/" + } +} + /// Base options for PayPal Checkout and PayPal Vault flows. /// - Note: Do not instantiate this class directly. Instead, use BTPayPalCheckoutRequest or BTPayPalVaultRequest. -@objcMembers open class BTPayPalRequest: NSObject { +@objcMembers open class BTPayPalRequest: NSObject, PayPalRequest { // MARK: - Internal Properties @@ -68,10 +102,6 @@ import BraintreeCore var riskCorrelationID: String? var userAuthenticationEmail: String? var userPhoneNumber: BTPayPalPhoneNumber? - - // MARK: - Static Properties - - static let callbackURLHostAndPath: String = "onetouch/v1/" // MARK: - Initializer From f9c90ecabdf834a80a0fada042c457b8a41c79b0 Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 10:58:46 -0600 Subject: [PATCH 23/49] BTPayPalCheckoutRequest conforms PayPalRequest --- .../BTPayPalCheckoutRequest.swift | 114 +++++++++++++++--- 1 file changed, 99 insertions(+), 15 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift index cc6f98898..3d7705a8e 100644 --- a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift @@ -56,17 +56,32 @@ import BraintreeCore } /// Options for the PayPal Checkout flow. -@objcMembers open class BTPayPalCheckoutRequest: BTPayPalRequest { - +@objcMembers public class BTPayPalCheckoutRequest: NSObject, PayPalRequest { + // MARK: - Internal Properties + let hermesPath: String + let paymentType: BTPayPalPaymentType + var amount: String var intent: BTPayPalRequestIntent var userAction: BTPayPalRequestUserAction var offerPayLater: Bool + var billingAgreementDescription: String? var currencyCode: String? + var displayName: String? + var isShippingAddressEditable: Bool = false + var isShippingAddressRequired: Bool = false + var landingPageType: BTPayPalRequestLandingPageType? + var lineItems: [BTPayPalLineItem]? + var localeCode: BTPayPalLocaleCode? + var merchantAccountID: String? var requestBillingAgreement: Bool - + var riskCorrelationID: String? + var shippingAddressOverride: BTPostalAddress? + var userAuthenticationEmail: String? + var userPhoneNumber: BTPayPalPhoneNumber? + // MARK: - Initializer /// Initializes a PayPal Native Checkout request @@ -75,48 +90,117 @@ import BraintreeCore /// - intent: Optional: Payment intent. Defaults to `.authorize`. Only applies to PayPal Checkout. /// - userAction: Optional: Changes the call-to-action in the PayPal Checkout flow. Defaults to `.none`. /// - offerPayLater: Optional: Offers PayPal Pay Later if the customer qualifies. Defaults to `false`. Only available with PayPal Checkout. + /// - billingAgreementDescription: Optional: Display a custom description to the user for a billing agreement. For Checkout with Vault flows, you must also set /// - currencyCode: Optional: A three-character ISO-4217 ISO currency code to use for the transaction. Defaults to merchant currency code if not set. /// See https://developer.paypal.com/docs/api/reference/currency-codes/ for a list of supported currency codes. + /// - displayName: Optional: The merchant name displayed inside of the PayPal flow; defaults to the company name on your Braintree account + /// - isShippingAddressEditable: Defaults to false. Set to true to enable user editing of the shipping address. + /// - isShippingAddressRequired: Defaults to false. When set to true, the shipping address selector will be displayed. + /// - landingPageType: Optional: Landing page type. Defaults to `.none`. + /// - Note: Setting the BTPayPalRequest's landingPageType changes the PayPal page to display when a user lands on the PayPal site to complete the payment. + /// `.login` specifies a PayPal account login page is used. + /// `.billing` specifies a non-PayPal account landing page is used. + /// - lineItems: Optional: The line items for this transaction. It can include up to 249 line items. + /// - localeCode: Optional: A locale code to use for the transaction. + /// - merchantAccountID: Optional: A non-default merchant account to use for tokenization. /// - requestBillingAgreement: Optional: If set to `true`, this enables the Checkout with Vault flow, where the customer will be prompted to consent to a billing agreement /// during checkout. Defaults to `false`. + /// - riskCorrelationID: Optional: A risk correlation ID created with Set Transaction Context on your server. + /// - shippingAddressOverride: Optional: A valid shipping address to be displayed in the transaction flow. An error will occur if this address is not valid. /// - userAuthenticationEmail: Optional: User email to initiate a quicker authentication flow in cases where the user has a PayPal Account with the same email. /// - userPhoneNumber: Optional: A user's phone number to initiate a quicker authentication flow in the scenario where the user has a PayPal account /// identified with the same phone number. - /// - lineItems: Optional: The line items for this transaction. It can include up to 249 line items. public init( amount: String, intent: BTPayPalRequestIntent = .authorize, userAction: BTPayPalRequestUserAction = .none, offerPayLater: Bool = false, + billingAgreementDescription: String? = nil, currencyCode: String? = nil, + displayName: String? = nil, + isShippingAddressEditable: Bool = false, + isShippingAddressRequired: Bool = false, + landingPageType: BTPayPalRequestLandingPageType = .none, + lineItems: [BTPayPalLineItem]? = nil, + localeCode: BTPayPalLocaleCode = .none, + merchantAccountID: String? = nil, requestBillingAgreement: Bool = false, + riskCorrelationID: String? = nil, + shippingAddressOverride: BTPostalAddress? = nil, userAuthenticationEmail: String? = nil, - userPhoneNumber: BTPayPalPhoneNumber? = nil, - lineItems: [BTPayPalLineItem]? = nil + userPhoneNumber: BTPayPalPhoneNumber? = nil ) { + self.hermesPath = "v1/paypal_hermes/create_payment_resource" + self.paymentType = .checkout self.amount = amount self.intent = intent self.userAction = userAction self.offerPayLater = offerPayLater + self.billingAgreementDescription = billingAgreementDescription self.currencyCode = currencyCode + self.displayName = displayName + self.isShippingAddressEditable = isShippingAddressEditable + self.isShippingAddressRequired = isShippingAddressRequired + self.landingPageType = landingPageType + self.lineItems = lineItems + self.localeCode = localeCode + self.merchantAccountID = merchantAccountID self.requestBillingAgreement = requestBillingAgreement - super.init( - hermesPath: "v1/paypal_hermes/create_payment_resource", - paymentType: .checkout, - lineItems: lineItems, - userAuthenticationEmail: userAuthenticationEmail, - userPhoneNumber: userPhoneNumber - ) + self.riskCorrelationID = riskCorrelationID + self.shippingAddressOverride = shippingAddressOverride + self.userAuthenticationEmail = userAuthenticationEmail + self.userPhoneNumber = userPhoneNumber } // MARK: Internal Methods - override func parameters( + func parameters( with configuration: BTConfiguration, universalLink: URL? = nil, isPayPalAppInstalled: Bool = false ) -> [String: Any] { - var baseParameters = super.parameters(with: configuration) + var experienceProfile: [String: Any] = [:] + + experienceProfile["no_shipping"] = !isShippingAddressRequired + experienceProfile["brand_name"] = displayName != nil ? displayName : configuration.json?["paypal"]["displayName"].asString() + + if landingPageType?.stringValue != nil { + experienceProfile["landing_page_type"] = landingPageType?.stringValue + } + + if localeCode?.stringValue != nil { + experienceProfile["locale_code"] = localeCode?.stringValue + } + + experienceProfile["address_override"] = shippingAddressOverride != nil ? !isShippingAddressEditable : false + + var baseParameters: [String: Any] = [:] + + if merchantAccountID != nil { + baseParameters["merchant_account_id"] = merchantAccountID + } + + if riskCorrelationID != nil { + baseParameters["correlation_id"] = riskCorrelationID + } + + if let lineItems, !lineItems.isEmpty { + let lineItemsArray = lineItems.compactMap { $0.requestParameters() } + baseParameters["line_items"] = lineItemsArray + } + + if let userAuthenticationEmail, !userAuthenticationEmail.isEmpty { + baseParameters["payer_email"] = userAuthenticationEmail + } + + if let userPhoneNumberDict = try? userPhoneNumber?.toDictionary() { + baseParameters["phone_number"] = userPhoneNumberDict + } + + baseParameters["return_url"] = BTCoreConstants.callbackURLScheme + "://\(Self.callbackURLHostAndPath)success" + baseParameters["cancel_url"] = BTCoreConstants.callbackURLScheme + "://\(Self.callbackURLHostAndPath)cancel" + baseParameters["experience_profile"] = experienceProfile + var checkoutParameters: [String: Any] = [ "intent": intent.stringValue, "amount": amount, From abe2c7e387434766ef962d779d2640c233e25ca5 Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 10:59:06 -0600 Subject: [PATCH 24/49] Move lineItems parameters on init --- .../Features/PayPalWebCheckoutViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Demo/Application/Features/PayPalWebCheckoutViewController.swift b/Demo/Application/Features/PayPalWebCheckoutViewController.swift index d851698fc..bf666a94b 100644 --- a/Demo/Application/Features/PayPalWebCheckoutViewController.swift +++ b/Demo/Application/Features/PayPalWebCheckoutViewController.swift @@ -144,12 +144,12 @@ class PayPalWebCheckoutViewController: PaymentButtonBaseViewController { amount: "5.00", intent: newPayPalCheckoutToggle.isOn ? .sale : .authorize, offerPayLater: payLaterToggle.isOn, + lineItems: [lineItem], userAuthenticationEmail: emailTextField.text, userPhoneNumber: BTPayPalPhoneNumber( countryCode: countryCodeTextField.text ?? "", nationalNumber: nationalNumberTextField.text ?? "" - ), - lineItems: [lineItem] + ) ) payPalClient.tokenize(request) { nonce, error in From b0cea9396b9b2767e33ed49145288d3e4e37e2e3 Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 11:44:23 -0600 Subject: [PATCH 25/49] BTPayPalVaultRequest conforms PayPalRequest --- .../BTPayPalVaultRequest.swift | 112 ++++++++++++++++-- 1 file changed, 100 insertions(+), 12 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift index 02616b18a..7e6dfc100 100644 --- a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift @@ -5,15 +5,30 @@ import BraintreeCore #endif /// Options for the PayPal Vault flow. -@objcMembers public class BTPayPalVaultRequest: BTPayPalRequest { - +@objcMembers public class BTPayPalVaultRequest: NSObject, PayPalRequest { + // MARK: - Internal Properties + let hermesPath: String + let paymentType: BTPayPalPaymentType + var offerCredit: Bool var enablePayPalAppSwitch: Bool = false - var recurringBillingPlanType: BTPayPalRecurringBillingPlanType? + var billingAgreementDescription: String? + var displayName: String? + var isShippingAddressEditable: Bool = false + var isShippingAddressRequired: Bool = false + var landingPageType: BTPayPalRequestLandingPageType? + var lineItems: [BTPayPalLineItem]? + var localeCode: BTPayPalLocaleCode? + var merchantAccountID: String? var recurringBillingDetails: BTPayPalRecurringBillingDetails? - + var recurringBillingPlanType: BTPayPalRecurringBillingPlanType? + var riskCorrelationID: String? + var shippingAddressOverride: BTPostalAddress? + var userAuthenticationEmail: String? + var userPhoneNumber: BTPayPalPhoneNumber? + // MARK: - Initializers /// Initializes a PayPal Vault request for the PayPal App Switch flow @@ -35,35 +50,108 @@ import BraintreeCore /// Initializes a PayPal Vault request /// - Parameters: /// - offerCredit: Optional: Offers PayPal Credit if the customer qualifies. Defaults to `false`. + /// - billingAgreementDescription: Optional: Display a custom description to the user for a billing agreement. For Checkout with Vault flows, you must also set + /// - currencyCode: Optional: A three-character ISO-4217 ISO currency code to use for the transaction. Defaults to merchant currency code if not set. + /// See https://developer.paypal.com/docs/api/reference/currency-codes/ for a list of supported currency codes. + /// - displayName: Optional: The merchant name displayed inside of the PayPal flow; defaults to the company name on your Braintree account + /// - isShippingAddressEditable: Defaults to false. Set to true to enable user editing of the shipping address. + /// - isShippingAddressRequired: Defaults to false. When set to true, the shipping address selector will be displayed. + /// - landingPageType: Optional: Landing page type. Defaults to `.none`. + /// - Note: Setting the BTPayPalRequest's landingPageType changes the PayPal page to display when a user lands on the PayPal site to complete the payment. + /// `.login` specifies a PayPal account login page is used. + /// `.billing` specifies a non-PayPal account landing page is used. + /// - lineItems: Optional: The line items for this transaction. It can include up to 249 line items. + /// - localeCode: Optional: A locale code to use for the transaction. + /// - merchantAccountID: Optional: A non-default merchant account to use for tokenization. /// - recurringBillingDetails: Optional: Recurring billing product details. /// - recurringBillingPlanType: Optional: Recurring billing plan type, or charge pattern. + /// - riskCorrelationID: Optional: A risk correlation ID created with Set Transaction Context on your server. + /// - shippingAddressOverride: Optional: A valid shipping address to be displayed in the transaction flow. An error will occur if this address is not valid. /// - userAuthenticationEmail: Optional: User email to initiate a quicker authentication flow in cases where the user has a PayPal Account with the same email. /// - userPhoneNumber: Optional: A user's phone number to initiate a quicker authentication flow in the scenario where the user has a PayPal account /// identified with the same phone number. public init( offerCredit: Bool = false, + billingAgreementDescription: String? = nil, + displayName: String? = nil, + isShippingAddressEditable: Bool = false, + isShippingAddressRequired: Bool = false, + landingPageType: BTPayPalRequestLandingPageType = .none, + lineItems: [BTPayPalLineItem]? = nil, + localeCode: BTPayPalLocaleCode = .none, + merchantAccountID: String? = nil, recurringBillingDetails: BTPayPalRecurringBillingDetails? = nil, recurringBillingPlanType: BTPayPalRecurringBillingPlanType? = nil, + riskCorrelationID: String? = nil, + shippingAddressOverride: BTPostalAddress? = nil, userAuthenticationEmail: String? = nil, userPhoneNumber: BTPayPalPhoneNumber? = nil ) { + self.hermesPath = "v1/paypal_hermes/setup_billing_agreement" + self.paymentType = .vault self.offerCredit = offerCredit + self.billingAgreementDescription = billingAgreementDescription + self.displayName = displayName + self.isShippingAddressEditable = isShippingAddressEditable + self.isShippingAddressRequired = isShippingAddressRequired + self.landingPageType = landingPageType + self.lineItems = lineItems + self.localeCode = localeCode + self.merchantAccountID = merchantAccountID self.recurringBillingDetails = recurringBillingDetails self.recurringBillingPlanType = recurringBillingPlanType - super.init( - hermesPath: "v1/paypal_hermes/setup_billing_agreement", - paymentType: .vault, - userAuthenticationEmail: userAuthenticationEmail, - userPhoneNumber: userPhoneNumber - ) + self.riskCorrelationID = riskCorrelationID + self.shippingAddressOverride = shippingAddressOverride + self.userAuthenticationEmail = userAuthenticationEmail + self.userPhoneNumber = userPhoneNumber } - public override func parameters( + func parameters( with configuration: BTConfiguration, universalLink: URL? = nil, isPayPalAppInstalled: Bool = false ) -> [String: Any] { - var baseParameters = super.parameters(with: configuration) + var experienceProfile: [String: Any] = [:] + + experienceProfile["no_shipping"] = !isShippingAddressRequired + experienceProfile["brand_name"] = displayName != nil ? displayName : configuration.json?["paypal"]["displayName"].asString() + + if landingPageType?.stringValue != nil { + experienceProfile["landing_page_type"] = landingPageType?.stringValue + } + + if localeCode?.stringValue != nil { + experienceProfile["locale_code"] = localeCode?.stringValue + } + + experienceProfile["address_override"] = shippingAddressOverride != nil ? !isShippingAddressEditable : false + + var baseParameters: [String: Any] = [:] + + if merchantAccountID != nil { + baseParameters["merchant_account_id"] = merchantAccountID + } + + if riskCorrelationID != nil { + baseParameters["correlation_id"] = riskCorrelationID + } + + if let lineItems, !lineItems.isEmpty { + let lineItemsArray = lineItems.compactMap { $0.requestParameters() } + baseParameters["line_items"] = lineItemsArray + } + + if let userAuthenticationEmail, !userAuthenticationEmail.isEmpty { + baseParameters["payer_email"] = userAuthenticationEmail + } + + if let userPhoneNumberDict = try? userPhoneNumber?.toDictionary() { + baseParameters["phone_number"] = userPhoneNumberDict + } + + baseParameters["return_url"] = BTCoreConstants.callbackURLScheme + "://\(Self.callbackURLHostAndPath)success" + baseParameters["cancel_url"] = BTCoreConstants.callbackURLScheme + "://\(Self.callbackURLHostAndPath)cancel" + baseParameters["experience_profile"] = experienceProfile if let universalLink, enablePayPalAppSwitch, isPayPalAppInstalled { let appSwitchParameters: [String: Any] = [ From 4a3b49cab3a1f4ba85d49969186b0e134a08118f Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 11:45:39 -0600 Subject: [PATCH 26/49] Update BTPayPalRequest UTs --- UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift index 787e9ef35..f44eebf97 100644 --- a/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift @@ -441,8 +441,7 @@ class BTPayPalClient_Tests: XCTestCase { func testHandleBrowserSwitchReturn_whenBrowserSwitchSucceeds_merchantAccountIdIsSet() { let merchantAccountID = "alternate-merchant-account-id" - payPalClient.payPalRequest = BTPayPalCheckoutRequest(amount: "1.34") - payPalClient.payPalRequest?.merchantAccountID = merchantAccountID + payPalClient.payPalRequest = BTPayPalCheckoutRequest(amount: "1.34", merchantAccountID: merchantAccountID) let returnURL = URL(string: "bar://onetouch/v1/success?token=hermes_token")! payPalClient.handleReturn(returnURL, paymentType: .checkout) { _, _ in } @@ -493,8 +492,7 @@ class BTPayPalClient_Tests: XCTestCase { func testHandleBrowserSwitchReturn_whenBrowserSwitchSucceeds_parametersAreConstructedAsExpected() { let merchantAccountID = "alternate-merchant-account-id" - payPalClient.payPalRequest = BTPayPalCheckoutRequest(amount: "1.34") - payPalClient.payPalRequest?.merchantAccountID = merchantAccountID + payPalClient.payPalRequest = BTPayPalCheckoutRequest(amount: "1.34", merchantAccountID: merchantAccountID) payPalClient.clientMetadataID = "a-fake-cmid" let returnURL = URL(string: "bar://onetouch/v1/success?token=hermes_token")! From 49886c73994e317fc3ea08219fba8639423569df Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 11:47:56 -0600 Subject: [PATCH 27/49] Update BTPayPalClient to use an interface instead of concrete class --- Sources/BraintreePayPal/BTPayPalClient.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index bced82fac..7dc2307b9 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -29,7 +29,7 @@ import BraintreeDataCollector var clientMetadataID: String? /// Exposed for testing the intent associated with this request - var payPalRequest: BTPayPalRequest? + var payPalRequest: PayPalRequest? /// Exposed for testing, the ASWebAuthenticationSession instance used for the PayPal flow var webAuthenticationSession: BTWebAuthenticationSession @@ -331,7 +331,7 @@ import BraintreeDataCollector // MARK: - Private Methods private func tokenize( - request: BTPayPalRequest, + request: PayPalRequest, completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void ) { linkType = (request as? BTPayPalVaultRequest)?.enablePayPalAppSwitch == true ? .universal : .deeplink From 3c1701ee3ae1592c9795bb0a939310a0c135f36d Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 11:54:17 -0600 Subject: [PATCH 28/49] Disable cyclomatic_complexity lint --- Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift | 1 + Sources/BraintreePayPal/BTPayPalVaultRequest.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift index 3d7705a8e..d18224dad 100644 --- a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift @@ -154,6 +154,7 @@ import BraintreeCore // MARK: Internal Methods + // swiftlint:disable cyclomatic_complexity func parameters( with configuration: BTConfiguration, universalLink: URL? = nil, diff --git a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift index 7e6dfc100..f2afc77a5 100644 --- a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift @@ -106,6 +106,7 @@ import BraintreeCore self.userPhoneNumber = userPhoneNumber } + // swiftlint:disable cyclomatic_complexity func parameters( with configuration: BTConfiguration, universalLink: URL? = nil, From b6ce34b7d8b974da86ce1c5eda1f0922a230742e Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 12:03:07 -0600 Subject: [PATCH 29/49] Disable function_body_length lint --- Sources/BraintreePayPal/BTPayPalVaultRequest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift index f2afc77a5..4bbaaea5f 100644 --- a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift @@ -106,7 +106,7 @@ import BraintreeCore self.userPhoneNumber = userPhoneNumber } - // swiftlint:disable cyclomatic_complexity + // swiftlint:disable cyclomatic_complexity function_body_length func parameters( with configuration: BTConfiguration, universalLink: URL? = nil, From 8e12bf81d41f046ac0832e8eba3895469c570f45 Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 13:06:07 -0600 Subject: [PATCH 30/49] Address PR comments --- Sources/BraintreePayPal/BTPayPalClient.swift | 16 +++++++++------- Sources/BraintreePayPal/BTPayPalError.swift | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index 522a0f06b..83344ef1e 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -343,7 +343,7 @@ import BraintreeDataCollector return } - guard let configuration, let json = configuration.json else { + guard let configuration else { self.notifyFailure(with: BTPayPalError.fetchConfigurationFailed, completion: completion) return } @@ -357,7 +357,7 @@ import BraintreeDataCollector self.payPalRequest = request - guard let parameters = self.encodedPostBody(fromRequest: request, configuration: configuration) else { + guard let parameters = self.encodedPostBody(from: request, configuration: configuration) else { self.notifyFailure(with: BTPayPalError.failedToCreateEncodable, completion: completion) return } @@ -404,18 +404,20 @@ import BraintreeDataCollector } } - private func encodedPostBody(fromRequest payPalRequest: BTPayPalRequest, configuration: BTConfiguration) -> Encodable? { - if let checkoutRequest = payPalRequest as? BTPayPalCheckoutRequest { + private func encodedPostBody(from payPalRequest: PayPalRequest, configuration: BTConfiguration) -> Encodable? { + switch payPalRequest.paymentType { + case .checkout: + guard let checkoutRequest = payPalRequest as? BTPayPalCheckoutRequest else { return nil } return PayPalCheckoutPOSTBody(payPalRequest: checkoutRequest, configuration: configuration) - } else if let vaultRequest = payPalRequest as? BTPayPalVaultRequest { + + case .vault: + guard let vaultRequest = payPalRequest as? BTPayPalVaultRequest else { return nil } return PayPalVaultPOSTBody( payPalRequest: vaultRequest, configuration: configuration, isPayPalAppInstalled: application.isPayPalAppInstalled(), universalLink: universalLink ) - } else { - return nil } } diff --git a/Sources/BraintreePayPal/BTPayPalError.swift b/Sources/BraintreePayPal/BTPayPalError.swift index 9e0bb031a..a9add815b 100644 --- a/Sources/BraintreePayPal/BTPayPalError.swift +++ b/Sources/BraintreePayPal/BTPayPalError.swift @@ -120,7 +120,7 @@ public enum BTPayPalError: Error, CustomNSError, LocalizedError, Equatable { case .missingPayPalRequest: return "The PayPal Request was missing or invalid." case .failedToCreateEncodable: - return "Unable to create Encodable object" + return "The request was not a Checkout or Vault request." } } From 8b18aeb6649cbc9e857a763bd321eb3efa30a46a Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 13:54:18 -0600 Subject: [PATCH 31/49] Remove parameters method --- .../BTPayPalCheckoutRequest.swift | 88 ------------------ Sources/BraintreePayPal/BTPayPalRequest.swift | 54 ----------- .../BTPayPalVaultRequest.swift | 90 ------------------- 3 files changed, 232 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift index d18224dad..7dfb56359 100644 --- a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift @@ -151,92 +151,4 @@ import BraintreeCore self.userAuthenticationEmail = userAuthenticationEmail self.userPhoneNumber = userPhoneNumber } - - // MARK: Internal Methods - - // swiftlint:disable cyclomatic_complexity - func parameters( - with configuration: BTConfiguration, - universalLink: URL? = nil, - isPayPalAppInstalled: Bool = false - ) -> [String: Any] { - var experienceProfile: [String: Any] = [:] - - experienceProfile["no_shipping"] = !isShippingAddressRequired - experienceProfile["brand_name"] = displayName != nil ? displayName : configuration.json?["paypal"]["displayName"].asString() - - if landingPageType?.stringValue != nil { - experienceProfile["landing_page_type"] = landingPageType?.stringValue - } - - if localeCode?.stringValue != nil { - experienceProfile["locale_code"] = localeCode?.stringValue - } - - experienceProfile["address_override"] = shippingAddressOverride != nil ? !isShippingAddressEditable : false - - var baseParameters: [String: Any] = [:] - - if merchantAccountID != nil { - baseParameters["merchant_account_id"] = merchantAccountID - } - - if riskCorrelationID != nil { - baseParameters["correlation_id"] = riskCorrelationID - } - - if let lineItems, !lineItems.isEmpty { - let lineItemsArray = lineItems.compactMap { $0.requestParameters() } - baseParameters["line_items"] = lineItemsArray - } - - if let userAuthenticationEmail, !userAuthenticationEmail.isEmpty { - baseParameters["payer_email"] = userAuthenticationEmail - } - - if let userPhoneNumberDict = try? userPhoneNumber?.toDictionary() { - baseParameters["phone_number"] = userPhoneNumberDict - } - - baseParameters["return_url"] = BTCoreConstants.callbackURLScheme + "://\(Self.callbackURLHostAndPath)success" - baseParameters["cancel_url"] = BTCoreConstants.callbackURLScheme + "://\(Self.callbackURLHostAndPath)cancel" - baseParameters["experience_profile"] = experienceProfile - - var checkoutParameters: [String: Any] = [ - "intent": intent.stringValue, - "amount": amount, - "offer_pay_later": offerPayLater - ] - - let currencyCode = currencyCode != nil ? currencyCode : configuration.json?["paypal"]["currencyIsoCode"].asString() - - if currencyCode != nil { - checkoutParameters["currency_iso_code"] = currencyCode - } - - if userAction != .none, var experienceProfile = baseParameters["experience_profile"] as? [String: Any] { - experienceProfile["user_action"] = userAction.stringValue - baseParameters["experience_profile"] = experienceProfile - } - - if requestBillingAgreement != false { - checkoutParameters["request_billing_agreement"] = requestBillingAgreement - - if billingAgreementDescription != nil { - checkoutParameters["billing_agreement_details"] = ["description": billingAgreementDescription] - } - } - - if shippingAddressOverride != nil { - checkoutParameters["line1"] = shippingAddressOverride?.streetAddress - checkoutParameters["line2"] = shippingAddressOverride?.extendedAddress - checkoutParameters["city"] = shippingAddressOverride?.locality - checkoutParameters["state"] = shippingAddressOverride?.region - checkoutParameters["postal_code"] = shippingAddressOverride?.postalCode - checkoutParameters["country_code"] = shippingAddressOverride?.countryCodeAlpha2 - checkoutParameters["recipient_name"] = shippingAddressOverride?.recipientName - } - - return baseParameters.merging(checkoutParameters) { $1 } - } } diff --git a/Sources/BraintreePayPal/BTPayPalRequest.swift b/Sources/BraintreePayPal/BTPayPalRequest.swift index be2b90dee..ace60f205 100644 --- a/Sources/BraintreePayPal/BTPayPalRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalRequest.swift @@ -68,11 +68,6 @@ protocol PayPalRequest { static var callbackURLHostAndPath: String { get } - func parameters( - with configuration: BTConfiguration, - universalLink: URL?, - isPayPalAppInstalled: Bool - ) -> [String: Any] } extension PayPalRequest { @@ -160,53 +155,4 @@ extension PayPalRequest { // MARK: Internal Methods - func parameters( - with configuration: BTConfiguration, - universalLink: URL? = nil, - isPayPalAppInstalled: Bool = false - ) -> [String: Any] { - var experienceProfile: [String: Any] = [:] - - experienceProfile["no_shipping"] = !isShippingAddressRequired - experienceProfile["brand_name"] = displayName != nil ? displayName : configuration.json?["paypal"]["displayName"].asString() - - if landingPageType?.stringValue != nil { - experienceProfile["landing_page_type"] = landingPageType?.stringValue - } - - if localeCode?.stringValue != nil { - experienceProfile["locale_code"] = localeCode?.stringValue - } - - experienceProfile["address_override"] = shippingAddressOverride != nil ? !isShippingAddressEditable : false - - var parameters: [String: Any] = [:] - - if merchantAccountID != nil { - parameters["merchant_account_id"] = merchantAccountID - } - - if riskCorrelationID != nil { - parameters["correlation_id"] = riskCorrelationID - } - - if let lineItems, !lineItems.isEmpty { - let lineItemsArray = lineItems.compactMap { $0.requestParameters() } - parameters["line_items"] = lineItemsArray - } - - if let userAuthenticationEmail, !userAuthenticationEmail.isEmpty { - parameters["payer_email"] = userAuthenticationEmail - } - - if let userPhoneNumberDict = try? userPhoneNumber?.toDictionary() { - parameters["phone_number"] = userPhoneNumberDict - } - - parameters["return_url"] = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)success" - parameters["cancel_url"] = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)cancel" - parameters["experience_profile"] = experienceProfile - - return parameters - } } diff --git a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift index 4bbaaea5f..e786e658b 100644 --- a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift @@ -105,94 +105,4 @@ import BraintreeCore self.userAuthenticationEmail = userAuthenticationEmail self.userPhoneNumber = userPhoneNumber } - - // swiftlint:disable cyclomatic_complexity function_body_length - func parameters( - with configuration: BTConfiguration, - universalLink: URL? = nil, - isPayPalAppInstalled: Bool = false - ) -> [String: Any] { - var experienceProfile: [String: Any] = [:] - - experienceProfile["no_shipping"] = !isShippingAddressRequired - experienceProfile["brand_name"] = displayName != nil ? displayName : configuration.json?["paypal"]["displayName"].asString() - - if landingPageType?.stringValue != nil { - experienceProfile["landing_page_type"] = landingPageType?.stringValue - } - - if localeCode?.stringValue != nil { - experienceProfile["locale_code"] = localeCode?.stringValue - } - - experienceProfile["address_override"] = shippingAddressOverride != nil ? !isShippingAddressEditable : false - - var baseParameters: [String: Any] = [:] - - if merchantAccountID != nil { - baseParameters["merchant_account_id"] = merchantAccountID - } - - if riskCorrelationID != nil { - baseParameters["correlation_id"] = riskCorrelationID - } - - if let lineItems, !lineItems.isEmpty { - let lineItemsArray = lineItems.compactMap { $0.requestParameters() } - baseParameters["line_items"] = lineItemsArray - } - - if let userAuthenticationEmail, !userAuthenticationEmail.isEmpty { - baseParameters["payer_email"] = userAuthenticationEmail - } - - if let userPhoneNumberDict = try? userPhoneNumber?.toDictionary() { - baseParameters["phone_number"] = userPhoneNumberDict - } - - baseParameters["return_url"] = BTCoreConstants.callbackURLScheme + "://\(Self.callbackURLHostAndPath)success" - baseParameters["cancel_url"] = BTCoreConstants.callbackURLScheme + "://\(Self.callbackURLHostAndPath)cancel" - baseParameters["experience_profile"] = experienceProfile - - if let universalLink, enablePayPalAppSwitch, isPayPalAppInstalled { - let appSwitchParameters: [String: Any] = [ - "launch_paypal_app": enablePayPalAppSwitch, - "os_version": UIDevice.current.systemVersion, - "os_type": UIDevice.current.systemName, - "merchant_app_return_url": universalLink.absoluteString - ] - - return baseParameters.merging(appSwitchParameters) { $1 } - } - - if let recurringBillingPlanType { - baseParameters["plan_type"] = recurringBillingPlanType.rawValue - } - - if let recurringBillingDetails { - baseParameters["plan_metadata"] = recurringBillingDetails.parameters() - } - - var vaultParameters: [String: Any] = ["offer_paypal_credit": offerCredit] - - if let billingAgreementDescription { - vaultParameters["description"] = billingAgreementDescription - } - - if let shippingAddressOverride { - let shippingAddressParameters: [String: String?] = [ - "line1": shippingAddressOverride.streetAddress, - "line2": shippingAddressOverride.extendedAddress, - "city": shippingAddressOverride.locality, - "state": shippingAddressOverride.region, - "postal_code": shippingAddressOverride.postalCode, - "country_code": shippingAddressOverride.countryCodeAlpha2, - "recipient_name": shippingAddressOverride.recipientName - ] - - vaultParameters["shipping_address"] = shippingAddressParameters - } - - return baseParameters.merging(vaultParameters) { $1 } - } } From b903ba271199bd740ee938df4b001eb6ca6bcb73 Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 13:57:19 -0600 Subject: [PATCH 32/49] Remove BTPayPalRequest class --- Sources/BraintreePayPal/BTPayPalRequest.swift | 90 ------------------- 1 file changed, 90 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalRequest.swift b/Sources/BraintreePayPal/BTPayPalRequest.swift index ace60f205..73421a5f0 100644 --- a/Sources/BraintreePayPal/BTPayPalRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalRequest.swift @@ -63,96 +63,6 @@ protocol PayPalRequest { var shippingAddressOverride: BTPostalAddress? { get } var userAuthenticationEmail: String? { get } var userPhoneNumber: BTPayPalPhoneNumber? { get } - - // MARK: - Static Properties - - static var callbackURLHostAndPath: String { get } - -} - -extension PayPalRequest { - - static var callbackURLHostAndPath: String { - "onetouch/v1/" - } } -/// Base options for PayPal Checkout and PayPal Vault flows. -/// - Note: Do not instantiate this class directly. Instead, use BTPayPalCheckoutRequest or BTPayPalVaultRequest. -@objcMembers open class BTPayPalRequest: NSObject, PayPalRequest { - - // MARK: - Internal Properties - - var hermesPath: String - var paymentType: BTPayPalPaymentType - var isShippingAddressRequired: Bool - var isShippingAddressEditable: Bool - var localeCode: BTPayPalLocaleCode? - var shippingAddressOverride: BTPostalAddress? - var landingPageType: BTPayPalRequestLandingPageType? - var displayName: String? - var merchantAccountID: String? - var lineItems: [BTPayPalLineItem]? - var billingAgreementDescription: String? - var riskCorrelationID: String? - var userAuthenticationEmail: String? - var userPhoneNumber: BTPayPalPhoneNumber? - - // MARK: - Initializer - - /// Initialize a new `BTPayPalRequest` - /// - Parameters: - /// - hermesPath: Required :nodoc: The hermes path or endpoint URI path. This property is not covered by semantic versioning. - /// - paymentType: Required :nodoc: The payment type, either checkout or vault. This property is not covered by semantic versioning. - /// - isShippingAddressRequired: Defaults to false. When set to true, the shipping address selector will be displayed. - /// - isShippingAddressEditable: Defaults to false. Set to true to enable user editing of the shipping address. - /// - Note: Only applies when `shippingAddressOverride` is set. - /// - localeCode: Optional: A locale code to use for the transaction. - /// - shippingAddressOverride: Optional: A valid shipping address to be displayed in the transaction flow. An error will occur if this address is not valid. - /// - landingPageType: Optional: Landing page type. Defaults to `.none`. - /// - Note: Setting the BTPayPalRequest's landingPageType changes the PayPal page to display when a user lands on the PayPal site to complete the payment. - /// `.login` specifies a PayPal account login page is used. - /// `.billing` specifies a non-PayPal account landing page is used. - /// - displayName: Optional: The merchant name displayed inside of the PayPal flow; defaults to the company name on your Braintree account - /// - merchantAccountID: Optional: A non-default merchant account to use for tokenization. - /// - lineItems: Optional: The line items for this transaction. It can include up to 249 line items. - /// - billingAgreementDescription: Optional: Display a custom description to the user for a billing agreement. For Checkout with Vault flows, you must also set - /// `requestBillingAgreement` to `true` on your `BTPayPalCheckoutRequest`. - /// - riskCorrelationID: Optional: A risk correlation ID created with Set Transaction Context on your server. - /// - userAuthenticationEmail: Optional: User email to initiate a quicker authentication flow in cases where the user has a PayPal Account with the same email. - /// - userPhoneNumber: Optional: A user's phone number to initiate a quicker authentication flow in the scenario where the user has a PayPal account identified with the same phone number. - init( - hermesPath: String, - paymentType: BTPayPalPaymentType, - isShippingAddressRequired: Bool = false, - isShippingAddressEditable: Bool = false, - localeCode: BTPayPalLocaleCode = .none, - shippingAddressOverride: BTPostalAddress? = nil, - landingPageType: BTPayPalRequestLandingPageType = .none, - displayName: String? = nil, - merchantAccountID: String? = nil, - lineItems: [BTPayPalLineItem]? = nil, - billingAgreementDescription: String? = nil, - riskCorrelationID: String? = nil, - userAuthenticationEmail: String? = nil, - userPhoneNumber: BTPayPalPhoneNumber? = nil - ) { - self.hermesPath = hermesPath - self.paymentType = paymentType - self.isShippingAddressRequired = isShippingAddressRequired - self.isShippingAddressEditable = isShippingAddressEditable - self.localeCode = localeCode - self.shippingAddressOverride = shippingAddressOverride - self.landingPageType = landingPageType - self.displayName = displayName - self.merchantAccountID = merchantAccountID - self.lineItems = lineItems - self.billingAgreementDescription = billingAgreementDescription - self.riskCorrelationID = riskCorrelationID - self.userAuthenticationEmail = userAuthenticationEmail - self.userPhoneNumber = userPhoneNumber - } - - // MARK: Internal Methods - } From 44d61f40f85e491952b9a68f0b616cee205245c7 Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 13:57:46 -0600 Subject: [PATCH 33/49] Add PayPalConstanst enum --- Sources/BraintreePayPal/BTPayPalRequest.swift | 2 ++ Sources/BraintreePayPal/BTPayPalReturnURL.swift | 2 +- Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift | 4 ++-- Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalRequest.swift b/Sources/BraintreePayPal/BTPayPalRequest.swift index 73421a5f0..241fdd9df 100644 --- a/Sources/BraintreePayPal/BTPayPalRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalRequest.swift @@ -65,4 +65,6 @@ protocol PayPalRequest { var userPhoneNumber: BTPayPalPhoneNumber? { get } } +enum PayPalRequestConstants { + static let callbackURLHostAndPath = "onetouch/v1/" } diff --git a/Sources/BraintreePayPal/BTPayPalReturnURL.swift b/Sources/BraintreePayPal/BTPayPalReturnURL.swift index 76f97508f..58b641f50 100644 --- a/Sources/BraintreePayPal/BTPayPalReturnURL.swift +++ b/Sources/BraintreePayPal/BTPayPalReturnURL.swift @@ -59,7 +59,7 @@ struct BTPayPalReturnURL { /// If we are using the deeplink/ASWeb based PayPal flow we want to check that the host and path matches /// the static callbackURLHostAndPath. For the universal link flow we do not care about this check. - if hostAndPath != BTPayPalRequest.callbackURLHostAndPath && linkType == .deeplink { + if hostAndPath != PayPalRequestConstants.callbackURLHostAndPath && linkType == .deeplink { return false } diff --git a/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift index 4bd815722..e17f0bd75 100644 --- a/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift @@ -82,8 +82,8 @@ struct PayPalCheckoutPOSTBody: Encodable { } self.userPhoneNumber = payPalRequest.userPhoneNumber - self.returnURL = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)success" - self.cancelURL = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)cancel" + self.returnURL = BTCoreConstants.callbackURLScheme + "://\(PayPalRequestConstants.callbackURLHostAndPath)success" + self.cancelURL = BTCoreConstants.callbackURLScheme + "://\(PayPalRequestConstants.callbackURLHostAndPath)cancel" self.experienceProfile = PayPalExperienceProfile(payPalRequest: payPalRequest, configuration: configuration) } diff --git a/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift index 6022454f0..77d04443c 100644 --- a/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift @@ -54,8 +54,8 @@ struct PayPalVaultPOSTBody: Encodable { } self.userPhoneNumber = payPalRequest.userPhoneNumber - self.returnURL = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)success" - self.cancelURL = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)cancel" + self.returnURL = BTCoreConstants.callbackURLScheme + "://\(PayPalRequestConstants.callbackURLHostAndPath)success" + self.cancelURL = BTCoreConstants.callbackURLScheme + "://\(PayPalRequestConstants.callbackURLHostAndPath)cancel" self.experienceProfile = PayPalExperienceProfile(payPalRequest: payPalRequest, configuration: configuration) if let universalLink, payPalRequest.enablePayPalAppSwitch, isPayPalAppInstalled { From 7f0a207b0c1c78ba10ec142ace0ee6d29397e05a Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 16:27:51 -0600 Subject: [PATCH 34/49] Remove unnecessary error --- Sources/BraintreePayPal/BTPayPalError.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalError.swift b/Sources/BraintreePayPal/BTPayPalError.swift index a9add815b..3a947c3dd 100644 --- a/Sources/BraintreePayPal/BTPayPalError.swift +++ b/Sources/BraintreePayPal/BTPayPalError.swift @@ -44,9 +44,6 @@ public enum BTPayPalError: Error, CustomNSError, LocalizedError, Equatable { /// 13. Missing PayPal Request case missingPayPalRequest - - /// 14. Unable to create Encodable - case failedToCreateEncodable public static var errorDomain: String { "com.braintreepayments.BTPayPalErrorDomain" @@ -82,8 +79,6 @@ public enum BTPayPalError: Error, CustomNSError, LocalizedError, Equatable { return 12 case .missingPayPalRequest: return 13 - case .failedToCreateEncodable: - return 14 } } @@ -119,8 +114,6 @@ public enum BTPayPalError: Error, CustomNSError, LocalizedError, Equatable { return "Missing BA Token for PayPal App Switch." case .missingPayPalRequest: return "The PayPal Request was missing or invalid." - case .failedToCreateEncodable: - return "The request was not a Checkout or Vault request." } } From 39ca19f40a9a5b426f9b5f56f29fb15a0f46ae6b Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 16:28:37 -0600 Subject: [PATCH 35/49] Move encoded method --- .../BTPayPalCheckoutRequest.swift | 8 ++++++ Sources/BraintreePayPal/BTPayPalClient.swift | 26 ++++--------------- Sources/BraintreePayPal/BTPayPalRequest.swift | 2 ++ .../BTPayPalVaultRequest.swift | 13 ++++++++++ 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift index 7dfb56359..c8cc7044c 100644 --- a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift @@ -151,4 +151,12 @@ import BraintreeCore self.userAuthenticationEmail = userAuthenticationEmail self.userPhoneNumber = userPhoneNumber } + + func encodedPostBodyWith( + configuration: BTConfiguration, + isPayPalAppInstalled: Bool = false, + universalLink: URL? = nil + ) -> Encodable { + PayPalCheckoutPOSTBody(payPalRequest: self, configuration: configuration) + } } diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index 83344ef1e..6c49ba7bb 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -357,10 +357,11 @@ import BraintreeDataCollector self.payPalRequest = request - guard let parameters = self.encodedPostBody(from: request, configuration: configuration) else { - self.notifyFailure(with: BTPayPalError.failedToCreateEncodable, completion: completion) - return - } + let parameters = request.encodedPostBodyWith( + configuration: configuration, + isPayPalAppInstalled: self.application.isPayPalAppInstalled(), + universalLink: self.universalLink + ) self.apiClient.post( request.hermesPath, @@ -403,23 +404,6 @@ import BraintreeDataCollector } } } - - private func encodedPostBody(from payPalRequest: PayPalRequest, configuration: BTConfiguration) -> Encodable? { - switch payPalRequest.paymentType { - case .checkout: - guard let checkoutRequest = payPalRequest as? BTPayPalCheckoutRequest else { return nil } - return PayPalCheckoutPOSTBody(payPalRequest: checkoutRequest, configuration: configuration) - - case .vault: - guard let vaultRequest = payPalRequest as? BTPayPalVaultRequest else { return nil } - return PayPalVaultPOSTBody( - payPalRequest: vaultRequest, - configuration: configuration, - isPayPalAppInstalled: application.isPayPalAppInstalled(), - universalLink: universalLink - ) - } - } private func launchPayPalApp( with payPalAppRedirectURL: URL, diff --git a/Sources/BraintreePayPal/BTPayPalRequest.swift b/Sources/BraintreePayPal/BTPayPalRequest.swift index 241fdd9df..4cd5e0986 100644 --- a/Sources/BraintreePayPal/BTPayPalRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalRequest.swift @@ -63,6 +63,8 @@ protocol PayPalRequest { var shippingAddressOverride: BTPostalAddress? { get } var userAuthenticationEmail: String? { get } var userPhoneNumber: BTPayPalPhoneNumber? { get } + + func encodedPostBodyWith(configuration: BTConfiguration, isPayPalAppInstalled: Bool, universalLink: URL?) -> Encodable } enum PayPalRequestConstants { diff --git a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift index e786e658b..663c8e3be 100644 --- a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift @@ -105,4 +105,17 @@ import BraintreeCore self.userAuthenticationEmail = userAuthenticationEmail self.userPhoneNumber = userPhoneNumber } + + func encodedPostBodyWith( + configuration: BTConfiguration, + isPayPalAppInstalled: Bool = false, + universalLink: URL? = nil + ) -> Encodable { + PayPalVaultPOSTBody( + payPalRequest: self, + configuration: configuration, + isPayPalAppInstalled: isPayPalAppInstalled, + universalLink: universalLink + ) + } } From 169ff3abc25945ce7fcf6ceb3b5ed16cd88ed70a Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 9 Jan 2025 16:29:02 -0600 Subject: [PATCH 36/49] Update UTs --- .../BTPayPalCheckoutRequest_Tests.swift | 26 +++++++++++++++---- .../BTPayPalVaultRequest_Tests.swift | 18 ++++++++++--- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/UnitTests/BraintreePayPalTests/BTPayPalCheckoutRequest_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalCheckoutRequest_Tests.swift index e082e120f..ce01d31ba 100644 --- a/UnitTests/BraintreePayPalTests/BTPayPalCheckoutRequest_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalCheckoutRequest_Tests.swift @@ -100,7 +100,10 @@ class BTPayPalCheckoutRequest_Tests: XCTestCase { request.shippingAddressOverride = shippingAddress request.isShippingAddressEditable = true - let parameters = request.parameters(with: configuration) + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration).toDictionary() else { + XCTFail() + return + } XCTAssertEqual(parameters["intent"] as? String, "sale") XCTAssertEqual(parameters["amount"] as? String, "1") @@ -137,7 +140,10 @@ class BTPayPalCheckoutRequest_Tests: XCTestCase { func testParametersWithConfiguration_returnsMinimumParams() { let request = BTPayPalCheckoutRequest(amount: "1") - let parameters = request.parameters(with: configuration) + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration).toDictionary() else { + XCTFail() + return + } XCTAssertEqual(parameters["intent"] as? String, "authorize") XCTAssertEqual(parameters["amount"] as? String, "1") @@ -153,7 +159,11 @@ class BTPayPalCheckoutRequest_Tests: XCTestCase { configuration = BTConfiguration(json: json) let request = BTPayPalCheckoutRequest(amount: "1") - let parameters = request.parameters(with: configuration) + + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration).toDictionary() else { + XCTFail() + return + } XCTAssertEqual(parameters["currency_iso_code"] as? String, "currency-code") } @@ -162,7 +172,10 @@ class BTPayPalCheckoutRequest_Tests: XCTestCase { let request = BTPayPalCheckoutRequest(amount: "1") request.billingAgreementDescription = "description" - let parameters = request.parameters(with: configuration) + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration).toDictionary() else { + XCTFail() + return + } XCTAssertNil(parameters["request_billing_agreement"]) XCTAssertNil(parameters["billing_agreement_details"]) @@ -172,7 +185,10 @@ class BTPayPalCheckoutRequest_Tests: XCTestCase { let request = BTPayPalCheckoutRequest(amount: "1") request.userAuthenticationEmail = "" - let parameters = request.parameters(with: configuration) + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration).toDictionary() else { + XCTFail() + return + } XCTAssertNil(parameters["payer_email"]) } diff --git a/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift index 645bbbd4d..13b130db0 100644 --- a/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift @@ -51,7 +51,10 @@ class BTPayPalVaultRequest_Tests: XCTestCase { request.userAuthenticationEmail = "fake@email.com" request.userPhoneNumber = BTPayPalPhoneNumber(countryCode: "1", nationalNumber: "4087463271") - let parameters = request.parameters(with: configuration) + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration).toDictionary() else { + XCTFail() + return + } XCTAssertEqual(parameters["description"] as? String, "desc") XCTAssertEqual(parameters["offer_paypal_credit"] as? Bool, true) @@ -84,8 +87,11 @@ class BTPayPalVaultRequest_Tests: XCTestCase { userAuthenticationEmail: "sally@gmail.com", enablePayPalAppSwitch: true ) - - let parameters = request.parameters(with: configuration, universalLink: URL(string: "some-url")!, isPayPalAppInstalled: true) + + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration, isPayPalAppInstalled: true, universalLink: URL(string: "some-url")!).toDictionary() else { + XCTFail() + return + } XCTAssertEqual(parameters["launch_paypal_app"] as? Bool, true) XCTAssertTrue((parameters["os_version"] as! String).matches("\\d+\\.\\d+")) @@ -125,7 +131,11 @@ class BTPayPalVaultRequest_Tests: XCTestCase { let request = BTPayPalVaultRequest(recurringBillingDetails: recurringBillingDetails, recurringBillingPlanType: .subscription) - let parameters = request.parameters(with: configuration, universalLink: URL(string: "some-url")!) + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration, universalLink: URL(string: "some-url")!).toDictionary() else { + XCTFail() + return + } + XCTAssertEqual(parameters["plan_type"] as! String, "SUBSCRIPTION") guard let planMetadata = parameters["plan_metadata"] as? [String: Any] else { XCTFail(); return } From d6284ec089e559b69cb0204501a79e2b608d330d Mon Sep 17 00:00:00 2001 From: richherrera Date: Fri, 10 Jan 2025 10:22:42 -0600 Subject: [PATCH 37/49] Move BTPayPalRequest UTs to BTPayPalCheckout and BTPayPalVault requests --- .../BTPayPalCheckoutRequest_Tests.swift | 88 +++++++++++++++ .../BTPayPalVaultRequest_Tests.swift | 100 ++++++++++++++++++ 2 files changed, 188 insertions(+) diff --git a/UnitTests/BraintreePayPalTests/BTPayPalCheckoutRequest_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalCheckoutRequest_Tests.swift index ce01d31ba..6110ce692 100644 --- a/UnitTests/BraintreePayPalTests/BTPayPalCheckoutRequest_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalCheckoutRequest_Tests.swift @@ -192,4 +192,92 @@ class BTPayPalCheckoutRequest_Tests: XCTestCase { XCTAssertNil(parameters["payer_email"]) } + + func testParametersWithConfiguration_returnsAllBaseParams() { + let request = BTPayPalCheckoutRequest(amount: "1") + request.isShippingAddressRequired = true + request.displayName = "Display Name" + request.landingPageType = .login + request.localeCode = .en_US + request.riskCorrelationID = "123-correlation-id" + request.merchantAccountID = "merchant-account-id" + request.isShippingAddressEditable = true + + let lineItem = BTPayPalLineItem(quantity: "1", unitAmount: "1", name: "item", kind: .credit) + lineItem.imageURL = URL(string: "http://example/image.jpg") + lineItem.upcCode = "upc-code" + lineItem.upcType = .UPC_A + request.lineItems = [lineItem] + + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration).toDictionary() else { + XCTFail() + return + } + + guard let experienceProfile = parameters["experience_profile"] as? [String : Any] else { XCTFail(); return } + + XCTAssertEqual(experienceProfile["no_shipping"] as? Bool, false) + XCTAssertEqual(experienceProfile["brand_name"] as? String, "Display Name") + XCTAssertEqual(experienceProfile["landing_page_type"] as? String, "login") + XCTAssertEqual(experienceProfile["locale_code"] as? String, "en_US") + XCTAssertEqual(parameters["merchant_account_id"] as? String, "merchant-account-id") + XCTAssertEqual(parameters["correlation_id"] as? String, "123-correlation-id") + XCTAssertEqual(experienceProfile["address_override"] as? Bool, false) + XCTAssertEqual(parameters["line_items"] as? [[String : String]], [["quantity" : "1", + "unit_amount": "1", + "name": "item", + "kind": "credit", + "upc_code": "upc-code", + "upc_type": "UPC-A", + "image_url": "http://example/image.jpg"]]) + + XCTAssertEqual(parameters["return_url"] as? String, "sdk.ios.braintree://onetouch/v1/success") + XCTAssertEqual(parameters["cancel_url"] as? String, "sdk.ios.braintree://onetouch/v1/cancel") + } + + func testParametersWithConfiguration_whenShippingAddressIsRequiredNotSet_returnsNoShippingTrue() { + let request = BTPayPalCheckoutRequest(amount: "1") + // no_shipping = true should be the default. + + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration).toDictionary() else { + XCTFail() + return + } + + guard let experienceProfile = parameters["experience_profile"] as? [String : Any] else { XCTFail(); return } + + XCTAssertEqual(experienceProfile["no_shipping"] as? Bool, true) + } + + func testParametersWithConfiguration_whenShippingAddressIsRequiredIsTrue_returnsNoShippingFalse() { + let request = BTPayPalCheckoutRequest(amount: "1") + request.isShippingAddressRequired = true + + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration).toDictionary() else { + XCTFail() + return + } + + guard let experienceProfile = parameters["experience_profile"] as? [String:Any] else { XCTFail(); return } + XCTAssertEqual(experienceProfile["no_shipping"] as? Bool, false) + } + + // MARK: - landingPageTypeAsString + + func testLandingPageTypeAsString_whenLandingPageTypeIsNotSpecified_returnNil() { + let request = BTPayPalCheckoutRequest(amount: "1") + XCTAssertNil(request.landingPageType?.stringValue) + } + + func testLandingPageTypeAsString_whenLandingPageTypeIsBilling_returnsBilling() { + let request = BTPayPalCheckoutRequest(amount: "1") + request.landingPageType = .billing + XCTAssertEqual(request.landingPageType?.stringValue, "billing") + } + + func testLandingPageTypeAsString_whenLandingPageTypeIsLogin_returnsLogin() { + let request = BTPayPalCheckoutRequest(amount: "1") + request.landingPageType = .login + XCTAssertEqual(request.landingPageType?.stringValue, "login") + } } diff --git a/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift index 13b130db0..8b3e62c6b 100644 --- a/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift @@ -162,4 +162,104 @@ class BTPayPalVaultRequest_Tests: XCTestCase { XCTAssertEqual(pricingScheme["price"], "test-price") XCTAssertEqual(pricingScheme["reload_threshold_amount"], "test-threshold") } + + func testParametersWithConfiguration_returnsAllBaseParams() { + let request = BTPayPalVaultRequest() + request.isShippingAddressRequired = true + request.displayName = "Display Name" + request.landingPageType = .login + request.localeCode = .en_US + request.riskCorrelationID = "123-correlation-id" + request.merchantAccountID = "merchant-account-id" + request.isShippingAddressEditable = true + + let lineItem = BTPayPalLineItem(quantity: "1", unitAmount: "1", name: "item", kind: .credit) + lineItem.imageURL = URL(string: "http://example/image.jpg") + lineItem.upcCode = "upc-code" + lineItem.upcType = .UPC_A + request.lineItems = [lineItem] + + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration).toDictionary() else { + XCTFail() + return + } + + guard let experienceProfile = parameters["experience_profile"] as? [String : Any] else { XCTFail(); return } + + XCTAssertEqual(experienceProfile["no_shipping"] as? Bool, false) + XCTAssertEqual(experienceProfile["brand_name"] as? String, "Display Name") + XCTAssertEqual(experienceProfile["landing_page_type"] as? String, "login") + XCTAssertEqual(experienceProfile["locale_code"] as? String, "en_US") + XCTAssertEqual(parameters["merchant_account_id"] as? String, "merchant-account-id") + XCTAssertEqual(parameters["correlation_id"] as? String, "123-correlation-id") + XCTAssertEqual(experienceProfile["address_override"] as? Bool, false) + XCTAssertEqual(parameters["line_items"] as? [[String : String]], [["quantity" : "1", + "unit_amount": "1", + "name": "item", + "kind": "credit", + "upc_code": "upc-code", + "upc_type": "UPC-A", + "image_url": "http://example/image.jpg"]]) + + XCTAssertEqual(parameters["return_url"] as? String, "sdk.ios.braintree://onetouch/v1/success") + XCTAssertEqual(parameters["cancel_url"] as? String, "sdk.ios.braintree://onetouch/v1/cancel") + } + + func testParametersWithConfiguration_whenShippingAddressIsRequiredNotSet_returnsNoShippingTrue() { + let request = BTPayPalVaultRequest() + // no_shipping = true should be the default. + + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration).toDictionary() else { + XCTFail() + return + } + + guard let experienceProfile = parameters["experience_profile"] as? [String : Any] else { XCTFail(); return } + + XCTAssertEqual(experienceProfile["no_shipping"] as? Bool, true) + } + + func testParametersWithConfiguration_whenShippingAddressIsRequiredIsTrue_returnsNoShippingFalse() { + let request = BTPayPalVaultRequest() + request.isShippingAddressRequired = true + + guard let parameters = try? request.encodedPostBodyWith(configuration: configuration).toDictionary() else { + XCTFail() + return + } + + guard let experienceProfile = parameters["experience_profile"] as? [String:Any] else { XCTFail(); return } + XCTAssertEqual(experienceProfile["no_shipping"] as? Bool, false) + } + + // MARK: - landingPageTypeAsString + + func testLandingPageTypeAsString_whenLandingPageTypeIsNotSpecified_returnNil() { + let request = BTPayPalVaultRequest() + XCTAssertNil(request.landingPageType?.stringValue) + } + + func testLandingPageTypeAsString_whenLandingPageTypeIsBilling_returnsBilling() { + let request = BTPayPalVaultRequest() + request.landingPageType = .billing + XCTAssertEqual(request.landingPageType?.stringValue, "billing") + } + + func testLandingPageTypeAsString_whenLandingPageTypeIsLogin_returnsLogin() { + let request = BTPayPalVaultRequest() + request.landingPageType = .login + XCTAssertEqual(request.landingPageType?.stringValue, "login") + } + + // MARK: - enablePayPalAppSwitch + + func testEnablePayPalAppSwitch_whenInitialized_setsAllRequiredValues() { + let request = BTPayPalVaultRequest( + userAuthenticationEmail: "fake@gmail.com", + enablePayPalAppSwitch: true + ) + + XCTAssertEqual(request.userAuthenticationEmail, "fake@gmail.com") + XCTAssertTrue(request.enablePayPalAppSwitch) + } } From 7601a154c6edd5db1918673a5a7c4faff74eceb5 Mon Sep 17 00:00:00 2001 From: richherrera Date: Fri, 10 Jan 2025 10:23:01 -0600 Subject: [PATCH 38/49] Delete BTPayPalRequest UTs --- Braintree.xcodeproj/project.pbxproj | 4 - .../BTPayPalRequest_Tests.swift | 108 ------------------ 2 files changed, 112 deletions(-) delete mode 100644 UnitTests/BraintreePayPalTests/BTPayPalRequest_Tests.swift diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index 2231e8ac8..868e81537 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -34,7 +34,6 @@ 427F32E025D1D62D00435294 /* BTPayPalClient_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427F32DF25D1D62D00435294 /* BTPayPalClient_Tests.swift */; }; 428F48E42624C9B700EC8DB4 /* BTVenmoAppSwitchRedirectURL_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 428F48E32624C9B700EC8DB4 /* BTVenmoAppSwitchRedirectURL_Tests.swift */; }; 428F976626727333001042E1 /* BTMockOpenURLContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 428F976526727333001042E1 /* BTMockOpenURLContext.m */; }; - 42FC218B25CDE0290047C49A /* BTPayPalRequest_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42FC218A25CDE0290047C49A /* BTPayPalRequest_Tests.swift */; }; 42FC237125CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42FC237025CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift */; }; 45227FC52C330FDE00A15018 /* MockURLSessionTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45227FC32C330FDE00A15018 /* MockURLSessionTask.swift */; }; 45227FC72C33104100A15018 /* MockBTHTTPNetworkTiming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45227FC62C33104100A15018 /* MockBTHTTPNetworkTiming.swift */; }; @@ -719,7 +718,6 @@ 42F2FF2E2333D2B20083CA10 /* BTAuthenticationInsight_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAuthenticationInsight_Tests.swift; sourceTree = ""; }; 42F75E5824D46DA2007DC5E7 /* BTThreeDSecureLookup_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureLookup_Tests.swift; sourceTree = ""; }; 42F75E5A24D48138007DC5E7 /* BTThreeDSecureClient_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureClient_Tests.swift; sourceTree = ""; }; - 42FC218A25CDE0290047C49A /* BTPayPalRequest_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalRequest_Tests.swift; sourceTree = ""; }; 42FC237025CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalCheckoutRequest_Tests.swift; sourceTree = ""; }; 45227FC32C330FDE00A15018 /* MockURLSessionTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSessionTask.swift; sourceTree = ""; }; 45227FC62C33104100A15018 /* MockBTHTTPNetworkTiming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBTHTTPNetworkTiming.swift; sourceTree = ""; }; @@ -1922,7 +1920,6 @@ 42FC237025CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift */, 427F32DF25D1D62D00435294 /* BTPayPalClient_Tests.swift */, BECB10C52B5999EE008D398E /* BTPayPalLineItem_Tests.swift */, - 42FC218A25CDE0290047C49A /* BTPayPalRequest_Tests.swift */, BEBA590E2BB1B5B9005FA8A2 /* BTPayPalReturnURL_Tests.swift */, 427F328F25D1A7B900435294 /* BTPayPalVaultRequest_Tests.swift */, A9E5C1E424FD665D00EE691F /* Info.plist */, @@ -3441,7 +3438,6 @@ buildActionMask = 2147483647; files = ( 427F32E025D1D62D00435294 /* BTPayPalClient_Tests.swift in Sources */, - 42FC218B25CDE0290047C49A /* BTPayPalRequest_Tests.swift in Sources */, 42FC237125CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift in Sources */, BEDEAF112AC1D049004EA970 /* BTPayPalAccountNonce_Tests.swift in Sources */, 427F329025D1A7B900435294 /* BTPayPalVaultRequest_Tests.swift in Sources */, diff --git a/UnitTests/BraintreePayPalTests/BTPayPalRequest_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalRequest_Tests.swift deleted file mode 100644 index b17de93df..000000000 --- a/UnitTests/BraintreePayPalTests/BTPayPalRequest_Tests.swift +++ /dev/null @@ -1,108 +0,0 @@ -import XCTest -@testable import BraintreeCore -@testable import BraintreePayPal - -class BTPayPalRequest_Tests: XCTestCase { - - private var configuration: BTConfiguration! - - override func setUp() { - super.setUp() - let json = BTJSON(value: [ - "paypalEnabled": true, - "paypal": ["environment": "offline"] - ] as [String: Any]) - - configuration = BTConfiguration(json: json) - } - - // MARK: - landingPageTypeAsString - - func testLandingPageTypeAsString_whenLandingPageTypeIsNotSpecified_returnNil() { - let request = BTPayPalRequest(hermesPath: "hermes-test-path", paymentType: .checkout) - XCTAssertNil(request.landingPageType?.stringValue) - } - - func testLandingPageTypeAsString_whenLandingPageTypeIsBilling_returnsBilling() { - let request = BTPayPalRequest(hermesPath: "hermes-test-path", paymentType: .checkout) - request.landingPageType = .billing - XCTAssertEqual(request.landingPageType?.stringValue, "billing") - } - - func testLandingPageTypeAsString_whenLandingPageTypeIsLogin_returnsLogin() { - let request = BTPayPalRequest(hermesPath: "hermes-test-path", paymentType: .checkout) - request.landingPageType = .login - XCTAssertEqual(request.landingPageType?.stringValue, "login") - } - - // MARK: - parametersWithConfiguration - - func testParametersWithConfiguration_returnsAllParams() { - let request = BTPayPalRequest(hermesPath: "hermes-test-path", paymentType: .checkout) - request.isShippingAddressRequired = true - request.displayName = "Display Name" - request.landingPageType = .login - request.localeCode = .en_US - request.riskCorrelationID = "123-correlation-id" - request.merchantAccountID = "merchant-account-id" - request.isShippingAddressEditable = true - - let lineItem = BTPayPalLineItem(quantity: "1", unitAmount: "1", name: "item", kind: .credit) - lineItem.imageURL = URL(string: "http://example/image.jpg") - lineItem.upcCode = "upc-code" - lineItem.upcType = .UPC_A - request.lineItems = [lineItem] - - let parameters = request.parameters(with: configuration) - guard let experienceProfile = parameters["experience_profile"] as? [String : Any] else { XCTFail(); return } - - XCTAssertEqual(experienceProfile["no_shipping"] as? Bool, false) - XCTAssertEqual(experienceProfile["brand_name"] as? String, "Display Name") - XCTAssertEqual(experienceProfile["landing_page_type"] as? String, "login") - XCTAssertEqual(experienceProfile["locale_code"] as? String, "en_US") - XCTAssertEqual(parameters["merchant_account_id"] as? String, "merchant-account-id") - XCTAssertEqual(parameters["correlation_id"] as? String, "123-correlation-id") - XCTAssertEqual(experienceProfile["address_override"] as? Bool, false) - XCTAssertEqual(parameters["line_items"] as? [[String : String]], [["quantity" : "1", - "unit_amount": "1", - "name": "item", - "kind": "credit", - "upc_code": "upc-code", - "upc_type": "UPC-A", - "image_url": "http://example/image.jpg"]]) - - XCTAssertEqual(parameters["return_url"] as? String, "sdk.ios.braintree://onetouch/v1/success") - XCTAssertEqual(parameters["cancel_url"] as? String, "sdk.ios.braintree://onetouch/v1/cancel") - } - - func testParametersWithConfiguration_whenShippingAddressIsRequiredNotSet_returnsNoShippingTrue() { - let request = BTPayPalRequest(hermesPath: "hermes-test-path", paymentType: .checkout) - // no_shipping = true should be the default. - - let parameters = request.parameters(with: configuration) - guard let experienceProfile = parameters["experience_profile"] as? [String : Any] else { XCTFail(); return } - - XCTAssertEqual(experienceProfile["no_shipping"] as? Bool, true) - } - - func testParametersWithConfiguration_whenShippingAddressIsRequiredIsTrue_returnsNoShippingFalse() { - let request = BTPayPalRequest(hermesPath: "hermes-test-path", paymentType: .checkout) - request.isShippingAddressRequired = true - - let parameters = request.parameters(with: configuration) - guard let experienceProfile = parameters["experience_profile"] as? [String:Any] else { XCTFail(); return } - XCTAssertEqual(experienceProfile["no_shipping"] as? Bool, false) - } - - // MARK: - enablePayPalAppSwitch - - func testEnablePayPalAppSwitch_whenInitialized_setsAllRequiredValues() { - let request = BTPayPalVaultRequest( - userAuthenticationEmail: "fake@gmail.com", - enablePayPalAppSwitch: true - ) - - XCTAssertEqual(request.userAuthenticationEmail, "fake@gmail.com") - XCTAssertTrue(request.enablePayPalAppSwitch) - } -} From e446f03b33f01e67c7c74af9f64ba0879a245f58 Mon Sep 17 00:00:00 2001 From: richherrera Date: Fri, 10 Jan 2025 15:15:14 -0600 Subject: [PATCH 39/49] Use if let syntax --- .../BTPayPalCheckoutRequest.swift | 32 +++++++++---------- .../BTPayPalVaultRequest.swift | 12 +++---- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift index d18224dad..2b57238d7 100644 --- a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift @@ -165,23 +165,23 @@ import BraintreeCore experienceProfile["no_shipping"] = !isShippingAddressRequired experienceProfile["brand_name"] = displayName != nil ? displayName : configuration.json?["paypal"]["displayName"].asString() - if landingPageType?.stringValue != nil { - experienceProfile["landing_page_type"] = landingPageType?.stringValue + if let landingPageType = landingPageType?.stringValue { + experienceProfile["landing_page_type"] = landingPageType } - if localeCode?.stringValue != nil { - experienceProfile["locale_code"] = localeCode?.stringValue + if let localeCode = localeCode?.stringValue { + experienceProfile["locale_code"] = localeCode } experienceProfile["address_override"] = shippingAddressOverride != nil ? !isShippingAddressEditable : false var baseParameters: [String: Any] = [:] - if merchantAccountID != nil { + if let merchantAccountID { baseParameters["merchant_account_id"] = merchantAccountID } - if riskCorrelationID != nil { + if let riskCorrelationID { baseParameters["correlation_id"] = riskCorrelationID } @@ -210,7 +210,7 @@ import BraintreeCore let currencyCode = currencyCode != nil ? currencyCode : configuration.json?["paypal"]["currencyIsoCode"].asString() - if currencyCode != nil { + if let currencyCode { checkoutParameters["currency_iso_code"] = currencyCode } @@ -219,7 +219,7 @@ import BraintreeCore baseParameters["experience_profile"] = experienceProfile } - if requestBillingAgreement != false { + if requestBillingAgreement { checkoutParameters["request_billing_agreement"] = requestBillingAgreement if billingAgreementDescription != nil { @@ -227,14 +227,14 @@ import BraintreeCore } } - if shippingAddressOverride != nil { - checkoutParameters["line1"] = shippingAddressOverride?.streetAddress - checkoutParameters["line2"] = shippingAddressOverride?.extendedAddress - checkoutParameters["city"] = shippingAddressOverride?.locality - checkoutParameters["state"] = shippingAddressOverride?.region - checkoutParameters["postal_code"] = shippingAddressOverride?.postalCode - checkoutParameters["country_code"] = shippingAddressOverride?.countryCodeAlpha2 - checkoutParameters["recipient_name"] = shippingAddressOverride?.recipientName + if let shippingAddressOverride { + checkoutParameters["line1"] = shippingAddressOverride.streetAddress + checkoutParameters["line2"] = shippingAddressOverride.extendedAddress + checkoutParameters["city"] = shippingAddressOverride.locality + checkoutParameters["state"] = shippingAddressOverride.region + checkoutParameters["postal_code"] = shippingAddressOverride.postalCode + checkoutParameters["country_code"] = shippingAddressOverride.countryCodeAlpha2 + checkoutParameters["recipient_name"] = shippingAddressOverride.recipientName } return baseParameters.merging(checkoutParameters) { $1 } diff --git a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift index 4bbaaea5f..00528f170 100644 --- a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift @@ -117,23 +117,23 @@ import BraintreeCore experienceProfile["no_shipping"] = !isShippingAddressRequired experienceProfile["brand_name"] = displayName != nil ? displayName : configuration.json?["paypal"]["displayName"].asString() - if landingPageType?.stringValue != nil { - experienceProfile["landing_page_type"] = landingPageType?.stringValue + if let landingPageType = landingPageType?.stringValue { + experienceProfile["landing_page_type"] = landingPageType } - if localeCode?.stringValue != nil { - experienceProfile["locale_code"] = localeCode?.stringValue + if let localeCode = localeCode?.stringValue { + experienceProfile["locale_code"] = localeCode } experienceProfile["address_override"] = shippingAddressOverride != nil ? !isShippingAddressEditable : false var baseParameters: [String: Any] = [:] - if merchantAccountID != nil { + if let merchantAccountID { baseParameters["merchant_account_id"] = merchantAccountID } - if riskCorrelationID != nil { + if let riskCorrelationID { baseParameters["correlation_id"] = riskCorrelationID } From ac6f13e132f6bd7d19f89b307d209082a1045d82 Mon Sep 17 00:00:00 2001 From: richherrera Date: Fri, 10 Jan 2025 15:16:50 -0600 Subject: [PATCH 40/49] Address PR comments --- Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift | 6 ++---- Sources/BraintreePayPal/BTPayPalVaultRequest.swift | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift index 2b57238d7..efc9a7387 100644 --- a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift @@ -60,8 +60,8 @@ import BraintreeCore // MARK: - Internal Properties - let hermesPath: String - let paymentType: BTPayPalPaymentType + let hermesPath = "v1/paypal_hermes/create_payment_resource" + let paymentType: BTPayPalPaymentType = .checkout var amount: String var intent: BTPayPalRequestIntent @@ -130,8 +130,6 @@ import BraintreeCore userAuthenticationEmail: String? = nil, userPhoneNumber: BTPayPalPhoneNumber? = nil ) { - self.hermesPath = "v1/paypal_hermes/create_payment_resource" - self.paymentType = .checkout self.amount = amount self.intent = intent self.userAction = userAction diff --git a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift index 00528f170..9e36f4487 100644 --- a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift @@ -9,8 +9,8 @@ import BraintreeCore // MARK: - Internal Properties - let hermesPath: String - let paymentType: BTPayPalPaymentType + let hermesPath = "v1/paypal_hermes/setup_billing_agreement" + let paymentType: BTPayPalPaymentType = .vault var offerCredit: Bool var enablePayPalAppSwitch: Bool = false @@ -87,8 +87,6 @@ import BraintreeCore userAuthenticationEmail: String? = nil, userPhoneNumber: BTPayPalPhoneNumber? = nil ) { - self.hermesPath = "v1/paypal_hermes/setup_billing_agreement" - self.paymentType = .vault self.offerCredit = offerCredit self.billingAgreementDescription = billingAgreementDescription self.displayName = displayName From e47843789f07e812d39d8e7f486d1259885ef9a5 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 13 Jan 2025 10:14:11 -0600 Subject: [PATCH 41/49] Remove BTLineItems custom encode method --- Sources/BraintreePayPal/BTPayPalClient.swift | 31 ++++++++++++++--- .../BraintreePayPal/BTPayPalLineItem.swift | 33 +++++++------------ 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index 7dc2307b9..7e6acbaf7 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -356,13 +356,12 @@ import BraintreeDataCollector } self.payPalRequest = request + + let parameters = PayPalCheckoutPOSTBody(payPalRequest: request as! BTPayPalCheckoutRequest, configuration: configuration) + self.apiClient.post( request.hermesPath, - parameters: request.parameters( - with: configuration, - universalLink: self.universalLink, - isPayPalAppInstalled: self.application.isPayPalAppInstalled() - ) + parameters: parameters ) { body, _, error in if let error = error as? NSError { guard let jsonResponseBody = error.userInfo[BTCoreConstants.jsonResponseBodyKey] as? BTJSON else { @@ -402,6 +401,28 @@ import BraintreeDataCollector } } + func printJSON(from dictionary: [String: Any]) { + if let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted), + let jsonString = String(data: jsonData, encoding: .utf8) { + print(jsonString) + } else { + print("Failed to convert dictionary to JSON.") + } + } + + func printJSONEncodable(from model: Encodable) { + do { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let jsonData = try encoder.encode(model) + if let jsonString = String(data: jsonData, encoding: .utf8) { + print(jsonString) + } + } catch { + print("Error encoding JSON: \(error)") + } + } + private func launchPayPalApp( with payPalAppRedirectURL: URL, baToken: String, diff --git a/Sources/BraintreePayPal/BTPayPalLineItem.swift b/Sources/BraintreePayPal/BTPayPalLineItem.swift index f3e1fe0b0..43a093ebe 100644 --- a/Sources/BraintreePayPal/BTPayPalLineItem.swift +++ b/Sources/BraintreePayPal/BTPayPalLineItem.swift @@ -1,7 +1,7 @@ import Foundation /// Use this option to specify whether a line item is a debit (sale) or credit (refund) to the customer. -@objc public enum BTPayPalLineItemKind: Int { +@objc public enum BTPayPalLineItemKind: Int, Encodable { /// Debit case debit @@ -16,11 +16,16 @@ import Foundation return "credit" } } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(stringValue) + } } // swiftlint:disable identifier_name /// Use this option to specify the UPC type of the line item. -@objc public enum BTPayPalLineItemUPCType: Int { +@objc public enum BTPayPalLineItemUPCType: Int, Encodable { /// Default case none @@ -67,6 +72,11 @@ import Foundation return "UPC-5" } } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(stringValue) + } } /// A PayPal line item to be displayed in the PayPal checkout flow. @@ -137,25 +147,6 @@ import Foundation case url } - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(quantity, forKey: .quantity) - try container.encode(unitAmount, forKey: .unitAmount) - try container.encode(name, forKey: .name) - try container.encode(kind.stringValue, forKey: .kind) - - if let unitTaxAmount, !unitTaxAmount.isEmpty { - try container.encode(unitAmount, forKey: .unitTaxAmount) - } - - try container.encodeIfPresent(itemDescription, forKey: .itemDescription) - try container.encodeIfPresent(productCode, forKey: .productCode) - try container.encodeIfPresent(url?.absoluteString, forKey: .url) - try container.encodeIfPresent(imageURL?.absoluteString, forKey: .imageURL) - try container.encodeIfPresent(upcCode, forKey: .upcCode) - try container.encodeIfPresent(upcType.stringValue, forKey: .upcType) - } - // MARK: - Internal Methods /// Returns the line item in a dictionary. From 6dfb4887c1abc1503b57cd9e2477372a77c5c206 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 13 Jan 2025 10:20:26 -0600 Subject: [PATCH 42/49] Revert unnecessary changes --- Sources/BraintreePayPal/BTPayPalClient.swift | 31 ++++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index 7e6acbaf7..7dc2307b9 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -356,12 +356,13 @@ import BraintreeDataCollector } self.payPalRequest = request - - let parameters = PayPalCheckoutPOSTBody(payPalRequest: request as! BTPayPalCheckoutRequest, configuration: configuration) - self.apiClient.post( request.hermesPath, - parameters: parameters + parameters: request.parameters( + with: configuration, + universalLink: self.universalLink, + isPayPalAppInstalled: self.application.isPayPalAppInstalled() + ) ) { body, _, error in if let error = error as? NSError { guard let jsonResponseBody = error.userInfo[BTCoreConstants.jsonResponseBodyKey] as? BTJSON else { @@ -401,28 +402,6 @@ import BraintreeDataCollector } } - func printJSON(from dictionary: [String: Any]) { - if let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted), - let jsonString = String(data: jsonData, encoding: .utf8) { - print(jsonString) - } else { - print("Failed to convert dictionary to JSON.") - } - } - - func printJSONEncodable(from model: Encodable) { - do { - let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - let jsonData = try encoder.encode(model) - if let jsonString = String(data: jsonData, encoding: .utf8) { - print(jsonString) - } - } catch { - print("Error encoding JSON: \(error)") - } - } - private func launchPayPalApp( with payPalAppRedirectURL: URL, baToken: String, From f90356b85bc8d0b0fd5529d41ea4508b61f52df5 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 13 Jan 2025 12:52:01 -0600 Subject: [PATCH 43/49] Rename PayPalRequest protocol to BTPayPalRequest --- Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift | 2 +- Sources/BraintreePayPal/BTPayPalClient.swift | 4 ++-- Sources/BraintreePayPal/BTPayPalRequest.swift | 2 +- Sources/BraintreePayPal/BTPayPalVaultRequest.swift | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift index 578b2367f..6fd5bc5e2 100644 --- a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift @@ -56,7 +56,7 @@ import BraintreeCore } /// Options for the PayPal Checkout flow. -@objcMembers public class BTPayPalCheckoutRequest: NSObject, PayPalRequest { +@objcMembers public class BTPayPalCheckoutRequest: NSObject, BTPayPalRequest { // MARK: - Internal Properties diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index 6c49ba7bb..0961a4b4a 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -29,7 +29,7 @@ import BraintreeDataCollector var clientMetadataID: String? /// Exposed for testing the intent associated with this request - var payPalRequest: PayPalRequest? + var payPalRequest: BTPayPalRequest? /// Exposed for testing, the ASWebAuthenticationSession instance used for the PayPal flow var webAuthenticationSession: BTWebAuthenticationSession @@ -331,7 +331,7 @@ import BraintreeDataCollector // MARK: - Private Methods private func tokenize( - request: PayPalRequest, + request: BTPayPalRequest, completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void ) { linkType = (request as? BTPayPalVaultRequest)?.enablePayPalAppSwitch == true ? .universal : .deeplink diff --git a/Sources/BraintreePayPal/BTPayPalRequest.swift b/Sources/BraintreePayPal/BTPayPalRequest.swift index 4cd5e0986..7a8f30780 100644 --- a/Sources/BraintreePayPal/BTPayPalRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalRequest.swift @@ -48,7 +48,7 @@ import BraintreeCore } } -protocol PayPalRequest { +protocol BTPayPalRequest { var hermesPath: String { get } var paymentType: BTPayPalPaymentType { get } var billingAgreementDescription: String? { get } diff --git a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift index 610698d01..2d33d7365 100644 --- a/Sources/BraintreePayPal/BTPayPalVaultRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift @@ -5,7 +5,7 @@ import BraintreeCore #endif /// Options for the PayPal Vault flow. -@objcMembers public class BTPayPalVaultRequest: NSObject, PayPalRequest { +@objcMembers public class BTPayPalVaultRequest: NSObject, BTPayPalRequest { // MARK: - Internal Properties From df8eba1ea2e34fe22f62a824ee2e4736c08fb235 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 13 Jan 2025 13:26:30 -0600 Subject: [PATCH 44/49] Remove unnecessary parameters methods --- .../BraintreePayPal/BTPayPalLineItem.swift | 43 ------------------- .../BTPayPalRecurringBillingDetails.swift | 40 ----------------- .../BTPayPalLineItem_Tests.swift | 42 +++++++++--------- 3 files changed, 21 insertions(+), 104 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalLineItem.swift b/Sources/BraintreePayPal/BTPayPalLineItem.swift index 43a093ebe..62435d9ba 100644 --- a/Sources/BraintreePayPal/BTPayPalLineItem.swift +++ b/Sources/BraintreePayPal/BTPayPalLineItem.swift @@ -146,47 +146,4 @@ import Foundation case upcType = "upc_type" case url } - - // MARK: - Internal Methods - - /// Returns the line item in a dictionary. - /// - Returns: A dictionary with the line item information formatted for a request. - func requestParameters() -> [String: String] { - var requestParameters = [ - "quantity": quantity, - "unit_amount": unitAmount, - "name": name, - "kind": kind == .debit ? "debit" : "credit" - ] - - if let unitTaxAmount, !unitTaxAmount.isEmpty { - requestParameters["unit_tax_amount"] = unitAmount - } - - if let itemDescription, !itemDescription.isEmpty { - requestParameters["description"] = itemDescription - } - - if let productCode, !productCode.isEmpty { - requestParameters["product_code"] = productCode - } - - if let url, url != URL(string: "") { - requestParameters["url"] = url.absoluteString - } - - if let imageURL, imageURL != URL(string: "") { - requestParameters["image_url"] = imageURL.absoluteString - } - - if let upcCode, !upcCode.isEmpty { - requestParameters["upc_code"] = upcCode - } - - if upcType.stringValue != nil { - requestParameters["upc_type"] = upcType.stringValue - } - - return requestParameters - } } diff --git a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingDetails.swift b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingDetails.swift index 2a4f0b4a0..be06573d0 100644 --- a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingDetails.swift +++ b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingDetails.swift @@ -66,44 +66,4 @@ public struct BTPayPalRecurringBillingDetails: Encodable { case taxAmount = "tax_amount" case totalAmount = "total_amount" } - - // MARK: - Internal Methods - - func parameters() -> [String: Any] { - var parameters: [String: Any] = [ - "total_amount": totalAmount, - "currency_iso_code": currencyISOCode, - "billing_cycles": billingCycles.map { $0.parameters() } - ] - - if let productName { - parameters["name"] = productName - } - - if let productDescription { - parameters["product_description"] = productDescription - } - - if let productQuantity { - parameters["product_quantity"] = productQuantity - } - - if let oneTimeFeeAmount { - parameters["one_time_fee_amount"] = oneTimeFeeAmount - } - - if let shippingAmount { - parameters["shipping_amount"] = shippingAmount - } - - if let productAmount { - parameters["product_price"] = productAmount - } - - if let taxAmount { - parameters["tax_amount"] = taxAmount - } - - return parameters - } } diff --git a/UnitTests/BraintreePayPalTests/BTPayPalLineItem_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalLineItem_Tests.swift index f6439c0dd..fb62d7b86 100644 --- a/UnitTests/BraintreePayPalTests/BTPayPalLineItem_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalLineItem_Tests.swift @@ -5,44 +5,44 @@ import XCTest class BTPayPalLineItem_Tests: XCTestCase { func testUPCTypeStringReturnsCorrectValue() { - var lineItem = BTPayPalLineItem(quantity: "1", unitAmount: "10", name: "item-name", kind: .debit) + let lineItem = BTPayPalLineItem(quantity: "1", unitAmount: "10", name: "item-name", kind: .debit) - lineItem.upcType = .UPC_A - var requestParams = lineItem.requestParameters() - XCTAssertEqual(requestParams["upc_type"], "UPC-A") + lineItem.upcType = .UPC_A + var requestParams = try? lineItem.toDictionary() + XCTAssertEqual(requestParams?["upc_type"] as? String, "UPC-A") lineItem.upcType = .UPC_B - requestParams = lineItem.requestParameters() - XCTAssertEqual(requestParams["upc_type"], "UPC-B") + requestParams = try? lineItem.toDictionary() + XCTAssertEqual(requestParams?["upc_type"] as? String, "UPC-B") lineItem.upcType = .UPC_C - requestParams = lineItem.requestParameters() - XCTAssertEqual(requestParams["upc_type"], "UPC-C") + requestParams = try? lineItem.toDictionary() + XCTAssertEqual(requestParams?["upc_type"] as? String, "UPC-C") lineItem.upcType = .UPC_D - requestParams = lineItem.requestParameters() - XCTAssertEqual(requestParams["upc_type"], "UPC-D") + requestParams = try? lineItem.toDictionary() + XCTAssertEqual(requestParams?["upc_type"] as? String, "UPC-D") lineItem.upcType = .UPC_E - requestParams = lineItem.requestParameters() - XCTAssertEqual(requestParams["upc_type"], "UPC-E") + requestParams = try? lineItem.toDictionary() + XCTAssertEqual(requestParams?["upc_type"] as? String, "UPC-E") lineItem.upcType = .UPC_2 - requestParams = lineItem.requestParameters() - XCTAssertEqual(requestParams["upc_type"]!, "UPC-2") + requestParams = try? lineItem.toDictionary() + XCTAssertEqual(requestParams?["upc_type"] as? String, "UPC-2") lineItem.upcType = .UPC_5 - requestParams = lineItem.requestParameters() - XCTAssertEqual(requestParams["upc_type"], "UPC-5") + requestParams = try? lineItem.toDictionary() + XCTAssertEqual(requestParams?["upc_type"] as? String, "UPC-5") } func testKindStringReturnsCorrectValue() { var lineItem = BTPayPalLineItem(quantity: "1", unitAmount: "10", name: "item-name", kind: .debit) - var requestParams = lineItem.requestParameters() - XCTAssertEqual(requestParams["kind"], "debit") - + var requestParams = try? lineItem.toDictionary() + XCTAssertEqual(requestParams?["kind"] as? String, "debit") + lineItem = BTPayPalLineItem(quantity: "1", unitAmount: "10", name: "item-name", kind: .credit) - requestParams = lineItem.requestParameters() - XCTAssertEqual(requestParams["kind"], "credit") + requestParams = try? lineItem.toDictionary() + XCTAssertEqual(requestParams?["kind"] as? String, "credit") } } From 3fa9a0d69f35a6f838e5cde363a7da25a55f3623 Mon Sep 17 00:00:00 2001 From: richherrera Date: Mon, 13 Jan 2025 13:34:38 -0600 Subject: [PATCH 45/49] Remove unnecessary parameters methods --- .../BTPayPalBillingCycle.swift | 31 ------------------- .../BTPayPalBillingPricing.swift | 18 ----------- 2 files changed, 49 deletions(-) diff --git a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingCycle.swift b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingCycle.swift index 5012d7e6c..3284ca49c 100644 --- a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingCycle.swift +++ b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingCycle.swift @@ -61,35 +61,4 @@ public struct BTPayPalBillingCycle: Encodable { case startDate = "start_date" case pricing = "pricing_scheme" } - - // MARK: - Internal Methods - - func parameters() -> [String: Any] { - var parameters: [String: Any] = [ - "number_of_executions": numberOfExecutions, - "trial": isTrial - ] - - if let interval { - parameters["billing_frequency_unit"] = interval.rawValue - } - - if let intervalCount { - parameters["billing_frequency"] = intervalCount - } - - if let sequence { - parameters["sequence"] = sequence - } - - if let startDate { - parameters["start_date"] = startDate - } - - if let pricing { - parameters["pricing_scheme"] = pricing.parameters() - } - - return parameters - } } diff --git a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingPricing.swift b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingPricing.swift index 2d18f7b0e..1312d200a 100644 --- a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingPricing.swift +++ b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingPricing.swift @@ -36,22 +36,4 @@ public struct BTPayPalBillingPricing: Encodable { case pricingModel = "pricing_model" case reloadThresholdAmount = "reload_threshold_amount" } - - // MARK: - Internal Methods - - func parameters() -> [String: Any] { - var parameters: [String: Any] = [ - "pricing_model": pricingModel.rawValue - ] - - if let amount { - parameters["price"] = amount - } - - if let reloadThresholdAmount { - parameters["reload_threshold_amount"] = reloadThresholdAmount - } - - return parameters - } } From f3ccfe4d1eb757034b7925ea4afb5dc743d5f947 Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 21 Jan 2025 10:23:52 -0600 Subject: [PATCH 46/49] Add quotation marks --- Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift index 8b17ae7d8..2933bd466 100644 --- a/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift @@ -4,7 +4,7 @@ import Foundation import BraintreeCore #endif -/// The POST body for v1/paypal_hermes/create_payment_resource +/// The POST body for `v1/paypal_hermes/create_payment_resource` struct PayPalCheckoutPOSTBody: Encodable { // MARK: - Private Properties From 876599dc2b2ea9a8095389d900a1864e98a1d409 Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 21 Jan 2025 11:37:57 -0600 Subject: [PATCH 47/49] Add docstrings and move the enums to their own file. --- Braintree.xcodeproj/project.pbxproj | 10 ++++- .../BraintreePayPal/BTPayPalPaymentType.swift | 19 ++++++++ Sources/BraintreePayPal/BTPayPalRequest.swift | 45 +------------------ .../BTPayPalRequestLandingPageType.swift | 27 +++++++++++ 4 files changed, 56 insertions(+), 45 deletions(-) create mode 100644 Sources/BraintreePayPal/BTPayPalPaymentType.swift create mode 100644 Sources/BraintreePayPal/BTPayPalRequestLandingPageType.swift diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index 2f5ebd107..1cd0cf3d2 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -36,6 +36,8 @@ 428F976626727333001042E1 /* BTMockOpenURLContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 428F976526727333001042E1 /* BTMockOpenURLContext.m */; }; 42FC218B25CDE0290047C49A /* BTPayPalRequest_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42FC218A25CDE0290047C49A /* BTPayPalRequest_Tests.swift */; }; 42FC237125CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42FC237025CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift */; }; + 451508362D400C050071E385 /* BTPayPalPaymentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451508352D400C050071E385 /* BTPayPalPaymentType.swift */; }; + 451508382D400C400071E385 /* BTPayPalRequestLandingPageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451508372D400C400071E385 /* BTPayPalRequestLandingPageType.swift */; }; 45227FC52C330FDE00A15018 /* MockURLSessionTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45227FC32C330FDE00A15018 /* MockURLSessionTask.swift */; }; 45227FC72C33104100A15018 /* MockBTHTTPNetworkTiming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45227FC62C33104100A15018 /* MockBTHTTPNetworkTiming.swift */; }; 454722B02CF0DA34000DCF4E /* LocalPaymentPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454722AF2CF0DA27000DCF4E /* LocalPaymentPOSTBody.swift */; }; @@ -719,6 +721,8 @@ 42F75E5A24D48138007DC5E7 /* BTThreeDSecureClient_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureClient_Tests.swift; sourceTree = ""; }; 42FC218A25CDE0290047C49A /* BTPayPalRequest_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalRequest_Tests.swift; sourceTree = ""; }; 42FC237025CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalCheckoutRequest_Tests.swift; sourceTree = ""; }; + 451508352D400C050071E385 /* BTPayPalPaymentType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalPaymentType.swift; sourceTree = ""; }; + 451508372D400C400071E385 /* BTPayPalRequestLandingPageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalRequestLandingPageType.swift; sourceTree = ""; }; 45227FC32C330FDE00A15018 /* MockURLSessionTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSessionTask.swift; sourceTree = ""; }; 45227FC62C33104100A15018 /* MockBTHTTPNetworkTiming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBTHTTPNetworkTiming.swift; sourceTree = ""; }; 454722AF2CF0DA27000DCF4E /* LocalPaymentPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalPaymentPOSTBody.swift; sourceTree = ""; }; @@ -1299,13 +1303,15 @@ 57544F5D295258AC00DEB7B0 /* BTPayPalError.swift */, BEF5D2E5294A18B300FFD56D /* BTPayPalLineItem.swift */, 57D9436D2968A8080079EAB1 /* BTPayPalLocaleCode.swift */, + 451508352D400C050071E385 /* BTPayPalPaymentType.swift */, + 62B811872CC002470024A688 /* BTPayPalPhoneNumber.swift */, BE349112294B798300D2CF68 /* BTPayPalRequest.swift */, + 451508372D400C400071E385 /* BTPayPalRequestLandingPageType.swift */, BE6BC22D2BA9CFFC00C3E321 /* BTPayPalReturnURL.swift */, BE349110294B77E100D2CF68 /* BTPayPalVaultRequest.swift */, 45E8CE4A2D1F291400D7A2DC /* Models */, 62A659A32B98CB23008DFD67 /* PrivacyInfo.xcprivacy */, 807D22F32C29ADA8009FFEA4 /* RecurringBillingMetadata */, - 62B811872CC002470024A688 /* BTPayPalPhoneNumber.swift */, ); path = BraintreePayPal; sourceTree = ""; @@ -3119,12 +3125,14 @@ BE549F112BF5445F00B6F441 /* BTPayPalReturnURL.swift in Sources */, 57544F5C295254A500DEB7B0 /* BTJSON+PayPal.swift in Sources */, 3B7A261129C0CAA40087059D /* BTPayPalAnalytics.swift in Sources */, + 451508382D400C400071E385 /* BTPayPalRequestLandingPageType.swift in Sources */, BE8E5CEF294B6937001BF017 /* BTPayPalCheckoutRequest.swift in Sources */, 807D22F02C29A93A009FFEA4 /* BTPayPalBillingCycle.swift in Sources */, 5754481E294A2A1D00DEB7B0 /* BTPayPalCreditFinancingAmount.swift in Sources */, 57D9436E2968A8080079EAB1 /* BTPayPalLocaleCode.swift in Sources */, 45E8CE4C2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift in Sources */, 57544F582952298900DEB7B0 /* BTPayPalAccountNonce.swift in Sources */, + 451508362D400C050071E385 /* BTPayPalPaymentType.swift in Sources */, 8014221C2BAE935B009F9999 /* BTPayPalApprovalURLParser.swift in Sources */, BE349111294B77E100D2CF68 /* BTPayPalVaultRequest.swift in Sources */, 62B811882CC002470024A688 /* BTPayPalPhoneNumber.swift in Sources */, diff --git a/Sources/BraintreePayPal/BTPayPalPaymentType.swift b/Sources/BraintreePayPal/BTPayPalPaymentType.swift new file mode 100644 index 000000000..d5ce26ba8 --- /dev/null +++ b/Sources/BraintreePayPal/BTPayPalPaymentType.swift @@ -0,0 +1,19 @@ +import Foundation + +@objc public enum BTPayPalPaymentType: Int { + + /// Checkout + case checkout + + /// Vault + case vault + + var stringValue: String { + switch self { + case .vault: + return "paypal-ba" + case .checkout: + return "paypal-single-payment" + } + } +} diff --git a/Sources/BraintreePayPal/BTPayPalRequest.swift b/Sources/BraintreePayPal/BTPayPalRequest.swift index be2b90dee..03fd35b71 100644 --- a/Sources/BraintreePayPal/BTPayPalRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalRequest.swift @@ -4,50 +4,7 @@ import UIKit import BraintreeCore #endif -@objc public enum BTPayPalPaymentType: Int { - - /// Checkout - case checkout - - /// Vault - case vault - - var stringValue: String { - switch self { - case .vault: - return "paypal-ba" - case .checkout: - return "paypal-single-payment" - } - } -} - -/// Use this option to specify the PayPal page to display when a user lands on the PayPal site to complete the payment. -@objc public enum BTPayPalRequestLandingPageType: Int { - - /// Default - case none // Obj-C enums cannot be nil; this default option is used to make `landingPageType` optional for merchants - - /// Login - case login - - /// Billing - case billing - - var stringValue: String? { - switch self { - case .login: - return "login" - - case .billing: - return "billing" - - default: - return nil - } - } -} - +/// Defines the structure and requirements for PayPal Checkout and PayPal Vault flows. protocol PayPalRequest { var hermesPath: String { get } var paymentType: BTPayPalPaymentType { get } diff --git a/Sources/BraintreePayPal/BTPayPalRequestLandingPageType.swift b/Sources/BraintreePayPal/BTPayPalRequestLandingPageType.swift new file mode 100644 index 000000000..63480e6b5 --- /dev/null +++ b/Sources/BraintreePayPal/BTPayPalRequestLandingPageType.swift @@ -0,0 +1,27 @@ +import Foundation + +/// Use this option to specify the PayPal page to display when a user lands on the PayPal site to complete the payment. +@objc public enum BTPayPalRequestLandingPageType: Int { + + /// Default + case none // Obj-C enums cannot be nil; this default option is used to make `landingPageType` optional for merchants + + /// Login + case login + + /// Billing + case billing + + var stringValue: String? { + switch self { + case .login: + return "login" + + case .billing: + return "billing" + + default: + return nil + } + } +} From 09357ff5c1a438d8e878c54bf62f3c5d5fae2f5b Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 21 Jan 2025 12:07:31 -0600 Subject: [PATCH 48/49] Address PR comments --- Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift index 77d04443c..eedc538a0 100644 --- a/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift @@ -10,10 +10,10 @@ struct PayPalVaultPOSTBody: Encodable { // MARK: - Private Properties - private let userPhoneNumber: BTPayPalPhoneNumber? private let returnURL: String private let cancelURL: String private let experienceProfile: PayPalExperienceProfile + private let userPhoneNumber: BTPayPalPhoneNumber? private var billingAgreementDescription: String? private var enablePayPalAppSwitch: Bool? From 362f9bb963f696e9d0fb4c9e8f112744be2d34e1 Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 21 Jan 2025 12:28:35 -0600 Subject: [PATCH 49/49] Update paypalrequest property --- Sources/BraintreePayPal/BTPayPalClient.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index 6c49ba7bb..0961a4b4a 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -29,7 +29,7 @@ import BraintreeDataCollector var clientMetadataID: String? /// Exposed for testing the intent associated with this request - var payPalRequest: PayPalRequest? + var payPalRequest: BTPayPalRequest? /// Exposed for testing, the ASWebAuthenticationSession instance used for the PayPal flow var webAuthenticationSession: BTWebAuthenticationSession @@ -331,7 +331,7 @@ import BraintreeDataCollector // MARK: - Private Methods private func tokenize( - request: PayPalRequest, + request: BTPayPalRequest, completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void ) { linkType = (request as? BTPayPalVaultRequest)?.enablePayPalAppSwitch == true ? .universal : .deeplink