diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index 2f5ebd107..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 */; }; @@ -45,6 +44,8 @@ 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 */; }; + 45E8CE502D2C773600D7A2DC /* PayPalExperienceProfilePOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE4F2D2C772000D7A2DC /* PayPalExperienceProfilePOSTBody.swift */; }; 45E8CE522D2C920000D7A2DC /* LocalPaymentAccountsPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE512D2C91F000D7A2DC /* LocalPaymentAccountsPOSTBody.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 */; }; @@ -717,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 = ""; }; @@ -728,6 +728,8 @@ 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 = ""; }; + 45E8CE4F2D2C772000D7A2DC /* PayPalExperienceProfilePOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalExperienceProfilePOSTBody.swift; sourceTree = ""; }; 45E8CE512D2C91F000D7A2DC /* LocalPaymentAccountsPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalPaymentAccountsPOSTBody.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 = ""; }; @@ -1323,6 +1325,8 @@ isa = PBXGroup; children = ( 45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */, + 45E8CE4D2D28611700D7A2DC /* PayPalVaultPOSTBody.swift */, + 45E8CE4F2D2C772000D7A2DC /* PayPalExperienceProfilePOSTBody.swift */, ); path = Models; sourceTree = ""; @@ -1916,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 */, @@ -3117,10 +3120,12 @@ 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 */, 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 */, @@ -3433,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/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" + } } diff --git a/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift b/Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift index efc9a7387..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 @@ -149,92 +149,14 @@ 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 let landingPageType = landingPageType?.stringValue { - experienceProfile["landing_page_type"] = landingPageType - } - - if let localeCode = localeCode?.stringValue { - experienceProfile["locale_code"] = localeCode - } - - experienceProfile["address_override"] = shippingAddressOverride != nil ? !isShippingAddressEditable : false - - var baseParameters: [String: Any] = [:] - - if let merchantAccountID { - baseParameters["merchant_account_id"] = merchantAccountID - } - - if let riskCorrelationID { - 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 let currencyCode { - 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 { - checkoutParameters["request_billing_agreement"] = requestBillingAgreement - - if billingAgreementDescription != nil { - checkoutParameters["billing_agreement_details"] = ["description": billingAgreementDescription] - } - } - - 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 } + + 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 7dc2307b9..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 @@ -343,26 +343,29 @@ import BraintreeDataCollector return } - guard let configuration, let json = configuration.json else { + guard let configuration else { self.notifyFailure(with: BTPayPalError.fetchConfigurationFailed, completion: completion) return } self.isConfigFromCache = configuration.isFromCache - guard json["paypalEnabled"].isTrue else { + guard configuration.isPayPalEnabled else { self.notifyFailure(with: BTPayPalError.disabled, completion: completion) return } self.payPalRequest = request + + let parameters = request.encodedPostBodyWith( + configuration: configuration, + isPayPalAppInstalled: self.application.isPayPalAppInstalled(), + universalLink: self.universalLink + ) + 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 { 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/BTPayPalRequest.swift b/Sources/BraintreePayPal/BTPayPalRequest.swift index be2b90dee..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 } @@ -64,149 +64,9 @@ protocol PayPalRequest { 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/" - } + func encodedPostBodyWith(configuration: BTConfiguration, isPayPalAppInstalled: Bool, universalLink: URL?) -> Encodable } -/// 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 - - 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 - } +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/BTPayPalVaultRequest.swift b/Sources/BraintreePayPal/BTPayPalVaultRequest.swift index 9e36f4487..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 @@ -103,94 +103,19 @@ 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 let landingPageType = landingPageType?.stringValue { - experienceProfile["landing_page_type"] = landingPageType - } - - if let localeCode = localeCode?.stringValue { - experienceProfile["locale_code"] = localeCode - } - - experienceProfile["address_override"] = shippingAddressOverride != nil ? !isShippingAddressEditable : false - - var baseParameters: [String: Any] = [:] - - if let merchantAccountID { - baseParameters["merchant_account_id"] = merchantAccountID - } - - if let riskCorrelationID { - 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 } + + // MARK: Internal Methods + + func encodedPostBodyWith( + configuration: BTConfiguration, + isPayPalAppInstalled: Bool = false, + universalLink: URL? = nil + ) -> Encodable { + PayPalVaultPOSTBody( + payPalRequest: self, + configuration: configuration, + isPayPalAppInstalled: isPayPalAppInstalled, + universalLink: universalLink + ) } } diff --git a/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift index 8b17ae7d8..e17f0bd75 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? @@ -82,9 +82,9 @@ 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.returnURL = BTCoreConstants.callbackURLScheme + "://\(PayPalRequestConstants.callbackURLHostAndPath)success" + self.cancelURL = BTCoreConstants.callbackURLScheme + "://\(PayPalRequestConstants.callbackURLHostAndPath)cancel" + 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 new file mode 100644 index 000000000..77d04443c --- /dev/null +++ b/Sources/BraintreePayPal/Models/PayPalVaultPOSTBody.swift @@ -0,0 +1,107 @@ +import Foundation +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: PayPalExperienceProfile + + private var billingAgreementDescription: String? + private var enablePayPalAppSwitch: Bool? + private var lineItems: [BTPayPalLineItem]? + 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 { + self.lineItems = lineItems + } + + if let userAuthenticationEmail = payPalRequest.userAuthenticationEmail, !userAuthenticationEmail.isEmpty { + self.userAuthenticationEmail = userAuthenticationEmail + } + + self.userPhoneNumber = payPalRequest.userPhoneNumber + 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 { + 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" + } +} diff --git a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingCycle.swift b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingCycle.swift index e67b45b6b..3284ca49c 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,34 +52,13 @@ public struct BTPayPalBillingCycle { self.pricing = pricing } - // 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 + 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" } } diff --git a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingPricing.swift b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalBillingPricing.swift index 48c6876ff..1312d200a 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,21 +31,9 @@ public struct BTPayPalBillingPricing { self.reloadThresholdAmount = reloadThresholdAmount } - // 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 + enum CodingKeys: String, CodingKey { + case amount = "price" + case pricingModel = "pricing_model" + case reloadThresholdAmount = "reload_threshold_amount" } } diff --git a/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingDetails.swift b/Sources/BraintreePayPal/RecurringBillingMetadata/BTPayPalRecurringBillingDetails.swift index 312ca1963..be06573d0 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,43 +54,16 @@ public struct BTPayPalRecurringBillingDetails { self.taxAmount = taxAmount } - // 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 + 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" } } 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" diff --git a/UnitTests/BraintreePayPalTests/BTPayPalCheckoutRequest_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalCheckoutRequest_Tests.swift index e082e120f..6110ce692 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,8 +185,99 @@ 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"]) } + + 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/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") } } 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) - } -} diff --git a/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalVaultRequest_Tests.swift index 645bbbd4d..8b3e62c6b 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 } @@ -152,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) + } }