diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index 781021fc4d..9556cad414 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -150,7 +150,6 @@ A95C411724FAEF5100045045 /* BTCardNonce_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7A094F51B8276E500D732CC /* BTCardNonce_Tests.swift */; }; A95C411824FAEF5100045045 /* BTThreeDSecureInfo_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0301A42E22E2286C008A26BD /* BTThreeDSecureInfo_Tests.swift */; }; A95C411C24FAF21B00045045 /* BraintreeTestShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A903E1A624F9D34000C314E1 /* BraintreeTestShared.framework */; }; - A977A04124FEC36F006049AB /* BTPaymentMethodNonceParser_PayPal_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977A04024FEC36F006049AB /* BTPaymentMethodNonceParser_PayPal_Tests.swift */; platformFilter = ios; }; A977A06B24FECFC3006049AB /* NonceValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977A06A24FECFC3006049AB /* NonceValidator.swift */; }; A9A76CA724F9E92E0044EAEE /* TestClientTokenFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A76CA624F9E92E0044EAEE /* TestClientTokenFactory.swift */; }; A9E5C1A324FD5A7C00EE691F /* BraintreeDataCollector.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A76D7C001BB1CAB00000FA6A /* BraintreeDataCollector.framework */; platformFilter = ios; }; @@ -270,6 +269,7 @@ BEDB820629B13F9500075AF3 /* BTPayPalNativeCheckoutAccountNonce_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEDB820529B13F9500075AF3 /* BTPayPalNativeCheckoutAccountNonce_Tests.swift */; }; BEDB820829B675DC00075AF3 /* BTVenmoAccountNonce.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEDB820729B675DC00075AF3 /* BTVenmoAccountNonce.swift */; }; BEDB820A29B7A9E600075AF3 /* BTVenmoClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEDB820929B7A9E600075AF3 /* BTVenmoClient.swift */; }; + BEDEAF112AC1D049004EA970 /* BTPayPalAccountNonce_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEDEAF102AC1D049004EA970 /* BTPayPalAccountNonce_Tests.swift */; }; BEE2E4A728FDB64400C03FDD /* BTAnalyticsService_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEE2E4A628FDB64400C03FDD /* BTAnalyticsService_Tests.swift */; }; BEE2E4B9290043A200C03FDD /* PayPalCheckout in Frameworks */ = {isa = PBXBuildFile; productRef = BEE2E4B8290043A200C03FDD /* PayPalCheckout */; }; BEE2E4E6290080BD00C03FDD /* BTAnalyticsServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEE2E4E5290080BD00C03FDD /* BTAnalyticsServiceError.swift */; }; @@ -778,7 +778,6 @@ A9589DD924FEA45C00AF4FF7 /* BTConfiguration+Venmo_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BTConfiguration+Venmo_Tests.swift"; sourceTree = ""; }; A95C410824FAEF2100045045 /* BraintreeCardTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BraintreeCardTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A95C410C24FAEF2100045045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A977A04024FEC36F006049AB /* BTPaymentMethodNonceParser_PayPal_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPaymentMethodNonceParser_PayPal_Tests.swift; sourceTree = ""; }; A977A06924FECBAF006049AB /* BraintreeThreeDSecureTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BraintreeThreeDSecureTests-Bridging-Header.h"; sourceTree = ""; }; A977A06A24FECFC3006049AB /* NonceValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceValidator.swift; sourceTree = ""; }; A9A76CA624F9E92E0044EAEE /* TestClientTokenFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestClientTokenFactory.swift; sourceTree = ""; }; @@ -883,6 +882,7 @@ BEDB820729B675DC00075AF3 /* BTVenmoAccountNonce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTVenmoAccountNonce.swift; sourceTree = ""; }; BEDB820929B7A9E600075AF3 /* BTVenmoClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTVenmoClient.swift; sourceTree = ""; }; BEDE06942FEDCD2F91884DA9 /* Pods-Tests-IntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-IntegrationTests.release.xcconfig"; path = "Target Support Files/Pods-Tests-IntegrationTests/Pods-Tests-IntegrationTests.release.xcconfig"; sourceTree = ""; }; + BEDEAF102AC1D049004EA970 /* BTPayPalAccountNonce_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalAccountNonce_Tests.swift; sourceTree = ""; }; BEE2E4A628FDB64400C03FDD /* BTAnalyticsService_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAnalyticsService_Tests.swift; sourceTree = ""; }; BEE2E4E329007FF100C03FDD /* BTAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAnalyticsService.swift; sourceTree = ""; }; BEE2E4E5290080BD00C03FDD /* BTAnalyticsServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAnalyticsServiceError.swift; sourceTree = ""; }; @@ -1605,8 +1605,8 @@ 428F48E32624C9B700EC8DB4 /* BTVenmoAppSwitchRedirectURL_Tests.swift */, 4245D668256C255F00F1A413 /* BTVenmoAppSwitchReturnURL_Tests.swift */, A71F7DE61B6180A3005DA1B0 /* BTVenmoClient_Tests.swift */, - 4228D8682624E5C3001D2564 /* BTVenmoRequest_Tests.swift */, 09357DCA2A2FBEC10096D449 /* BTVenmoLineItem_Tests.swift */, + 4228D8682624E5C3001D2564 /* BTVenmoRequest_Tests.swift */, A948D6F424FAC8F900F4F178 /* Info.plist */, ); path = BraintreeVenmoTests; @@ -1641,7 +1641,7 @@ children = ( A9E5C1F724FD672A00EE691F /* BraintreePayPalTests-Bridging-Header.h */, A95229C624FD949D006F7D25 /* BTConfiguration+PayPal_Tests.swift */, - A977A04024FEC36F006049AB /* BTPaymentMethodNonceParser_PayPal_Tests.swift */, + BEDEAF102AC1D049004EA970 /* BTPayPalAccountNonce_Tests.swift */, 3B7A261229C35B670087059D /* BTPayPalAnalytics_Tests.swift */, 42FC237025CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift */, 427F32DF25D1D62D00435294 /* BTPayPalClient_Tests.swift */, @@ -3014,7 +3014,7 @@ 427F32E025D1D62D00435294 /* BTPayPalClient_Tests.swift in Sources */, 42FC218B25CDE0290047C49A /* BTPayPalRequest_Tests.swift in Sources */, 42FC237125CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift in Sources */, - A977A04124FEC36F006049AB /* BTPaymentMethodNonceParser_PayPal_Tests.swift in Sources */, + BEDEAF112AC1D049004EA970 /* BTPayPalAccountNonce_Tests.swift in Sources */, 427F329025D1A7B900435294 /* BTPayPalVaultRequest_Tests.swift in Sources */, 3B7A261429C35BD00087059D /* BTPayPalAnalytics_Tests.swift in Sources */, A95229C724FD949D006F7D25 /* BTConfiguration+PayPal_Tests.swift in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 577440f2de..759ebfa750 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Braintree iOS SDK Release Notes +## unreleased +* BraintreeCore + * Fix bug where `type` was always returned as `Unknown` in `fetchPaymentMethodNonces` (fixes #1099) + ## 6.6.0 (2023-08-22) * BraintreePayPalNativeCheckout * Update PayPalCheckout from 1.0.0 to 1.1.0. diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index a18dcac813..8f858f265c 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -203,15 +203,20 @@ import Foundation /// Fetches a customer's vaulted payment method nonces. /// Must be using client token with a customer ID specified. /// - Parameter completion: Callback that returns either an array of payment method nonces or an error + /// - Note: Only the top level `BTPaymentMethodNonce` type is returned, fetching any additional details will need to be done on the server public func fetchPaymentMethodNonces(_ completion: @escaping ([BTPaymentMethodNonce]?, Error?) -> Void) { fetchPaymentMethodNonces(false, completion: completion) } + // NEXT_MAJOR_VERSION: this should move into the Drop-in for parity with Android + // This will also allow us to return the types directly which we were doing in the +load method + // previously in Obj-C - this is not available in Swift /// Fetches a customer's vaulted payment method nonces. /// Must be using client token with a customer ID specified. /// - Parameters: /// - defaultFirst: Specifies whether to sort the fetched payment method nonces with the default payment method or the most recently used payment method first /// - completion: Callback that returns either an array of payment method nonces or an error + /// - Note: Only the top level `BTPaymentMethodNonce` type is returned, fetching any additional details will need to be done on the server public func fetchPaymentMethodNonces(_ defaultFirst: Bool, completion: @escaping ([BTPaymentMethodNonce]?, Error?) -> Void) { if clientToken == nil { completion(nil, BTAPIClientError.notAuthorized) diff --git a/Sources/BraintreeCore/BTPaymentMethodNonceParser.swift b/Sources/BraintreeCore/BTPaymentMethodNonceParser.swift index 5f52134092..7268e8bd44 100644 --- a/Sources/BraintreeCore/BTPaymentMethodNonceParser.swift +++ b/Sources/BraintreeCore/BTPaymentMethodNonceParser.swift @@ -53,18 +53,80 @@ import Foundation return nil } - if let completionHandler = completionHandler { + if let completionHandler { return completionHandler(json) } - if json?["nonce"].isString != false { + if json?["nonce"].isString == false { + return nil + } + + let type = json?["type"].asString() + + if type == "CreditCard", let cardType = json?["details"]["cardType"].asString() { + return BTPaymentMethodNonce( + nonce: json?["nonce"].asString() ?? "", + type: self.cardType(from: cardType), + isDefault: json?["default"].isTrue ?? false + ) + } else if type == "ApplePayCard" { + return BTPaymentMethodNonce( + nonce: json?["nonce"].asString() ?? "", + type: json?["details"]["cardType"].asString() ?? "ApplePayCard", + isDefault: json?["default"].isTrue ?? false + ) + } else if type == "PayPalAccount" { + return BTPaymentMethodNonce( + nonce: json?["nonce"].asString() ?? "", + type: "PayPal", + isDefault: json?["default"].isTrue ?? false + ) + } else if type == "VenmoAccount" { + return BTPaymentMethodNonce( + nonce: json?["nonce"].asString() ?? "", + type: "Venmo", + isDefault: json?["default"].isTrue ?? false + ) + } else { return BTPaymentMethodNonce( nonce: json?["nonce"].asString() ?? "", type: "Unknown", isDefault: json?["default"].isTrue ?? false ) } + } + + private func cardType(from cardType: String) -> String { + let cardType = cardType.lowercased() + + if cardType == "american express" { + return "AMEX" + } else if cardType == "diners club" { + return "DinersClub" + } else if cardType == "unionpay" { + return "UnionPay" + } else if cardType == "discover" { + return "Discover" + } else if cardType == "mastercard" { + return "MasterCard" + } else if cardType == "jcb" { + return "JCB" + } else if cardType == "hiper" { + return "Hiper" + } else if cardType == "hipercard" { + return "Hipercard" + } else if cardType == "laser" { + return "Laser" + } else if cardType == "solo" { + return "Solo" + } else if cardType == "switch" { + return "Switch" + } else if cardType == "uk maestro" { + return "UKMaestro" + } else if cardType == "visa" { + return "Visa" + } - return nil + return "Unknown" } } diff --git a/UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift b/UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift index adaa7fdab3..3c77f71ed1 100644 --- a/UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift +++ b/UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift @@ -248,9 +248,11 @@ class BTAPIClient_Tests: XCTestCase { let firstNonce = paymentMethodNonces[0]; XCTAssertEqual(firstNonce.nonce, "fake-nonce1") + XCTAssertEqual(firstNonce.type, "AMEX") let secondNonce = paymentMethodNonces[1] XCTAssertEqual(secondNonce.nonce, "fake-nonce2") + XCTAssertEqual(secondNonce.type, "PayPal") expectation.fulfill() } diff --git a/UnitTests/BraintreeCoreTests/BTPaymentMethodNonceParser_Tests.swift b/UnitTests/BraintreeCoreTests/BTPaymentMethodNonceParser_Tests.swift index 741ea7471b..733fe12a8b 100644 --- a/UnitTests/BraintreeCoreTests/BTPaymentMethodNonceParser_Tests.swift +++ b/UnitTests/BraintreeCoreTests/BTPaymentMethodNonceParser_Tests.swift @@ -2,7 +2,7 @@ import XCTest @testable import BraintreeCore class BTPaymentMethodNonceParser_Tests: XCTestCase { - var parser : BTPaymentMethodNonceParser = BTPaymentMethodNonceParser() + var parser: BTPaymentMethodNonceParser = BTPaymentMethodNonceParser() func testRegisterType_addsTypeToTypes() { parser.registerType("MyType") { _ -> BTPaymentMethodNonce? in return nil} @@ -67,4 +67,86 @@ class BTPaymentMethodNonceParser_Tests: XCTestCase { XCTAssertEqual(unknownNonce.type, "Unknown") XCTAssertTrue(unknownNonce.isDefault) } + + func testSharedParser_whenTypeIsApplePayCard_returnsApplePayType() { + let sharedParser = BTPaymentMethodNonceParser.shared + let applePayCard = BTJSON(value: [ + "consumed": false, + "details": ["cardType": "American Express"], + "isLocked": false, + "nonce": "a-nonce", + "securityQuestions": [] as [Any], + "type": "ApplePayCard", + ] as [String: Any]) + + let applePayCardNonce = sharedParser.parseJSON(applePayCard, withParsingBlockForType: "ApplePayCard") + + XCTAssertEqual(applePayCardNonce?.nonce, "a-nonce") + XCTAssertEqual(applePayCardNonce?.type, "American Express") + } + + func testSharedParser_whenTypeIsCreditCard_returnsCardType() { + let sharedParser = BTPaymentMethodNonceParser.shared + + let creditCardJSON = BTJSON(value: [ + "consumed": false, + "description": "ending in 31", + "details": [ + "cardType": "American Express", + "lastTwo": "31", + ], + "isLocked": false, + "nonce": "0099b1d0-7a1c-44c3-b1e4-297082290bb9", + "securityQuestions": ["cvv"], + "threeDSecureInfo": NSNull(), + "type": "CreditCard", + "default": true + ] as [String: Any]) + + let cardNonce = sharedParser.parseJSON(creditCardJSON, withParsingBlockForType:"CreditCard")! + + XCTAssertEqual(cardNonce.nonce, "0099b1d0-7a1c-44c3-b1e4-297082290bb9") + XCTAssertEqual(cardNonce.type, "AMEX") + XCTAssertTrue(cardNonce.isDefault) + } + + func testSharedParser_whenTypeIsVenmo_returnsVenmoType() { + let sharedParser = BTPaymentMethodNonceParser.shared + + let venmoAccountJSON = BTJSON(value: [ + "consumed": false, + "description": "VenmoAccount", + "details": ["username": "jane.doe.username@example.com"], + "isLocked": false, + "nonce": "a-nonce", + "securityQuestions": [] as [Any], + "type": "VenmoAccount", + "default": true + ] as [String: Any]) + + let venmoAccountNonce = sharedParser.parseJSON(venmoAccountJSON, withParsingBlockForType: "VenmoAccount") + + XCTAssertEqual(venmoAccountNonce?.nonce, "a-nonce") + XCTAssertEqual(venmoAccountNonce?.type, "Venmo") + } + + func testSharedParser_whenTypeIsPayPal_returnsPayPalType() { + let sharedParser = BTPaymentMethodNonceParser.shared + + let payPalAccountJSON = BTJSON(value: [ + "consumed": false, + "description": "jane.doe@example.com", + "details": ["email": "jane.doe@example.com"], + "isLocked": false, + "nonce": "a-nonce", + "securityQuestions": [] as [Any], + "type": "PayPalAccount", + "default": true + ] as [String: Any]) + + let payPalAccountNonce = sharedParser.parseJSON(payPalAccountJSON, withParsingBlockForType: "PayPalAccount") + + XCTAssertEqual(payPalAccountNonce?.nonce, "a-nonce") + XCTAssertEqual(payPalAccountNonce?.type, "PayPal") + } } diff --git a/UnitTests/BraintreePayPalTests/BTPaymentMethodNonceParser_PayPal_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalAccountNonce_Tests.swift similarity index 80% rename from UnitTests/BraintreePayPalTests/BTPaymentMethodNonceParser_PayPal_Tests.swift rename to UnitTests/BraintreePayPalTests/BTPayPalAccountNonce_Tests.swift index 61ea9439ab..3605d52abe 100644 --- a/UnitTests/BraintreePayPalTests/BTPaymentMethodNonceParser_PayPal_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalAccountNonce_Tests.swift @@ -1,33 +1,9 @@ import XCTest @testable import BraintreePayPal -class BTPaymentMethodNonceParser_PayPal_Tests: XCTestCase { - func testSharedParser_whenTypeIsPayPal_returnsPayPalAccountNonce() { - let payPalAccountNonce = BTPayPalAccountNonce( - json: BTJSON( - value: [ - "consumed": false, - "description": "jane.doe@example.com", - "details": [ - "email": "jane.doe@example.com", - ], - "isLocked": false, - "nonce": "a-nonce", - "securityQuestions": [] as [Any?], - "type": "PayPalAccount", - "default": true - ] as [String: Any] - ) - ) - - XCTAssertEqual(payPalAccountNonce?.nonce, "a-nonce") - XCTAssertEqual(payPalAccountNonce?.type, "PayPal") - XCTAssertEqual(payPalAccountNonce?.email, "jane.doe@example.com") - XCTAssertTrue(payPalAccountNonce!.isDefault) - XCTAssertNil(payPalAccountNonce?.creditFinancing) - } +final class BTPayPalAccountNonce_Tests: XCTestCase { - func testParsePayPalCreditFinancingAmount() { + func testPayPalAccountNonce_returnsPayPalCreditFinancingAmount() { let payPalCreditFinancingAmount = BTJSON(value: [ "currency": "USD", "value": "123.45", @@ -41,7 +17,7 @@ class BTPaymentMethodNonceParser_PayPal_Tests: XCTestCase { XCTAssertEqual(amount.value, "123.45") } - func testParsePayPalCreditFinancing() { + func testPayPalAccountNonce_returnsPayPalCreditFinancing() { let payPalCreditFinancing = BTJSON(value: [ "cardAmountImmutable": false, "monthlyPayment": [ @@ -93,7 +69,7 @@ class BTPaymentMethodNonceParser_PayPal_Tests: XCTestCase { XCTAssertEqual(totalInterest.value, "456.78") } - func testSharedParser_whenTypeIsPayPal_returnsPayPalAccountNonceWithCreditFinancingOffered() { + func testPayPalAccountNonce_returnsPayPalAccountNonceWithCreditFinancingOffered() { let payPalAccountNonce = BTPayPalAccountNonce( json: BTJSON( value: [