diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index c34262234..1acd53266 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -202,6 +202,8 @@ A9E80A9F24FEF40C00196BD3 /* BraintreeTestShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A903E1A624F9D34000C314E1 /* BraintreeTestShared.framework */; }; A9E80AA324FEF4D800196BD3 /* MockLocalPaymentRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E80AA224FEF4D800196BD3 /* MockLocalPaymentRequestDelegate.swift */; }; B8BA342E2D4811560030423C /* BTContactInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BA342D2D4811560030423C /* BTContactInformation.swift */; }; + B8F870152D48435D000FCE3C /* CreditCardPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F870142D48435D000FCE3C /* CreditCardPOSTBody.swift */; }; + B8F870162D48435D000FCE3C /* CreditCardGraphQLBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F870132D48435D000FCE3C /* CreditCardGraphQLBody.swift */; }; BC17F9B428D23C5C004B18CC /* BTGraphQLMultiErrorNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC17F9B328D23C5C004B18CC /* BTGraphQLMultiErrorNode.swift */; }; BC17F9BC28D24C9E004B18CC /* BTGraphQLErrorTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC17F9BB28D24C9E004B18CC /* BTGraphQLErrorTree.swift */; }; BC17F9BE28D25054004B18CC /* BTGraphQLErrorNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC17F9BD28D25054004B18CC /* BTGraphQLErrorNode.swift */; }; @@ -898,6 +900,8 @@ A9E5C22824FD6D0800EE691F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A9E80AA224FEF4D800196BD3 /* MockLocalPaymentRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocalPaymentRequestDelegate.swift; sourceTree = ""; }; B8BA342D2D4811560030423C /* BTContactInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTContactInformation.swift; sourceTree = ""; }; + B8F870132D48435D000FCE3C /* CreditCardGraphQLBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardGraphQLBody.swift; sourceTree = ""; }; + B8F870142D48435D000FCE3C /* CreditCardPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardPOSTBody.swift; sourceTree = ""; }; BC17F9B328D23C5C004B18CC /* BTGraphQLMultiErrorNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTGraphQLMultiErrorNode.swift; sourceTree = ""; }; BC17F9BB28D24C9E004B18CC /* BTGraphQLErrorTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTGraphQLErrorTree.swift; sourceTree = ""; }; BC17F9BD28D25054004B18CC /* BTGraphQLErrorNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTGraphQLErrorNode.swift; sourceTree = ""; }; @@ -1756,6 +1760,8 @@ BEFE9A3C29C8ECAA00BF69AB /* BTCardError.swift */, BE80C00A29C66F4800793A6C /* BTCardNonce.swift */, BE80C00629C5516E00793A6C /* BTThreeDSecureInfo.swift */, + B8F870132D48435D000FCE3C /* CreditCardGraphQLBody.swift */, + B8F870142D48435D000FCE3C /* CreditCardPOSTBody.swift */, 806C85622B90EBED00A2754C /* PrivacyInfo.xcprivacy */, ); path = BraintreeCard; @@ -3311,6 +3317,8 @@ BE80C00B29C66F4800793A6C /* BTCardNonce.swift in Sources */, BE80C00D29C8B4B900793A6C /* BTCard.swift in Sources */, BE80C00529C54F2000793A6C /* BTAuthenticationInsight.swift in Sources */, + B8F870152D48435D000FCE3C /* CreditCardPOSTBody.swift in Sources */, + B8F870162D48435D000FCE3C /* CreditCardGraphQLBody.swift in Sources */, BE80C00729C5516E00793A6C /* BTThreeDSecureInfo.swift in Sources */, BEFE9A3D29C8ECAA00BF69AB /* BTCardError.swift in Sources */, BEFE9A3B29C8EC6B00BF69AB /* BTCardClient.swift in Sources */, diff --git a/Sources/BraintreeCard/BTCard.swift b/Sources/BraintreeCard/BTCard.swift index 70a7da941..328eb6d9c 100644 --- a/Sources/BraintreeCard/BTCard.swift +++ b/Sources/BraintreeCard/BTCard.swift @@ -1,5 +1,9 @@ import Foundation +#if canImport(BraintreeCore) +import BraintreeCore +#endif + /// The card tokenization request represents raw credit or debit card data provided by the customer. /// Its main purpose is to serve as the input for tokenization. @objcMembers public class BTCard: NSObject { @@ -116,181 +120,14 @@ import Foundation // MARK: - Internal Methods - func parameters() -> [String: Any] { - var cardDictionary: [String: Any] = buildCardDictionary(isGraphQL: false) - let billingAddressDictionary: [String: String] = buildBillingAddressDictionary(isGraphQL: false) - - if !billingAddressDictionary.isEmpty { - cardDictionary["billing_address"] = billingAddressDictionary - } - - let options: [String: Bool] = ["validate": shouldValidate] - cardDictionary["options"] = options - return cardDictionary - } - - func graphQLParameters() -> [String: Any] { - var cardDictionary: [String: Any] = buildCardDictionary(isGraphQL: true) - let billingAddressDictionary: [String: String] = buildBillingAddressDictionary(isGraphQL: true) - - if !billingAddressDictionary.isEmpty { - cardDictionary["billingAddress"] = billingAddressDictionary - } - - let options: [String: Bool] = ["validate": shouldValidate] - let inputDictionary: [String: Any] = ["creditCard": cardDictionary, "options": options] - var variables: [String: Any] = ["input": inputDictionary] - - if authenticationInsightRequested { - if let merchantAccountID { - variables["authenticationInsightInput"] = ["merchantAccountId": merchantAccountID] - } else { - variables["authenticationInsightInput"] = [:] - } - } - - return [ - "operationName": "TokenizeCreditCard", - "query": cardTokenizationGraphQLMutation(), - "variables": variables - ] - } - - // MARK: - Private Methods - - private func buildCardDictionary(isGraphQL: Bool) -> [String: Any] { - var cardDictionary: [String: Any] = [:] - - if !number.isEmpty { - cardDictionary["number"] = number - } - - if !expirationMonth.isEmpty { - cardDictionary[isGraphQL ? "expirationMonth" : "expiration_month"] = expirationMonth - } - - if !expirationYear.isEmpty { - cardDictionary[isGraphQL ? "expirationYear" : "expiration_year"] = expirationYear - } - - cardDictionary["cvv"] = cvv - - if let cardholderName { - cardDictionary[isGraphQL ? "cardholderName" : "cardholder_name"] = cardholderName - } - - return cardDictionary - } - - // swiftlint:disable cyclomatic_complexity - private func buildBillingAddressDictionary(isGraphQL: Bool) -> [String: String] { - var billingAddressDictionary: [String: String] = [:] - - if let firstName { - billingAddressDictionary[isGraphQL ? "firstName" : "first_name"] = firstName - } - - if let lastName { - billingAddressDictionary[isGraphQL ? "lastName" : "last_name"] = lastName - } - - if let company { - billingAddressDictionary["company"] = company - } - - if let postalCode { - billingAddressDictionary[isGraphQL ? "postalCode" : "postal_code"] = postalCode - } - - if let streetAddress { - billingAddressDictionary[isGraphQL ? "streetAddress" : "street_address"] = streetAddress - } - - if let extendedAddress { - billingAddressDictionary[isGraphQL ? "extendedAddress" : "extended_address"] = extendedAddress - } - - if let locality { - billingAddressDictionary["locality"] = locality - } - - if let region { - billingAddressDictionary["region"] = region - } - - if let countryName { - billingAddressDictionary[isGraphQL ? "countryName" : "country_name"] = countryName - } - - if let countryCodeAlpha2 { - billingAddressDictionary[isGraphQL ? "countryCodeAlpha2" : "country_code_alpha2"] = countryCodeAlpha2 - } - - if let countryCodeAlpha3 { - billingAddressDictionary[isGraphQL ? "countryCode" : "country_code_alpha3"] = countryCodeAlpha3 - } - - if let countryCodeNumeric { - billingAddressDictionary[isGraphQL ? "countryCodeNumeric" : "country_code_numeric"] = countryCodeNumeric - } - - return billingAddressDictionary - } - // swiftlint:enable cyclomatic_complexity - - private func cardTokenizationGraphQLMutation() -> String { - var mutation = "mutation TokenizeCreditCard($input: TokenizeCreditCardInput!" - - if authenticationInsightRequested { - mutation.append(", $authenticationInsightInput: AuthenticationInsightInput!") - } - - // swiftlint:disable indentation_width - mutation.append( - """ - ) { - tokenizeCreditCard(input: $input) { - token - creditCard { - brand - expirationMonth - expirationYear - cardholderName - last4 - bin - binData { - prepaid - healthcare - debit - durbinRegulated - commercial - payroll - issuingBank - countryOfIssuance - productId - } - } - """ + func parameters(apiClient: BTAPIClient) -> CreditCardPOSTBody { + CreditCardPOSTBody( + card: self, + metadata: apiClient.metadata ) - - if authenticationInsightRequested { - mutation.append( - """ - authenticationInsight(input: $authenticationInsightInput) { - customerAuthenticationRegulationEnvironment - } - """ - ) - } - - mutation.append( - """ - } - } - """ - ) - // swiftlint:enable indentation_width - - return mutation.replacingOccurrences(of: "\n", with: "") + } + + func graphQLParameters() -> CreditCardGraphQLBody { + CreditCardGraphQLBody(card: self) } } diff --git a/Sources/BraintreeCard/BTCardClient.swift b/Sources/BraintreeCard/BTCardClient.swift index 3ab9c6322..e728efd58 100644 --- a/Sources/BraintreeCard/BTCardClient.swift +++ b/Sources/BraintreeCard/BTCardClient.swift @@ -79,7 +79,7 @@ import BraintreeCore return } } else { - let parameters = self.clientAPIParameters(for: card) + let parameters = card.parameters(apiClient: self.apiClient) self.apiClient.post("v1/payment_methods/credit_cards", parameters: parameters) {body, _, error in if let error = error as NSError? { @@ -136,26 +136,6 @@ import BraintreeCore return false } - private func clientAPIParameters(for card: BTCard) -> [String: Any] { - var parameters: [String: Any] = [:] - parameters["credit_card"] = card.parameters() - - let metadata: [String: String] = [ - "source": apiClient.metadata.source.stringValue, - "integration": apiClient.metadata.integration.stringValue, - "sessionId": apiClient.metadata.sessionID - ] - - parameters["_meta"] = metadata - - if card.authenticationInsightRequested { - parameters["authenticationInsight"] = true - parameters["merchantAccountId"] = card.merchantAccountID - } - - return parameters - } - // MARK: - Error Construction Methods /// Convenience helper method for creating friendlier, more human-readable userInfo dictionaries for 422 HTTP errors diff --git a/Sources/BraintreeCard/CreditCardGraphQLBody.swift b/Sources/BraintreeCard/CreditCardGraphQLBody.swift new file mode 100644 index 000000000..0a1ebb5b6 --- /dev/null +++ b/Sources/BraintreeCard/CreditCardGraphQLBody.swift @@ -0,0 +1,176 @@ +// swiftlint:disable nesting + +import Foundation + +/// The POST body for graphQL API Credit Card Tokenize Post +struct CreditCardGraphQLBody: Encodable { + + var variables: Variables + var query: String + var operationName: String + + init(card: BTCard) { + self.variables = Variables(card: card) + self.query = Self.cardTokenizationGraphQLMutation(authenticationInsightRequested: card.authenticationInsightRequested) + self.operationName = "TokenizeCreditCard" + } + + struct Variables: Encodable { + + var input: Input + + init(card: BTCard) { + self.input = Input(card: card) + } + + struct Input: Encodable { + + var creditCard: CreditCard + var options: Options + var authenticationInsightInput: AuthenticationInsightInput? + + init(card: BTCard) { + self.creditCard = CreditCard(card: card) + self.options = Options(validate: card.shouldValidate) + + if card.authenticationInsightRequested { + self.authenticationInsightInput = AuthenticationInsightInput(card: card) + } + } + + struct CreditCard: Encodable { + + var billingAddress: BillingAddress? + var number: String? + var expirationMonth: String? + var cvv: String? + var expirationYear: String? + var cardholderName: String? + + init(card: BTCard) { + self.billingAddress = BillingAddress(card: card) + self.number = card.number + self.expirationMonth = card.expirationMonth + self.cvv = card.cvv + self.expirationYear = card.expirationYear + self.cardholderName = card.cardholderName + } + + struct BillingAddress: Encodable { + + var firstName: String? + var lastName: String? + var company: String? + var postalCode: String? + var streetAddress: String? + var extendedAddress: String? + var locality: String? + var region: String? + var countryName: String? + var countryCodeAlpha2: String? + var countryCodeAlpha3: String? + var countryCodeNumeric: String? + + init?(card: BTCard) { + let properties = [ + card.firstName, card.lastName, card.company, card.postalCode, + card.streetAddress, card.extendedAddress, card.locality, card.region, + card.countryName, card.countryCodeAlpha2, card.countryCodeAlpha3, card.countryCodeNumeric + ] + + if properties.allSatisfy({ $0?.isEmpty ?? true }) { + return nil + } + + self.firstName = card.firstName + self.lastName = card.lastName + self.company = card.company + self.postalCode = card.postalCode + self.streetAddress = card.streetAddress + self.extendedAddress = card.extendedAddress + self.locality = card.locality + self.region = card.region + self.countryName = card.countryName + self.countryCodeAlpha2 = card.countryCodeAlpha2 + self.countryCodeAlpha3 = card.countryCodeAlpha3 + self.countryCodeNumeric = card.countryCodeNumeric + } + } + } + + struct AuthenticationInsightInput: Encodable { + + var merchantAccountID: String? + + init(card: BTCard) { + self.merchantAccountID = card.merchantAccountID + } + + enum CodingKeys: String, CodingKey { + case merchantAccountID = "merchantAccountId" + } + } + + struct Options: Encodable { + + var validate: Bool + } + } + } + + static func cardTokenizationGraphQLMutation(authenticationInsightRequested: Bool) -> String { + var mutation = "mutation TokenizeCreditCard($input: TokenizeCreditCardInput!" + + if authenticationInsightRequested { + mutation.append(", $authenticationInsightInput: AuthenticationInsightInput!") + } + + // swiftlint:disable indentation_width + mutation.append( + """ + ) { + tokenizeCreditCard(input: $input) { + token + creditCard { + brand + expirationMonth + expirationYear + cardholderName + last4 + bin + binData { + prepaid + healthcare + debit + durbinRegulated + commercial + payroll + issuingBank + countryOfIssuance + productId + } + } + """ + ) + + if authenticationInsightRequested { + mutation.append( + """ + authenticationInsight(input: $authenticationInsightInput) { + customerAuthenticationRegulationEnvironment + } + """ + ) + } + + mutation.append( + """ + } + } + """ + ) + // swiftlint:enable indentation_width + + return mutation.replacingOccurrences(of: "\n", with: "") + } +} diff --git a/Sources/BraintreeCard/CreditCardPOSTBody.swift b/Sources/BraintreeCard/CreditCardPOSTBody.swift new file mode 100644 index 000000000..314c9515c --- /dev/null +++ b/Sources/BraintreeCard/CreditCardPOSTBody.swift @@ -0,0 +1,139 @@ +// swiftlint:disable nesting + +import Foundation + +#if canImport(BraintreeCore) +import BraintreeCore +#endif + +// swiftlint:disable nesting +/// The POST body for "v1/payment_methods/credit_cards" +struct CreditCardPOSTBody: Encodable { + + var authenticationInsight: Bool? + var merchantAccountID: String? + var meta: Meta? + let creditCard: CreditCard? + + enum CodingKeys: String, CodingKey { + case authenticationInsight + case meta = "_meta" + case merchantAccountID = "merchantAccountId" + case creditCard = "credit_card" + } + + init( + card: BTCard, + metadata: BTClientMetadata + ) { + self.creditCard = CreditCard(card: card) + + if card.authenticationInsightRequested { + self.authenticationInsight = card.authenticationInsightRequested + self.merchantAccountID = card.merchantAccountID + } + + self.meta = Meta(metadata: metadata) + } + + struct Meta: Encodable { + + var integration: String + var source: String + var sessionID: String + + init(metadata: BTClientMetadata) { + self.integration = metadata.integration.stringValue + self.source = metadata.source.stringValue + self.sessionID = metadata.sessionID + } + + enum CodingKeys: String, CodingKey { + case integration + case source + case sessionID = "sessionId" + } + } + + struct CreditCard: Encodable { + + let billingAddress: BillingAddress? + let number: String? + let expirationMonth: String? + let cvv: String? + let options: Options? + let expirationYear: String? + let cardHolderName: String? + + init(card: BTCard) { + self.billingAddress = BillingAddress(card: card) + self.number = card.number + self.cvv = card.cvv + self.options = Options(validate: card.shouldValidate) + self.expirationMonth = card.expirationMonth + self.expirationYear = card.expirationYear + self.cardHolderName = card.cardholderName + } + + enum CodingKeys: String, CodingKey { + case billingAddress = "billing_address" + case number + case expirationMonth = "expiration_month" + case cvv + case options + case expirationYear = "expiration_year" + case cardHolderName = "cardholder_name" + } + + struct BillingAddress: Encodable { + + let firstName: String? + let lastName: String? + let company: String? + let postalCode: String? + let streetAddress: String? + let extendedAddress: String? + let locality: String? + let region: String? + let countryName: String? + let countryCodeAlpha2: String? + let countryCodeAlpha3: String? + let countryCodeNumeric: String? + + init(card: BTCard) { + self.firstName = card.firstName + self.lastName = card.lastName + self.company = card.company + self.postalCode = card.postalCode + self.streetAddress = card.streetAddress + self.extendedAddress = card.extendedAddress + self.locality = card.locality + self.region = card.region + self.countryName = card.countryName + self.countryCodeAlpha2 = card.countryCodeAlpha2 + self.countryCodeAlpha3 = card.countryCodeAlpha3 + self.countryCodeNumeric = card.countryCodeNumeric + } + + enum CodingKeys: String, CodingKey { + case firstName = "first_name" + case lastName = "last_name" + case company + case postalCode = "postal_code" + case streetAddress = "street_address" + case extendedAddress = "extended_address" + case locality + case region + case countryName = "country_name" + case countryCodeAlpha2 = "country_code_alpha2" + case countryCodeAlpha3 = "country_code_alpha3" + case countryCodeNumeric = "country_code_numeric" + } + } + + struct Options: Encodable { + + let validate: Bool + } + } +} diff --git a/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift index 1b680436d..bf64a6926 100644 --- a/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift +++ b/Sources/BraintreePayPal/Models/PayPalCheckoutPOSTBody.swift @@ -39,7 +39,7 @@ struct PayPalCheckoutPOSTBody: Encodable { private var recipientName: String? // MARK: - Initializer - + // swiftlint:disable:next cyclomatic_complexity init(payPalRequest: BTPayPalCheckoutRequest, configuration: BTConfiguration) { self.amount = payPalRequest.amount diff --git a/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift b/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift index 090085196..5d6b32edc 100644 --- a/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift +++ b/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift @@ -530,7 +530,11 @@ class BTCardClient_Tests: XCTestCase { return } lastPostParameters.removeValue(forKey: "clientSdkMetadata") - XCTAssertEqual(lastPostParameters as NSObject, card.graphQLParameters() as NSObject) + + let dictionary = try! card.graphQLParameters().toDictionary() + + XCTAssertEqual(lastPostParameters as NSObject, dictionary as NSObject) + expectation.fulfill() } @@ -554,9 +558,19 @@ class BTCardClient_Tests: XCTestCase { cardClient.tokenize(card) { (tokenizedCard, error) -> Void in XCTAssertTrue(mockApiClient.lastPOSTAPIClientHTTPType! == BTAPIClientHTTPService.gateway) + guard var lastPostParameters = mockApiClient.lastPOSTParameters else { + XCTFail() + return + } + lastPostParameters.removeValue(forKey: "clientSdkMetadata") + + let dictionary = try! card.parameters(apiClient: mockApiClient).toDictionary() + + XCTAssertEqual(lastPostParameters as NSObject, dictionary as NSObject) + expectation.fulfill() } - + waitForExpectations(timeout: 10, handler: nil) } diff --git a/UnitTests/BraintreeCardTests/BTCard_Tests.swift b/UnitTests/BraintreeCardTests/BTCard_Tests.swift index edb6c051d..9bf4d55f6 100644 --- a/UnitTests/BraintreeCardTests/BTCard_Tests.swift +++ b/UnitTests/BraintreeCardTests/BTCard_Tests.swift @@ -5,7 +5,7 @@ class BTCard_Tests: XCTestCase { func testInitialization_withoutParameters() { let card = BTCard( - number: "4111111111111111", + number: "4111111111111111", expirationMonth: "12", expirationYear: "2038", cvv: "123" @@ -42,32 +42,27 @@ class BTCard_Tests: XCTestCase { shouldValidate: true ) - let expectedParameters: [String : Any] = [ - "number": "4111111111111111", - "expiration_month": "12", - "expiration_year": "2038", - "cardholder_name": "Brian Tree", - "cvv": "123", - "billing_address": [ - "first_name": "Brian", - "last_name": "Tree", - "company": "Braintree", - "postal_code": "11111", - "street_address": "123 Main St.", - "extended_address": "Apt 2", - "locality": "Chicago", - "region": "IL", - "country_name": "US", - "country_code_alpha2": "US", - "country_code_alpha3": "USA", - "country_code_numeric": "123", - ], - "options": [ - "validate": 1 - ] - ] - - XCTAssertEqual(card.parameters() as NSObject, expectedParameters as NSObject) + + let params = card.graphQLParameters() + + XCTAssertEqual(params.variables.input.creditCard.number, "4111111111111111") + XCTAssertEqual(params.variables.input.creditCard.expirationMonth, "12") + XCTAssertEqual(params.variables.input.creditCard.expirationYear, "2038") + XCTAssertEqual(params.variables.input.creditCard.cvv, "123") + XCTAssertEqual(params.variables.input.creditCard.cardholderName, "Brian Tree") + XCTAssertEqual(params.variables.input.creditCard.billingAddress?.firstName, "Brian") + XCTAssertEqual(params.variables.input.creditCard.billingAddress?.lastName, "Tree") + XCTAssertEqual(params.variables.input.creditCard.billingAddress?.company, "Braintree") + XCTAssertEqual(params.variables.input.creditCard.billingAddress?.postalCode, "11111") + XCTAssertEqual(params.variables.input.creditCard.billingAddress?.streetAddress, "123 Main St.") + XCTAssertEqual(params.variables.input.creditCard.billingAddress?.extendedAddress, "Apt 2") + XCTAssertEqual(params.variables.input.creditCard.billingAddress?.locality, "Chicago") + XCTAssertEqual(params.variables.input.creditCard.billingAddress?.region, "IL") + XCTAssertEqual(params.variables.input.creditCard.billingAddress?.countryName, "US") + XCTAssertEqual(params.variables.input.creditCard.billingAddress?.countryCodeAlpha2, "US") + XCTAssertEqual(params.variables.input.creditCard.billingAddress?.countryCodeAlpha3, "USA") + XCTAssertEqual(params.variables.input.creditCard.billingAddress?.countryCodeNumeric, "123") + XCTAssertEqual(params.variables.input.options.validate, card.shouldValidate) } // MARK: - graphQLParameters @@ -151,56 +146,31 @@ class BTCard_Tests: XCTestCase { shouldValidate: false ) - XCTAssertEqual(card.graphQLParameters() as NSObject, [ - "operationName": "TokenizeCreditCard", - "query": graphQLQuery, - "variables": [ - "input": [ - "creditCard": [ - "cardholderName": "Brian Tree", - "number": "4111111111111111", - "expirationMonth": "12", - "expirationYear": "20", - "cvv": "123", - "billingAddress": [ - "firstName": "Joe", - "lastName": "Smith", - "company": "Company", - "streetAddress": "123 Townsend St", - "extendedAddress": "Unit 1", - "locality": "San Francisco", - "region": "CA", - "countryName": "United States of America", - "countryCodeAlpha2": "US", - "countryCode": "USA", - "countryCodeNumeric": "123", - "postalCode": "94107" - ], - ] as [String: Any], - "options": ["validate": false] - ] - ] - ] as [String: Any] as NSObject) + let params = card.graphQLParameters() + + XCTAssertEqual(params.variables.input.options.validate, false) + XCTAssertNotNil(params.query) } - + func testGraphQLParameters_whenDoingCVVOnly_returnsExpectedValue() { - let card = BTCard(cvv: "123") + let card = BTCard(cvv: "321") - XCTAssertEqual(card.graphQLParameters() as NSObject, [ - "operationName": "TokenizeCreditCard", - "query": graphQLQuery, - "variables": [ - "input": [ - "creditCard": ["cvv": "123"] as [String: String], - "options": ["validate": false] - ] as [String: Any] - ] - ] as [String: Any] as NSObject) + let params = card.graphQLParameters() + + XCTAssertEqual(params.variables.input.creditCard.cvv, "321") + XCTAssertEqual(params.operationName, "TokenizeCreditCard") + XCTAssertNotNil(params.query) + XCTAssertEqual(params.variables.input.options.validate, false) + + + XCTAssertEqual(params.variables.input.creditCard.number, "") + XCTAssertNil(params.variables.input.creditCard.billingAddress?.firstName) + XCTAssertNil(params.variables.input.creditCard.cardholderName) } func testGraphQLParameters_whenMerchantAccountIDIsPresent_andAuthInsightRequestedIsTrue_requestsAuthInsight() { let card = BTCard( - number: "4111111111111111", + number: "5111111111111111", expirationMonth: "12", expirationYear: "2038", cvv: "1234", @@ -208,54 +178,40 @@ class BTCard_Tests: XCTestCase { merchantAccountID: "some id" ) - XCTAssertEqual(card.graphQLParameters() as NSObject, [ - "operationName": "TokenizeCreditCard", - "query": graphQLQueryWithAuthInsightRequested, - "variables": [ - "input": [ - "creditCard": [ - "cvv": "1234", - "expirationMonth": "12", - "expirationYear": "2038", - "number": "4111111111111111", - ], - "options": [ "validate": false ], - ] as [String: Any], - "authenticationInsightInput": [ - "merchantAccountId": "some id" - ] - ] - ] as [String: Any] as NSObject) + let params = card.graphQLParameters() + + XCTAssertEqual(params.query, graphQLQueryWithAuthInsightRequested) + XCTAssertEqual(params.variables.input.creditCard.number, "5111111111111111") + XCTAssertEqual(params.variables.input.options.validate, false) + XCTAssertEqual(params.variables.input.authenticationInsightInput?.merchantAccountID, "some id") + + XCTAssertNil(params.variables.input.creditCard.billingAddress?.firstName) + XCTAssertNil(params.variables.input.creditCard.cardholderName) } func testGraphQLParameters_whenMerchantAccountIDIsPresent_andAuthInsightRequestedIsFalse_doesNotRequestAuthInsight() { let card = BTCard( - number: "4111111111111111", + number: "6111111111111111", expirationMonth: "12", expirationYear: "2038", cvv: "1234", authenticationInsightRequested: false, merchantAccountID: "some id" ) + + let params = card.graphQLParameters() + + XCTAssertEqual(params.variables.input.creditCard.number, "6111111111111111") + XCTAssertEqual(params.operationName, "TokenizeCreditCard") + XCTAssertNotNil(params.query) + XCTAssertEqual(params.variables.input.options.validate, false) - XCTAssertEqual(card.graphQLParameters() as NSObject, [ - "operationName": "TokenizeCreditCard", - "query": graphQLQuery, - "variables": [ - "input": [ - "creditCard": ["number": "4111111111111111", - "cvv": "1234", - "expirationMonth": "12", - "expirationYear": "2038"] as [String: String], - "options": ["validate": false], - ] as [String: Any] - ] - ] as [String: Any] as NSObject) + XCTAssertNil(params.variables.input.authenticationInsightInput?.merchantAccountID, "some id") } func testGraphQLParameters_whenMerchantAccountIDIsNil_andAuthInsightRequestedIsTrue_requestsAuthInsight() { let card = BTCard( - number: "4111111111111111", + number: "7111111111111111", expirationMonth: "12", expirationYear: "2038", cvv: "1234", @@ -263,27 +219,32 @@ class BTCard_Tests: XCTestCase { merchantAccountID: nil ) - XCTAssertEqual(card.graphQLParameters() as NSObject, [ - "operationName": "TokenizeCreditCard", - "query": graphQLQueryWithAuthInsightRequested, - "variables": [ - "input": [ - "creditCard": [ - "cvv": "1234", - "expirationMonth": "12", - "expirationYear": "2038", - "number": "4111111111111111", - ], - "options": [ "validate": false ], - ], - "authenticationInsightInput": NSDictionary() - ] - ] as [String: Any] as NSObject) + let params = card.graphQLParameters() + + XCTAssertEqual(params.variables.input.creditCard.number, "7111111111111111") + XCTAssertEqual(params.operationName, "TokenizeCreditCard") + XCTAssertNotNil(params.query) + XCTAssertEqual(params.variables.input.options.validate, false) + + XCTAssertNotNil(params.variables.input.authenticationInsightInput) + } + + func printEncodableObject(_ object: T) { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted // Makes the JSON easier to read + do { + let jsonData = try encoder.encode(object) + if let jsonString = String(data: jsonData, encoding: .utf8) { + print("Encoded JSON:\n\(jsonString)") + } + } catch { + print("Failed to encode object: \(error)") + } } func testGraphQLParameters_whenMerchantAccountIDIsNil_andAuthInsightRequestedIsFalse_doesNotRequestAuthInsight() { let card = BTCard( - number: "4111111111111111", + number: "8111111111111111", expirationMonth: "12", expirationYear: "2038", cvv: "123", @@ -291,18 +252,14 @@ class BTCard_Tests: XCTestCase { merchantAccountID: nil ) - XCTAssertEqual(card.graphQLParameters() as NSObject, [ - "operationName": "TokenizeCreditCard", - "query": graphQLQuery, - "variables": [ - "input": [ - "creditCard": ["number": "4111111111111111", - "cvv": "123", - "expirationMonth": "12", - "expirationYear": "2038"] as [String: String], - "options": [ "validate": false ], - ] as [String: Any] - ] - ] as [String: Any] as NSObject) + let params = card.graphQLParameters() + + XCTAssertEqual(params.variables.input.creditCard.number, "8111111111111111") + XCTAssertEqual(params.operationName, "TokenizeCreditCard") + XCTAssertNotNil(params.query) + XCTAssertEqual(params.variables.input.options.validate, false) + + XCTAssertNil(params.variables.input.authenticationInsightInput) + XCTAssertNil(params.variables.input.authenticationInsightInput?.merchantAccountID) } } diff --git a/UnitTests/BraintreeTestShared/MockAPIClient.swift b/UnitTests/BraintreeTestShared/MockAPIClient.swift index ae9127a30..b2b77a707 100644 --- a/UnitTests/BraintreeTestShared/MockAPIClient.swift +++ b/UnitTests/BraintreeTestShared/MockAPIClient.swift @@ -54,7 +54,7 @@ public class MockAPIClient: BTAPIClient { public override func post(_ path: String, parameters: Encodable, headers: [String: String]? = nil, httpType: BTAPIClientHTTPService = .gateway, completion completionBlock: ((BTJSON?, HTTPURLResponse?, Error?) -> Void)? = nil) { lastPOSTPath = path - lastPOSTParameters = try? parameters.toDictionary() + lastPOSTParameters = try? parameters.toDictionary() lastPOSTAPIClientHTTPType = httpType lastPOSTAdditionalHeaders = headers