From 096741e47e122087d87cd2e44fddeeac48854867 Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 22 Oct 2024 11:31:28 -0600 Subject: [PATCH 01/26] Include all properties in public init --- Sources/BraintreeCard/BTCard.swift | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Sources/BraintreeCard/BTCard.swift b/Sources/BraintreeCard/BTCard.swift index 420dd4fdfd..f54bf9fd7b 100644 --- a/Sources/BraintreeCard/BTCard.swift +++ b/Sources/BraintreeCard/BTCard.swift @@ -78,6 +78,50 @@ import Foundation /// Optional: The merchant account ID. public var merchantAccountID: String? + public init( + number: String? = nil, + expirationMonth: String? = nil, + expirationYear: String? = nil, + cvv: String? = nil, + postalCode: String? = nil, + cardholderName: String? = nil, + firstName: String? = nil, + lastName: String? = nil, + company: String? = nil, + streetAddress: String? = nil, + extendedAddress: String? = nil, + locality: String? = nil, + region: String? = nil, + countryName: String? = nil, + countryCodeAlpha2: String? = nil, + countryCodeAlpha3: String? = nil, + countryCodeNumeric: String? = nil, + shouldValidate: Bool = false, + authenticationInsightRequested: Bool = false, + merchantAccountID: String? = nil + ) { + self.number = number + self.expirationMonth = expirationMonth + self.expirationYear = expirationYear + self.cvv = cvv + self.postalCode = postalCode + self.cardholderName = cardholderName + self.firstName = firstName + self.lastName = lastName + self.company = company + self.streetAddress = streetAddress + self.extendedAddress = extendedAddress + self.locality = locality + self.region = region + self.countryName = countryName + self.countryCodeAlpha2 = countryCodeAlpha2 + self.countryCodeAlpha3 = countryCodeAlpha3 + self.countryCodeNumeric = countryCodeNumeric + self.shouldValidate = shouldValidate + self.authenticationInsightRequested = authenticationInsightRequested + self.merchantAccountID = merchantAccountID + } + // MARK: - Internal Methods func parameters() -> [String: Any] { From d4db0c9e73c362631d16629746e7f2767f51d4ac Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 22 Oct 2024 11:33:59 -0600 Subject: [PATCH 02/26] Make all properties internal --- Sources/BraintreeCard/BTCard.swift | 42 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Sources/BraintreeCard/BTCard.swift b/Sources/BraintreeCard/BTCard.swift index f54bf9fd7b..49c92df8f9 100644 --- a/Sources/BraintreeCard/BTCard.swift +++ b/Sources/BraintreeCard/BTCard.swift @@ -4,79 +4,79 @@ import Foundation /// Its main purpose is to serve as the input for tokenization. @objcMembers public class BTCard: NSObject { - // MARK: - Public Properties + // MARK: - Internal Properties /// The card number - public var number: String? + let number: String? /// The expiration month as a one or two-digit number on the Gregorian calendar - public var expirationMonth: String? + let expirationMonth: String? /// The expiration year as a two or four-digit number on the Gregorian calendar - public var expirationYear: String? + let expirationYear: String? /// The card verification code (like CVV or CID). /// - Note: If you wish to create a CVV-only payment method nonce to verify a card already stored in your Vault, /// omit all other properties to only collect CVV. - public var cvv: String? + let cvv: String? /// The postal code associated with the card's billing address - public var postalCode: String? + let postalCode: String? /// Optional: the cardholder's name. - public var cardholderName: String? + let cardholderName: String? /// Optional: first name on the card. - public var firstName: String? + let firstName: String? /// Optional: last name on the card. - public var lastName: String? + let lastName: String? /// Optional: company name associated with the card. - public var company: String? + let company: String? /// Optional: the street address associated with the card's billing address - public var streetAddress: String? + let streetAddress: String? /// Optional: the extended address associated with the card's billing address - public var extendedAddress: String? + let extendedAddress: String? /// Optional: the city associated with the card's billing address - public var locality: String? + let locality: String? /// Optional: 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? + let region: String? /// Optional: the country name associated with the card's billing address. /// - Note: Braintree only accepts specific country names. /// - SeeAlso: https://developer.paypal.com/braintree/docs/reference/general/countries#list-of-countries - public var countryName: String? + let countryName: String? /// Optional: the ISO 3166-1 alpha-2 country code specified in the card's billing address. /// - Note: Braintree only accepts specific alpha-2 values. /// - SeeAlso: https://developer.paypal.com/braintree/docs/reference/general/countries#list-of-countries - public var countryCodeAlpha2: String? + let countryCodeAlpha2: String? /// Optional: the ISO 3166-1 alpha-3 country code specified in the card's billing address. /// - Note: Braintree only accepts specific alpha-3 values. /// - SeeAlso: https://developer.paypal.com/braintree/docs/reference/general/countries#list-of-countries - public var countryCodeAlpha3: String? + let countryCodeAlpha3: String? /// Optional: The ISO 3166-1 numeric country code specified in the card's billing address. /// - Note: Braintree only accepts specific numeric values. /// - SeeAlso: https://developer.paypal.com/braintree/docs/reference/general/countries#list-of-countries - public var countryCodeNumeric: String? + let countryCodeNumeric: String? /// Controls whether or not to return validations and/or verification results. By default, this is not enabled. /// - Note: Use this flag with caution. By enabling client-side validation, certain tokenize card requests may result in adding the card to the vault. /// These semantics are not currently documented. - public var shouldValidate: Bool = false + var shouldValidate: Bool = false /// Optional: If authentication insight is requested. If this property is set to true, a `merchantAccountID` must be provided. Defaults to false. - public var authenticationInsightRequested: Bool = false + var authenticationInsightRequested: Bool = false /// Optional: The merchant account ID. - public var merchantAccountID: String? + let merchantAccountID: String? public init( number: String? = nil, From 56f49c80a749df5dc7aa4a23c6d2082f00e9bfaf Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 22 Oct 2024 11:36:03 -0600 Subject: [PATCH 03/26] Update BTCard tests --- .../BraintreeCardTests/BTCard_Tests.swift | 129 +++++++++--------- 1 file changed, 67 insertions(+), 62 deletions(-) diff --git a/UnitTests/BraintreeCardTests/BTCard_Tests.swift b/UnitTests/BraintreeCardTests/BTCard_Tests.swift index 74ffad696c..d6dda7015c 100644 --- a/UnitTests/BraintreeCardTests/BTCard_Tests.swift +++ b/UnitTests/BraintreeCardTests/BTCard_Tests.swift @@ -4,12 +4,12 @@ import XCTest class BTCard_Tests: XCTestCase { func testInitialization_withoutParameters() { - let card = BTCard() - - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "123" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "123" + ) XCTAssertEqual(card.number, "4111111111111111") XCTAssertEqual(card.expirationMonth, "12") @@ -21,25 +21,26 @@ class BTCard_Tests: XCTestCase { // MARK: - Non-GraphQL Parameters func testParameters_setsAllParameters() { - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "123" - card.cardholderName = "Brian Tree" - card.firstName = "Brian" - card.lastName = "Tree" - card.company = "Braintree" - card.postalCode = "11111" - card.streetAddress = "123 Main St." - card.extendedAddress = "Apt 2" - card.locality = "Chicago" - card.region = "IL" - card.countryName = "US" - card.countryCodeAlpha2 = "US" - card.countryCodeAlpha3 = "USA" - card.countryCodeNumeric = "123" - card.shouldValidate = true + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "123", + postalCode: "11111", + cardholderName: "Brian Tree", + firstName: "Brian", + lastName: "Tree", + company: "Braintree", + streetAddress: "123 Main St.", + extendedAddress: "Apt 2", + locality: "Chicago", + region: "IL", + countryName: "US", + countryCodeAlpha2: "US", + countryCodeAlpha3: "USA", + countryCodeNumeric: "123", + shouldValidate: true + ) let expectedParameters: [String : Any] = [ "number": "4111111111111111", @@ -129,25 +130,26 @@ class BTCard_Tests: XCTestCase { """ func testGraphQLParameters_whenInitializedWithInitWithParameters_returnsExpectedValues() { - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "20" - card.cvv = "123" - card.cardholderName = "Brian Tree" - card.firstName = "Joe" - card.lastName = "Smith" - card.company = "Company" - card.postalCode = "94107" - card.streetAddress = "123 Townsend St" - card.extendedAddress = "Unit 1" - card.locality = "San Francisco" - card.region = "CA" - card.countryName = "United States of America" - card.countryCodeAlpha2 = "US" - card.countryCodeAlpha3 = "USA" - card.countryCodeNumeric = "123" - card.shouldValidate = false + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "20", + cvv: "123", + postalCode: "94107", + cardholderName: "Brian Tree", + 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", + countryCodeAlpha3: "USA", + countryCodeNumeric: "123", + shouldValidate: false + ) XCTAssertEqual(card.graphQLParameters() as NSObject, [ "operationName": "TokenizeCreditCard", @@ -182,8 +184,7 @@ class BTCard_Tests: XCTestCase { } func testGraphQLParameters_whenDoingCVVOnly_returnsExpectedValue() { - let card = BTCard() - card.cvv = "123" + let card = BTCard(cvv: "123") XCTAssertEqual(card.graphQLParameters() as NSObject, [ "operationName": "TokenizeCreditCard", @@ -198,10 +199,11 @@ class BTCard_Tests: XCTestCase { } func testGraphQLParameters_whenMerchantAccountIDIsPresent_andAuthInsightRequestedIsTrue_requestsAuthInsight() { - let card = BTCard() - card.number = "4111111111111111" - card.authenticationInsightRequested = true - card.merchantAccountID = "some id" + let card = BTCard( + number: "4111111111111111", + authenticationInsightRequested: true, + merchantAccountID: "some id" + ) XCTAssertEqual(card.graphQLParameters() as NSObject, [ "operationName": "TokenizeCreditCard", @@ -221,10 +223,11 @@ class BTCard_Tests: XCTestCase { } func testGraphQLParameters_whenMerchantAccountIDIsPresent_andAuthInsightRequestedIsFalse_doesNotRequestAuthInsight() { - let card = BTCard() - card.number = "4111111111111111" - card.authenticationInsightRequested = false - card.merchantAccountID = "some id" + let card = BTCard( + number: "4111111111111111", + authenticationInsightRequested: false, + merchantAccountID: "some id" + ) XCTAssertEqual(card.graphQLParameters() as NSObject, [ "operationName": "TokenizeCreditCard", @@ -239,10 +242,11 @@ class BTCard_Tests: XCTestCase { } func testGraphQLParameters_whenMerchantAccountIDIsNil_andAuthInsightRequestedIsTrue_requestsAuthInsight() { - let card = BTCard() - card.number = "4111111111111111" - card.authenticationInsightRequested = true - card.merchantAccountID = nil + let card = BTCard( + number: "4111111111111111", + authenticationInsightRequested: true, + merchantAccountID: nil + ) XCTAssertEqual(card.graphQLParameters() as NSObject, [ "operationName": "TokenizeCreditCard", @@ -260,10 +264,11 @@ class BTCard_Tests: XCTestCase { } func testGraphQLParameters_whenMerchantAccountIDIsNil_andAuthInsightRequestedIsFalse_doesNotRequestAuthInsight() { - let card = BTCard() - card.number = "4111111111111111" - card.authenticationInsightRequested = false - card.merchantAccountID = nil + let card = BTCard( + number: "4111111111111111", + authenticationInsightRequested: false, + merchantAccountID: nil + ) XCTAssertEqual(card.graphQLParameters() as NSObject, [ "operationName": "TokenizeCreditCard", From 862763a58021e05a66ea91f56c5ee7d740fa5b88 Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 22 Oct 2024 11:39:12 -0600 Subject: [PATCH 04/26] Update BTVardClient tests --- .../BTCardClient_Tests.swift | 205 ++++++++++-------- 1 file changed, 112 insertions(+), 93 deletions(-) diff --git a/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift b/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift index 2024e8383b..60b8ce0ed2 100644 --- a/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift +++ b/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift @@ -15,14 +15,15 @@ class BTCardClient_Tests: XCTestCase { let cardClient = BTCardClient(apiClient: mockAPIClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "1234" - card.cardholderName = "Brian Tree" - card.authenticationInsightRequested = true - card.merchantAccountID = "some merchant account id" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234", + cardholderName: "Brian Tree", + authenticationInsightRequested: true, + merchantAccountID: "some merchant account id" + ) cardClient.tokenize(card) { (tokenizedCard, error) -> Void in @@ -58,12 +59,14 @@ class BTCardClient_Tests: XCTestCase { let cardClient = BTCardClient(apiClient: mockAPIClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "1234" - card.authenticationInsightRequested = false + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234", + cardholderName: "Brian Tree", + authenticationInsightRequested: false + ) cardClient.tokenize(card) { (tokenizedCard, error) -> Void in XCTAssertEqual(mockAPIClient.lastPOSTPath, "v1/payment_methods/credit_cards") @@ -103,10 +106,11 @@ class BTCardClient_Tests: XCTestCase { let cardClient = BTCardClient(apiClient: mockAPIClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038" + ) cardClient.tokenize(card) { (tokenizedCard, error) -> Void in guard let tokenizedCard = tokenizedCard else { @@ -134,10 +138,11 @@ class BTCardClient_Tests: XCTestCase { let cardClient = BTCardClient(apiClient: mockAPIClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038" + ) cardClient.tokenize(card) { (tokenizedCard, error) -> Void in XCTAssertNil(tokenizedCard) @@ -177,11 +182,12 @@ class BTCardClient_Tests: XCTestCase { stubAPIClient.cannedResponseError = stubError let cardClient = BTCardClient(apiClient: stubAPIClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "123" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "123" + ) let expectation = self.expectation(description: "Callback invoked with error") cardClient.tokenize(card) { (cardNonce, error) -> Void in @@ -233,11 +239,12 @@ class BTCardClient_Tests: XCTestCase { stubAPIClient.cannedResponseError = stubError let cardClient = BTCardClient(apiClient: stubAPIClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "123" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "123" + ) let expectation = self.expectation(description: "Callback invoked with error") cardClient.tokenize(card) { (cardNonce, error) -> Void in @@ -310,11 +317,12 @@ class BTCardClient_Tests: XCTestCase { mockAPIClient.cannedResponseError = stubError let cardClient = BTCardClient(apiClient: mockAPIClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "123" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "123" + ) let expectation = self.expectation(description: "Callback invoked with error") cardClient.tokenize(card) { (cardNonce, error) -> Void in @@ -341,11 +349,12 @@ class BTCardClient_Tests: XCTestCase { stubAPIClient.cannedConfigurationResponseBody = BTJSON(value: [] as [Any?]) let cardClient = BTCardClient(apiClient: stubAPIClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "123" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "123" + ) let expectation = self.expectation(description: "Callback invoked with error") cardClient.tokenize(card) { (cardNonce, error) -> Void in @@ -362,10 +371,11 @@ class BTCardClient_Tests: XCTestCase { func testMetaParameter_whenTokenizationIsSuccessful_isPOSTedToServer() { let mockAPIClient = MockAPIClient(authorization: "development_tokenization_key")! let cardClient = BTCardClient(apiClient: mockAPIClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038" + ) mockAPIClient.cannedConfigurationResponseBody = BTJSON(value: [:] as [String?: Any]) @@ -392,10 +402,11 @@ class BTCardClient_Tests: XCTestCase { mockAPIClient.cannedConfigurationResponseBody = BTJSON(value: [:] as [String?: Any]) let cardClient = BTCardClient(apiClient: mockAPIClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038" + ) let expectation = self.expectation(description: "Tokenized card") cardClient.tokenize(card) { _, _ -> Void in @@ -436,10 +447,11 @@ class BTCardClient_Tests: XCTestCase { mockAPIClient.cannedConfigurationResponseBody = BTJSON(value: [:] as [String?: Any]) let cardClient = BTCardClient(apiClient: mockAPIClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038" + ) let expectation = self.expectation(description: "Tokenized card") cardClient.tokenize(card) { _, _ -> Void in @@ -464,13 +476,14 @@ class BTCardClient_Tests: XCTestCase { let cardClient = BTCardClient(apiClient: mockApiClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "1234" - card.authenticationInsightRequested = true - card.merchantAccountID = nil + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234", + authenticationInsightRequested: true, + merchantAccountID: nil + ) let expectation = self.expectation(description: "Returns an error") @@ -495,12 +508,13 @@ class BTCardClient_Tests: XCTestCase { let cardClient = BTCardClient(apiClient: mockApiClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "1234" - card.cardholderName = "Brian Tree" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234", + cardholderName: "Brian Tree" + ) let expectation = self.expectation(description: "Tokenize Card") @@ -523,12 +537,13 @@ class BTCardClient_Tests: XCTestCase { mockApiClient.cannedConfigurationResponseBody = BTJSON(value: [] as [Any?]) let cardClient = BTCardClient(apiClient: mockApiClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "1234" - card.cardholderName = "Brian Tree" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234", + cardholderName: "Brian Tree" + ) let expectation = self.expectation(description: "Tokenize Card") @@ -550,12 +565,13 @@ class BTCardClient_Tests: XCTestCase { ]) let cardClient = BTCardClient(apiClient: mockApiClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "1234" - card.cardholderName = "Brian Tree" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234", + cardholderName: "Brian Tree" + ) let expectation = self.expectation(description: "Tokenize Card") @@ -604,11 +620,12 @@ class BTCardClient_Tests: XCTestCase { let cardClient = BTCardClient(apiClient: mockApiClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "1234" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234" + ) let expectation = self.expectation(description: "Tokenize Card") @@ -672,11 +689,12 @@ class BTCardClient_Tests: XCTestCase { let cardClient = BTCardClient(apiClient: mockApiClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" - card.cvv = "1234" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234" + ) let expectation = self.expectation(description: "Tokenize Card") @@ -721,10 +739,11 @@ class BTCardClient_Tests: XCTestCase { mockAPIClient.cannedResponseError = stubError let cardClient = BTCardClient(apiClient: mockAPIClient) - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = "2038" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038" + ) let expectation = self.expectation(description: "Tokenized card") cardClient.tokenize(card) { _, _ -> Void in From 2aa76c87eb7f02c1b1be7f2093097028d438422e Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 22 Oct 2024 11:39:42 -0600 Subject: [PATCH 05/26] Update Integration tests --- .../BTCardClient_IntegrationTests.swift | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/IntegrationTests/BTCardClient_IntegrationTests.swift b/IntegrationTests/BTCardClient_IntegrationTests.swift index 7968e16e45..ab50e77418 100644 --- a/IntegrationTests/BTCardClient_IntegrationTests.swift +++ b/IntegrationTests/BTCardClient_IntegrationTests.swift @@ -27,11 +27,12 @@ class BTCardClient_IntegrationTests: XCTestCase { func testTokenizeCard_whenCardIsInvalidAndValidationIsEnabled_failsWithExpectedValidationError() { let apiClient = BTAPIClient(authorization: BTIntegrationTestsConstants.sandboxClientToken)! let cardClient = BTCardClient(apiClient: apiClient) - let card = BTCard() - card.number = "123" - card.expirationMonth = "12" - card.expirationYear = Helpers.shared.futureYear() - card.shouldValidate = true + let card = BTCard( + number: "123", + expirationMonth: "12", + expirationYear: Helpers.shared.futureYear(), + shouldValidate: true + ) let expectation = expectation(description: "Tokenize card") @@ -160,8 +161,7 @@ class BTCardClient_IntegrationTests: XCTestCase { func testTokenizeCard_withCVVOnly_tokenizesSuccessfully() { let apiClient = BTAPIClient(authorization: BTIntegrationTestsConstants.sandboxClientTokenVersion3)! let cardClient = BTCardClient(apiClient: apiClient) - let card = BTCard() - card.cvv = "123" + let card = BTCard(cvv: "123") let expectation = expectation(description: "Tokenize card") @@ -182,19 +182,21 @@ class BTCardClient_IntegrationTests: XCTestCase { // MARK: - Private Helper Methods func invalidCard() -> BTCard { - let card = BTCard() - card.number = "123123" - card.expirationMonth = "XX" - card.expirationYear = "XXXX" + let card = BTCard( + number: "123123", + expirationMonth: "XX", + expirationYear: "XXXX" + ) return card } func validCard() -> BTCard { - let card = BTCard() - card.number = "4111111111111111" - card.expirationMonth = "12" - card.expirationYear = Helpers.shared.futureYear() - card.cardholderName = "Cookie Monster" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: Helpers.shared.futureYear(), + cardholderName: "Cookie Monster" + ) return card } } From bab98ba6a494efd2ddc890af7e38a81c11cd1afa Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 22 Oct 2024 11:42:12 -0600 Subject: [PATCH 06/26] Update CardHelper newCard method --- .../Features/Helpers/CardHelpers.swift | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/Demo/Application/Features/Helpers/CardHelpers.swift b/Demo/Application/Features/Helpers/CardHelpers.swift index 3af657ef71..6c7ae6f5b3 100644 --- a/Demo/Application/Features/Helpers/CardHelpers.swift +++ b/Demo/Application/Features/Helpers/CardHelpers.swift @@ -9,25 +9,12 @@ enum Format { enum CardHelpers { static func newCard(from cardFormView: BTCardFormView) -> BTCard { - let card = BTCard() - - if let cardNumber = cardFormView.cardNumber { - card.number = cardNumber - } - - if let expirationYear = cardFormView.expirationYear { - card.expirationYear = expirationYear - } - - if let expirationMonth = cardFormView.expirationMonth { - card.expirationMonth = expirationMonth - } - - if let cvv = cardFormView.cvv { - card.cvv = cvv - } - - return card + BTCard( + number: cardFormView.cardNumber, + expirationMonth: cardFormView.expirationMonth, + expirationYear: cardFormView.expirationYear, + cvv: cardFormView.cvv + ) } static func generateFuture(_ format: Format) -> String { From 54a08afca64a0152d84e9e13839fe8a5badaf927 Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 22 Oct 2024 11:42:36 -0600 Subject: [PATCH 07/26] Update demo app Amex View Controller --- Demo/Application/Features/AmexViewController.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Demo/Application/Features/AmexViewController.swift b/Demo/Application/Features/AmexViewController.swift index 1c082d54c0..1c05997905 100644 --- a/Demo/Application/Features/AmexViewController.swift +++ b/Demo/Application/Features/AmexViewController.swift @@ -39,11 +39,12 @@ class AmexViewController: PaymentButtonBaseViewController { } private func getRewards(for cardNumber: String) { - let card = BTCard() - card.number = cardNumber - card.expirationMonth = "12" - card.expirationYear = CardHelpers.generateFuture(.year) - card.cvv = "1234" + let card = BTCard( + number: cardNumber, + expirationMonth: "12", + expirationYear: CardHelpers.generateFuture(.year), + cvv: "1234" + ) progressBlock("Tokenizing Card") From 5cd44205719cdd67abe90188da6774b09c686a84 Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 22 Oct 2024 13:09:45 -0600 Subject: [PATCH 08/26] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01e435bca2..a46e9a86e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## unreleased * BraintreePayPal * Add `BTPayPalRequest.userPhoneNumber` optional property +* BraintreeCard + * Update `BTCard` to make all properties accessible on the initializer only vs via the dot syntax. ## 6.24.0 (2024-10-15) * BraintreePayPal From 36dd53fb5124262fa92060f9310bfe33bb4d32bf Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 22 Oct 2024 13:10:29 -0600 Subject: [PATCH 09/26] Update V7 readme --- V7_MIGRATION.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/V7_MIGRATION.md b/V7_MIGRATION.md index 8332dcd1b6..64fa451410 100644 --- a/V7_MIGRATION.md +++ b/V7_MIGRATION.md @@ -7,7 +7,20 @@ _Documentation for v7 will be published to https://developer.paypal.com/braintre ## Table of Contents 1. [Supported Versions](#supported-versions) +1. [Card](#card) ## Supported Versions -v7 bumps to a minimum deployment target of iOS 16+. \ No newline at end of file +v7 bumps to a minimum deployment target of iOS 16+. + +## Card +v7 updates `BTCard` to require setting all properties through the initializer, removing support for dot syntax. To construct a `BTCard`, pass the properties directly in the initializer: + +``` +let card = BTCard( + number = "4111111111111111" + expirationMonth = "12" + expirationYear = "2025" + cvv = "123" +) +``` From 0e4df321f81f80c342936708f6139be8d7d2e6ee Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 22 Oct 2024 13:56:17 -0600 Subject: [PATCH 10/26] Move property docstrings to the init method --- Sources/BraintreeCard/BTCard.swift | 85 ++++++++++++------------------ 1 file changed, 34 insertions(+), 51 deletions(-) diff --git a/Sources/BraintreeCard/BTCard.swift b/Sources/BraintreeCard/BTCard.swift index 49c92df8f9..7db4daadf5 100644 --- a/Sources/BraintreeCard/BTCard.swift +++ b/Sources/BraintreeCard/BTCard.swift @@ -6,78 +6,61 @@ import Foundation // MARK: - Internal Properties - /// The card number let number: String? - - /// The expiration month as a one or two-digit number on the Gregorian calendar let expirationMonth: String? - - /// The expiration year as a two or four-digit number on the Gregorian calendar let expirationYear: String? - - /// The card verification code (like CVV or CID). - /// - Note: If you wish to create a CVV-only payment method nonce to verify a card already stored in your Vault, - /// omit all other properties to only collect CVV. let cvv: String? - - /// The postal code associated with the card's billing address let postalCode: String? - - /// Optional: the cardholder's name. let cardholderName: String? - - /// Optional: first name on the card. let firstName: String? - - /// Optional: last name on the card. let lastName: String? - - /// Optional: company name associated with the card. let company: String? - - /// Optional: the street address associated with the card's billing address let streetAddress: String? - - /// Optional: the extended address associated with the card's billing address let extendedAddress: String? - - /// Optional: the city associated with the card's billing address let locality: String? - - /// Optional: either a two-letter state code (for the US), or an ISO-3166-2 country subdivision code of up to three letters. let region: String? - - /// Optional: the country name associated with the card's billing address. - /// - Note: Braintree only accepts specific country names. - /// - SeeAlso: https://developer.paypal.com/braintree/docs/reference/general/countries#list-of-countries let countryName: String? - - /// Optional: the ISO 3166-1 alpha-2 country code specified in the card's billing address. - /// - Note: Braintree only accepts specific alpha-2 values. - /// - SeeAlso: https://developer.paypal.com/braintree/docs/reference/general/countries#list-of-countries let countryCodeAlpha2: String? - - /// Optional: the ISO 3166-1 alpha-3 country code specified in the card's billing address. - /// - Note: Braintree only accepts specific alpha-3 values. - /// - SeeAlso: https://developer.paypal.com/braintree/docs/reference/general/countries#list-of-countries let countryCodeAlpha3: String? - - /// Optional: The ISO 3166-1 numeric country code specified in the card's billing address. - /// - Note: Braintree only accepts specific numeric values. - /// - SeeAlso: https://developer.paypal.com/braintree/docs/reference/general/countries#list-of-countries let countryCodeNumeric: String? - - /// Controls whether or not to return validations and/or verification results. By default, this is not enabled. - /// - Note: Use this flag with caution. By enabling client-side validation, certain tokenize card requests may result in adding the card to the vault. - /// These semantics are not currently documented. var shouldValidate: Bool = false - - /// Optional: If authentication insight is requested. If this property is set to true, a `merchantAccountID` must be provided. Defaults to false. var authenticationInsightRequested: Bool = false - - /// Optional: The merchant account ID. let merchantAccountID: String? + // MARK: - Initializer + + /// Creates a Card + /// - Parameters: + /// - number: The card number. + /// - expirationMonth: The expiration month as a one or two-digit number on the Gregorian calendar. + /// - expirationYear:The expiration year as a two or four-digit number on the Gregorian calendar. + /// - cvv: The card verification code (like CVV or CID). + /// - Note: If you wish to create a CVV-only payment method nonce to verify a card already stored in your Vault, omit all other properties to only collect CVV. + /// - postalCode: The postal code associated with the card's billing address. + /// - cardholderName: Optional: the cardholder's name. + /// - firstName: Optional: first name on the card. + /// - lastName: Optional: last name on the card. + /// - company: Optional: company name associated with the card. + /// - streetAddress: Optional: the street address associated with the card's billing address. + /// - extendedAddress: Optional: the extended address associated with the card's billing address. + /// - locality: Optional: the city associated with the card's billing address. + /// - region: Optional: either a two-letter state code (for the US), or an ISO-3166-2 country subdivision code of up to three letters. + /// - countryName: Optional: the country name associated with the card's billing address. + /// - Note: Braintree only accepts specific country names. + /// - SeeAlso: https://developer.paypal.com/braintree/docs/reference/general/countries#list-of-countries + /// - countryCodeAlpha2: Optional: the ISO 3166-1 alpha-2 country code specified in the card's billing address. + /// - Note: Braintree only accepts specific alpha-2 values. + /// - SeeAlso: https://developer.paypal.com/braintree/docs/reference/general/countries#list-of-countries + /// - countryCodeAlpha3: Optional: the ISO 3166-1 alpha-3 country code specified in the card's billing address. + /// - Note: Braintree only accepts specific alpha-3 values. + /// - SeeAlso: https://developer.paypal.com/braintree/docs/reference/general/countries#list-of-countries + /// - countryCodeNumeric: Optional: The ISO 3166-1 numeric country code specified in the card's billing address. + /// - Note: Braintree only accepts specific numeric values. + /// - SeeAlso: https://developer.paypal.com/braintree/docs/reference/general/countries#list-of-countries + /// - shouldValidate: Controls whether or not to return validations and/or verification results. By default, this is not enabled. + /// - Note: Use this flag with caution. By enabling client-side validation, certain tokenize card requests may result in adding the card to the vault. These semantics are not currently documented. + /// - authenticationInsightRequested: Optional: If authentication insight is requested. If this property is set to `true`, a `merchantAccountID` must be provided. Defaults to `false`. + /// - merchantAccountID: Optional: The merchant account ID. public init( number: String? = nil, expirationMonth: String? = nil, From 8724b6434c205b71a5a97bc073dea03d800fbcb5 Mon Sep 17 00:00:00 2001 From: richherrera Date: Wed, 23 Oct 2024 11:48:15 -0600 Subject: [PATCH 11/26] Add optional on postalCode docstring --- Sources/BraintreeCard/BTCard.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraintreeCard/BTCard.swift b/Sources/BraintreeCard/BTCard.swift index 7db4daadf5..0ada47a2c5 100644 --- a/Sources/BraintreeCard/BTCard.swift +++ b/Sources/BraintreeCard/BTCard.swift @@ -36,7 +36,7 @@ import Foundation /// - expirationYear:The expiration year as a two or four-digit number on the Gregorian calendar. /// - cvv: The card verification code (like CVV or CID). /// - Note: If you wish to create a CVV-only payment method nonce to verify a card already stored in your Vault, omit all other properties to only collect CVV. - /// - postalCode: The postal code associated with the card's billing address. + /// - postalCode: Optional: The postal code associated with the card's billing address. /// - cardholderName: Optional: the cardholder's name. /// - firstName: Optional: first name on the card. /// - lastName: Optional: last name on the card. From 6b1c65d70afa6679f708eab79f15f76f70b1b804 Mon Sep 17 00:00:00 2001 From: richherrera Date: Wed, 23 Oct 2024 11:56:01 -0600 Subject: [PATCH 12/26] Fix code snippet --- V7_MIGRATION.md | 1 + 1 file changed, 1 insertion(+) diff --git a/V7_MIGRATION.md b/V7_MIGRATION.md index 6447a420a9..9f20154576 100644 --- a/V7_MIGRATION.md +++ b/V7_MIGRATION.md @@ -24,6 +24,7 @@ let card = BTCard( expirationYear = "2025" cvv = "123" ) +``` ## Venmo All properties within `BTVenmoRequest` can only be accessed on the initializer vs via the dot syntax. From bc425468c74a33888128b0a9bbf5022c09bbcdb7 Mon Sep 17 00:00:00 2001 From: richherrera Date: Wed, 23 Oct 2024 15:11:09 -0600 Subject: [PATCH 13/26] Add the missing commas in the documentation example --- V7_MIGRATION.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/V7_MIGRATION.md b/V7_MIGRATION.md index ee88f8eda9..3e3320de1e 100644 --- a/V7_MIGRATION.md +++ b/V7_MIGRATION.md @@ -20,9 +20,9 @@ v7 updates `BTCard` to require setting all properties through the initializer, r ``` let card = BTCard( - number = "4111111111111111" - expirationMonth = "12" - expirationYear = "2025" + number = "4111111111111111", + expirationMonth = "12", + expirationYear = "2025", cvv = "123" ) ``` From 618dc37ad208cf4ff04cdcbe65598629b1d8c760 Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 29 Oct 2024 11:53:47 -0600 Subject: [PATCH 14/26] Remove optional for required properties, change boolean to let --- Sources/BraintreeCard/BTCard.swift | 40 +++++++++++------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/Sources/BraintreeCard/BTCard.swift b/Sources/BraintreeCard/BTCard.swift index 0ada47a2c5..4788eb01dc 100644 --- a/Sources/BraintreeCard/BTCard.swift +++ b/Sources/BraintreeCard/BTCard.swift @@ -6,10 +6,10 @@ import Foundation // MARK: - Internal Properties - let number: String? - let expirationMonth: String? - let expirationYear: String? - let cvv: String? + let number: String + let expirationMonth: String + let expirationYear: String + let cvv: String let postalCode: String? let cardholderName: String? let firstName: String? @@ -23,8 +23,8 @@ import Foundation let countryCodeAlpha2: String? let countryCodeAlpha3: String? let countryCodeNumeric: String? - var shouldValidate: Bool = false - var authenticationInsightRequested: Bool = false + let shouldValidate: Bool + let authenticationInsightRequested: Bool let merchantAccountID: String? // MARK: - Initializer @@ -62,10 +62,10 @@ import Foundation /// - authenticationInsightRequested: Optional: If authentication insight is requested. If this property is set to `true`, a `merchantAccountID` must be provided. Defaults to `false`. /// - merchantAccountID: Optional: The merchant account ID. public init( - number: String? = nil, - expirationMonth: String? = nil, - expirationYear: String? = nil, - cvv: String? = nil, + number: String, + expirationMonth: String, + expirationYear: String, + cvv: String, postalCode: String? = nil, cardholderName: String? = nil, firstName: String? = nil, @@ -151,22 +151,10 @@ import Foundation private func buildCardDictionary(isGraphQL: Bool) -> [String: Any] { var cardDictionary: [String: Any] = [:] - - if let number { - cardDictionary["number"] = number - } - - if let expirationMonth { - cardDictionary[isGraphQL ? "expirationMonth" : "expiration_month"] = expirationMonth - } - - if let expirationYear { - cardDictionary[isGraphQL ? "expirationYear" : "expiration_year"] = expirationYear - } - - if let cvv { - cardDictionary["cvv"] = cvv - } + cardDictionary["number"] = number + cardDictionary[isGraphQL ? "expirationMonth" : "expiration_month"] = expirationMonth + cardDictionary[isGraphQL ? "expirationYear" : "expiration_year"] = expirationYear + cardDictionary["cvv"] = cvv if let cardholderName { cardDictionary[isGraphQL ? "cardholderName" : "cardholder_name"] = cardholderName From d62f43d4e97c3b179f923cc24c98449d1704ccfd Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 29 Oct 2024 11:54:35 -0600 Subject: [PATCH 15/26] Make return optional for static method card helper --- .../Features/Helpers/CardHelpers.swift | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Demo/Application/Features/Helpers/CardHelpers.swift b/Demo/Application/Features/Helpers/CardHelpers.swift index 6c7ae6f5b3..829f0ba4aa 100644 --- a/Demo/Application/Features/Helpers/CardHelpers.swift +++ b/Demo/Application/Features/Helpers/CardHelpers.swift @@ -8,12 +8,19 @@ enum Format { enum CardHelpers { - static func newCard(from cardFormView: BTCardFormView) -> BTCard { - BTCard( - number: cardFormView.cardNumber, - expirationMonth: cardFormView.expirationMonth, - expirationYear: cardFormView.expirationYear, - cvv: cardFormView.cvv + static func newCard(from cardFormView: BTCardFormView) -> BTCard? { + guard + let cardNumber = cardFormView.cardNumber, + let expirationMonth = cardFormView.expirationMonth, + let expirationYear = cardFormView.expirationYear, + let cvv = cardFormView.cvv + else { return nil} + + return .init( + number: cardNumber, + expirationMonth: expirationMonth, + expirationYear: expirationYear, + cvv: cvv ) } From 727ce54525cbdad781610ae7ab4da4ef5f91e30b Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 29 Oct 2024 11:55:39 -0600 Subject: [PATCH 16/26] Add message when user uses optional card helper method --- .../Features/CardTokenizationViewController.swift | 5 ++++- Demo/Application/Features/ThreeDSecureViewController.swift | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Demo/Application/Features/CardTokenizationViewController.swift b/Demo/Application/Features/CardTokenizationViewController.swift index 198f72f245..fa946cea27 100644 --- a/Demo/Application/Features/CardTokenizationViewController.swift +++ b/Demo/Application/Features/CardTokenizationViewController.swift @@ -20,8 +20,11 @@ class CardTokenizationViewController: PaymentButtonBaseViewController { @objc func tappedSubmit() { progressBlock("Tokenizing card details!") + guard let card = CardHelpers.newCard(from: cardFormView) else { + progressBlock("Fill in all the card fields.") + return + } let cardClient = BTCardClient(apiClient: apiClient) - let card = CardHelpers.newCard(from: cardFormView) setFieldsEnabled(false) cardClient.tokenize(card) { nonce, error in diff --git a/Demo/Application/Features/ThreeDSecureViewController.swift b/Demo/Application/Features/ThreeDSecureViewController.swift index 315e0d7580..be1d7dccc1 100644 --- a/Demo/Application/Features/ThreeDSecureViewController.swift +++ b/Demo/Application/Features/ThreeDSecureViewController.swift @@ -43,7 +43,10 @@ class ThreeDSecureViewController: PaymentButtonBaseViewController { callbackCount = 0 updateCallbackCount() - let card = CardHelpers.newCard(from: cardFormView) + guard let card = CardHelpers.newCard(from: cardFormView) else { + progressBlock("Fill in all the card fields.") + return + } let cardClient = BTCardClient(apiClient: apiClient) cardClient.tokenize(card) { tokenizedCard, error in From c96278d444549ba88b476de462ba72d3bd78ff61 Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 29 Oct 2024 14:00:17 -0600 Subject: [PATCH 17/26] Fix BTCardTests --- .../BraintreeCardTests/BTCard_Tests.swift | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/UnitTests/BraintreeCardTests/BTCard_Tests.swift b/UnitTests/BraintreeCardTests/BTCard_Tests.swift index d6dda7015c..a902bd8186 100644 --- a/UnitTests/BraintreeCardTests/BTCard_Tests.swift +++ b/UnitTests/BraintreeCardTests/BTCard_Tests.swift @@ -182,25 +182,13 @@ class BTCard_Tests: XCTestCase { ] ] as [String: Any] as NSObject) } - - func testGraphQLParameters_whenDoingCVVOnly_returnsExpectedValue() { - let card = BTCard(cvv: "123") - - 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) - } func testGraphQLParameters_whenMerchantAccountIDIsPresent_andAuthInsightRequestedIsTrue_requestsAuthInsight() { let card = BTCard( number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234", authenticationInsightRequested: true, merchantAccountID: "some id" ) @@ -211,6 +199,9 @@ class BTCard_Tests: XCTestCase { "variables": [ "input": [ "creditCard": [ + "cvv": "1234", + "expirationMonth": "12", + "expirationYear": "2038", "number": "4111111111111111", ], "options": [ "validate": false ], @@ -225,6 +216,9 @@ class BTCard_Tests: XCTestCase { func testGraphQLParameters_whenMerchantAccountIDIsPresent_andAuthInsightRequestedIsFalse_doesNotRequestAuthInsight() { let card = BTCard( number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234", authenticationInsightRequested: false, merchantAccountID: "some id" ) @@ -234,7 +228,10 @@ class BTCard_Tests: XCTestCase { "query": graphQLQuery, "variables": [ "input": [ - "creditCard": ["number": "4111111111111111"] as [String: String], + "creditCard": ["number": "4111111111111111", + "cvv": "1234", + "expirationMonth": "12", + "expirationYear": "2038"] as [String: String], "options": ["validate": false], ] as [String: Any] ] @@ -244,6 +241,9 @@ class BTCard_Tests: XCTestCase { func testGraphQLParameters_whenMerchantAccountIDIsNil_andAuthInsightRequestedIsTrue_requestsAuthInsight() { let card = BTCard( number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234", authenticationInsightRequested: true, merchantAccountID: nil ) @@ -254,6 +254,9 @@ class BTCard_Tests: XCTestCase { "variables": [ "input": [ "creditCard": [ + "cvv": "1234", + "expirationMonth": "12", + "expirationYear": "2038", "number": "4111111111111111", ], "options": [ "validate": false ], @@ -266,6 +269,9 @@ class BTCard_Tests: XCTestCase { func testGraphQLParameters_whenMerchantAccountIDIsNil_andAuthInsightRequestedIsFalse_doesNotRequestAuthInsight() { let card = BTCard( number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "123", authenticationInsightRequested: false, merchantAccountID: nil ) @@ -275,7 +281,10 @@ class BTCard_Tests: XCTestCase { "query": graphQLQuery, "variables": [ "input": [ - "creditCard": ["number": "4111111111111111"] as [String: String], + "creditCard": ["number": "4111111111111111", + "cvv": "123", + "expirationMonth": "12", + "expirationYear": "2038"] as [String: String], "options": [ "validate": false ], ] as [String: Any] ] From f4628dbf661d0b8ff64a9b21d6ca41e9a86c703d Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 29 Oct 2024 14:00:58 -0600 Subject: [PATCH 18/26] Fix BTCardClient tests --- .../BTCardClient_Tests.swift | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift b/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift index 60b8ce0ed2..0900851962 100644 --- a/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift +++ b/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift @@ -109,7 +109,8 @@ class BTCardClient_Tests: XCTestCase { let card = BTCard( number: "4111111111111111", expirationMonth: "12", - expirationYear: "2038" + expirationYear: "2038", + cvv: "1234" ) cardClient.tokenize(card) { (tokenizedCard, error) -> Void in @@ -141,7 +142,8 @@ class BTCardClient_Tests: XCTestCase { let card = BTCard( number: "4111111111111111", expirationMonth: "12", - expirationYear: "2038" + expirationYear: "2038", + cvv: "1234" ) cardClient.tokenize(card) { (tokenizedCard, error) -> Void in @@ -374,7 +376,8 @@ class BTCardClient_Tests: XCTestCase { let card = BTCard( number: "4111111111111111", expirationMonth: "12", - expirationYear: "2038" + expirationYear: "2038", + cvv: "1234" ) mockAPIClient.cannedConfigurationResponseBody = BTJSON(value: [:] as [String?: Any]) @@ -405,7 +408,8 @@ class BTCardClient_Tests: XCTestCase { let card = BTCard( number: "4111111111111111", expirationMonth: "12", - expirationYear: "2038" + expirationYear: "2038", + cvv: "1234" ) let expectation = self.expectation(description: "Tokenized card") @@ -450,7 +454,8 @@ class BTCardClient_Tests: XCTestCase { let card = BTCard( number: "4111111111111111", expirationMonth: "12", - expirationYear: "2038" + expirationYear: "2038", + cvv: "1234" ) let expectation = self.expectation(description: "Tokenized card") @@ -742,7 +747,8 @@ class BTCardClient_Tests: XCTestCase { let card = BTCard( number: "4111111111111111", expirationMonth: "12", - expirationYear: "2038" + expirationYear: "2038", + cvv: "1234" ) let expectation = self.expectation(description: "Tokenized card") From a578f4ab6ada8c7c48f3367b15b39000e0ff6909 Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 29 Oct 2024 14:09:04 -0600 Subject: [PATCH 19/26] Fix BTCardClient integration tests --- .../BTCardClient_IntegrationTests.swift | 92 ++++++++----------- 1 file changed, 40 insertions(+), 52 deletions(-) diff --git a/IntegrationTests/BTCardClient_IntegrationTests.swift b/IntegrationTests/BTCardClient_IntegrationTests.swift index ab50e77418..7303d01c89 100644 --- a/IntegrationTests/BTCardClient_IntegrationTests.swift +++ b/IntegrationTests/BTCardClient_IntegrationTests.swift @@ -8,8 +8,14 @@ class BTCardClient_IntegrationTests: XCTestCase { let apiClient = BTAPIClient(authorization: BTIntegrationTestsConstants.sandboxTokenizationKey)! let cardClient = BTCardClient(apiClient: apiClient) let expectation = expectation(description: "Tokenize card") - - cardClient.tokenize(invalidCard()) { tokenizedCard, error in + let card = BTCard( + number: "123123", + expirationMonth: "XX", + expirationYear: "XXXX", + cvv: "1234" + ) + + cardClient.tokenize(card) { tokenizedCard, error in guard let tokenizedCard else { XCTFail("Expect a nonce to be returned") return @@ -31,6 +37,7 @@ class BTCardClient_IntegrationTests: XCTestCase { number: "123", expirationMonth: "12", expirationYear: Helpers.shared.futureYear(), + cvv: "1234", shouldValidate: true ) @@ -57,8 +64,15 @@ class BTCardClient_IntegrationTests: XCTestCase { let apiClient = BTAPIClient(authorization: BTIntegrationTestsConstants.sandboxTokenizationKey)! let cardClient = BTCardClient(apiClient: apiClient) let expectation = expectation(description: "Tokenize card") - - cardClient.tokenize(validCard()) { tokenizedCard, error in + let card = BTCard( + number: "123", + expirationMonth: "12", + expirationYear: Helpers.shared.futureYear(), + cvv: "1234", + cardholderName: "Cookie Monster" + ) + + cardClient.tokenize(card) { tokenizedCard, error in guard let tokenizedCard else { XCTFail("Expect a nonce to be returned") return @@ -90,8 +104,13 @@ class BTCardClient_IntegrationTests: XCTestCase { func testTokenizeCard_whenUsingTokenizationKeyAndCardHasValidationEnabled_failsWithAuthorizationError() { let apiClient = BTAPIClient(authorization: BTIntegrationTestsConstants.sandboxTokenizationKey)! let cardClient = BTCardClient(apiClient: apiClient) - let card = invalidCard() - card.shouldValidate = true + let card = BTCard( + number: "123123", + expirationMonth: "XX", + expirationYear: "XXXX", + cvv: "1234", + shouldValidate: true + ) let expectation = expectation(description: "Tokenize card") cardClient.tokenize(card) { tokenizedCard, error in @@ -115,8 +134,14 @@ class BTCardClient_IntegrationTests: XCTestCase { func testTokenizeCard_whenUsingClientTokenAndCardHasValidationEnabledAndCardIsValid_tokenizesSuccessfully() { let apiClient = BTAPIClient(authorization: BTIntegrationTestsConstants.sandboxClientToken)! let cardClient = BTCardClient(apiClient: apiClient) - let card = validCard() - card.shouldValidate = true + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: Helpers.shared.futureYear(), + cvv: "123", + cardholderName: "Cookie Monster", + shouldValidate: true + ) let expectation = expectation(description: "Tokenize card") @@ -138,8 +163,13 @@ class BTCardClient_IntegrationTests: XCTestCase { func testTokenizeCard_whenUsingVersionThreeClientTokenAndCardHasValidationEnabledAndCardIsValid_tokenizesSuccessfully() { let apiClient = BTAPIClient(authorization: BTIntegrationTestsConstants.sandboxClientTokenVersion3)! let cardClient = BTCardClient(apiClient: apiClient) - let card = validCard() - card.shouldValidate = true + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: Helpers.shared.futureYear(), + cvv: "123", + shouldValidate: true + ) let expectation = expectation(description: "Tokenize card") @@ -157,46 +187,4 @@ class BTCardClient_IntegrationTests: XCTestCase { waitForExpectations(timeout: 5) } - - func testTokenizeCard_withCVVOnly_tokenizesSuccessfully() { - let apiClient = BTAPIClient(authorization: BTIntegrationTestsConstants.sandboxClientTokenVersion3)! - let cardClient = BTCardClient(apiClient: apiClient) - let card = BTCard(cvv: "123") - - let expectation = expectation(description: "Tokenize card") - - cardClient.tokenize(card) { tokenizedCard, error in - guard let tokenizedCard else { - XCTFail("Expect a nonce to be returned") - return - } - - XCTAssertTrue(tokenizedCard.nonce.isValidNonce) - XCTAssertNil(error) - expectation.fulfill() - } - - waitForExpectations(timeout: 5) - } - - // MARK: - Private Helper Methods - - func invalidCard() -> BTCard { - let card = BTCard( - number: "123123", - expirationMonth: "XX", - expirationYear: "XXXX" - ) - return card - } - - func validCard() -> BTCard { - let card = BTCard( - number: "4111111111111111", - expirationMonth: "12", - expirationYear: Helpers.shared.futureYear(), - cardholderName: "Cookie Monster" - ) - return card - } } From c127063a846a995a4f9b039521ab7d5f84d8663d Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 29 Oct 2024 16:28:22 -0600 Subject: [PATCH 20/26] Remove unnecessary code sample --- V7_MIGRATION.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/V7_MIGRATION.md b/V7_MIGRATION.md index 3e3320de1e..796bbc9d05 100644 --- a/V7_MIGRATION.md +++ b/V7_MIGRATION.md @@ -16,16 +16,7 @@ _Documentation for v7 will be published to https://developer.paypal.com/braintre v7 bumps to a minimum deployment target of iOS 16+. ## Card -v7 updates `BTCard` to require setting all properties through the initializer, removing support for dot syntax. To construct a `BTCard`, pass the properties directly in the initializer: - -``` -let card = BTCard( - number = "4111111111111111", - expirationMonth = "12", - expirationYear = "2025", - cvv = "123" -) -``` +v7 updates `BTCard` to require setting all properties through the initializer, removing support for dot syntax. To construct a `BTCard`, pass the properties directly in the initializer. ## Venmo All properties within `BTVenmoRequest` can only be accessed on the initializer vs via the dot syntax. From dfb1c5e9df67b80c57a111813d7ddcfd5f93a18e Mon Sep 17 00:00:00 2001 From: richherrera Date: Wed, 30 Oct 2024 10:11:03 -0600 Subject: [PATCH 21/26] Address PR feedback --- Demo/Application/Features/Helpers/CardHelpers.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Demo/Application/Features/Helpers/CardHelpers.swift b/Demo/Application/Features/Helpers/CardHelpers.swift index 829f0ba4aa..26f30fbd24 100644 --- a/Demo/Application/Features/Helpers/CardHelpers.swift +++ b/Demo/Application/Features/Helpers/CardHelpers.swift @@ -14,9 +14,9 @@ enum CardHelpers { let expirationMonth = cardFormView.expirationMonth, let expirationYear = cardFormView.expirationYear, let cvv = cardFormView.cvv - else { return nil} + else { return nil } - return .init( + return BTCard( number: cardNumber, expirationMonth: expirationMonth, expirationYear: expirationYear, From 759bfc6a324ab2e135e3a87bf1d9fc1de9959bcb Mon Sep 17 00:00:00 2001 From: richherrera Date: Wed, 6 Nov 2024 12:42:48 -0600 Subject: [PATCH 22/26] Remove cvv note --- Sources/BraintreeCard/BTCard.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/BraintreeCard/BTCard.swift b/Sources/BraintreeCard/BTCard.swift index 4788eb01dc..5317aae718 100644 --- a/Sources/BraintreeCard/BTCard.swift +++ b/Sources/BraintreeCard/BTCard.swift @@ -35,7 +35,6 @@ import Foundation /// - expirationMonth: The expiration month as a one or two-digit number on the Gregorian calendar. /// - expirationYear:The expiration year as a two or four-digit number on the Gregorian calendar. /// - cvv: The card verification code (like CVV or CID). - /// - Note: If you wish to create a CVV-only payment method nonce to verify a card already stored in your Vault, omit all other properties to only collect CVV. /// - postalCode: Optional: The postal code associated with the card's billing address. /// - cardholderName: Optional: the cardholder's name. /// - firstName: Optional: first name on the card. From b9744d38e89630f6ff573399a9b8163beb2742cd Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 14 Nov 2024 12:04:43 -0600 Subject: [PATCH 23/26] Add init with cvv --- .../Features/Helpers/CardHelpers.swift | 11 +++++------ Sources/BraintreeCard/BTCard.swift | 8 ++++++++ .../BraintreeCardTests/BTCard_Tests.swift | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Demo/Application/Features/Helpers/CardHelpers.swift b/Demo/Application/Features/Helpers/CardHelpers.swift index 26f30fbd24..de369ba9a1 100644 --- a/Demo/Application/Features/Helpers/CardHelpers.swift +++ b/Demo/Application/Features/Helpers/CardHelpers.swift @@ -9,12 +9,11 @@ enum Format { enum CardHelpers { static func newCard(from cardFormView: BTCardFormView) -> BTCard? { - guard - let cardNumber = cardFormView.cardNumber, - let expirationMonth = cardFormView.expirationMonth, - let expirationYear = cardFormView.expirationYear, - let cvv = cardFormView.cvv - else { return nil } + let cardNumber = cardFormView.cardNumber ?? "" + let expirationMonth = cardFormView.expirationMonth ?? "" + let expirationYear = cardFormView.expirationYear ?? "" + + guard let cvv = cardFormView.cvv else { return nil } return BTCard( number: cardNumber, diff --git a/Sources/BraintreeCard/BTCard.swift b/Sources/BraintreeCard/BTCard.swift index 5317aae718..1e736c72a9 100644 --- a/Sources/BraintreeCard/BTCard.swift +++ b/Sources/BraintreeCard/BTCard.swift @@ -104,6 +104,14 @@ import Foundation self.merchantAccountID = merchantAccountID } + /// Creates a new instance of `BTCard` with only a CVV value, + /// setting default values for all other parameters. + /// - Parameters: + /// - cvv: The card verification code (like CVV or CID). + public convenience init(cvv: String) { + self.init(number: "", expirationMonth: "", expirationYear: "", cvv: cvv) + } + // MARK: - Internal Methods func parameters() -> [String: Any] { diff --git a/UnitTests/BraintreeCardTests/BTCard_Tests.swift b/UnitTests/BraintreeCardTests/BTCard_Tests.swift index a902bd8186..96777ea521 100644 --- a/UnitTests/BraintreeCardTests/BTCard_Tests.swift +++ b/UnitTests/BraintreeCardTests/BTCard_Tests.swift @@ -183,6 +183,24 @@ class BTCard_Tests: XCTestCase { ] as [String: Any] as NSObject) } + func testGraphQLParameters_whenDoingCVVOnly_returnsExpectedValue() { + let card = BTCard(cvv: "123") + + XCTAssertEqual(card.graphQLParameters() as NSObject, [ + "operationName": "TokenizeCreditCard", + "query": graphQLQuery, + "variables": [ + "input": [ + "creditCard": ["cvv": "123", + "expirationMonth": "", + "expirationYear": "", + "number": ""] as [String: String], + "options": ["validate": false] + ] as [String: Any] + ] + ] as [String: Any] as NSObject) + } + func testGraphQLParameters_whenMerchantAccountIDIsPresent_andAuthInsightRequestedIsTrue_requestsAuthInsight() { let card = BTCard( number: "4111111111111111", From 8e192dffaff4b0d044c2356adc94a51838ee8d33 Mon Sep 17 00:00:00 2001 From: richherrera Date: Thu, 14 Nov 2024 12:08:56 -0600 Subject: [PATCH 24/26] Update amex integration test --- .../BraintreeAmexExpress_IntegrationTests.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/IntegrationTests/BraintreeAmexExpress_IntegrationTests.swift b/IntegrationTests/BraintreeAmexExpress_IntegrationTests.swift index e3ab53db58..2e5f523677 100644 --- a/IntegrationTests/BraintreeAmexExpress_IntegrationTests.swift +++ b/IntegrationTests/BraintreeAmexExpress_IntegrationTests.swift @@ -10,11 +10,12 @@ class BraintreeAmexExpress_IntegrationTests: XCTestCase { let cardClient = BTCardClient(apiClient: apiClient) let amexClient = BTAmericanExpressClient(apiClient: apiClient) - let card = BTCard() - card.number = "371260714673002" - card.expirationMonth = "12" - card.expirationYear = Helpers.shared.futureYear() - card.cvv = "1234" + let card = BTCard( + number: "371260714673002", + expirationMonth: "12", + expirationYear: Helpers.shared.futureYear(), + cvv: "1234" + ) do { let tokenizedCard = try await cardClient.tokenize(card) From 6ae1adb29b4ddc40f868683cfa1870aadc797b77 Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 19 Nov 2024 10:20:56 -0600 Subject: [PATCH 25/26] Update docstring and empty validation --- Sources/BraintreeCard/BTCard.swift | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Sources/BraintreeCard/BTCard.swift b/Sources/BraintreeCard/BTCard.swift index 1e736c72a9..70a7da941c 100644 --- a/Sources/BraintreeCard/BTCard.swift +++ b/Sources/BraintreeCard/BTCard.swift @@ -106,6 +106,8 @@ import Foundation /// Creates a new instance of `BTCard` with only a CVV value, /// setting default values for all other parameters. + /// This initializer should only be used if you wish to create a + /// CVV-only payment method nonce to verify a card already stored in your Vault. /// - Parameters: /// - cvv: The card verification code (like CVV or CID). public convenience init(cvv: String) { @@ -158,9 +160,19 @@ import Foundation private func buildCardDictionary(isGraphQL: Bool) -> [String: Any] { var cardDictionary: [String: Any] = [:] - cardDictionary["number"] = number - cardDictionary[isGraphQL ? "expirationMonth" : "expiration_month"] = expirationMonth - cardDictionary[isGraphQL ? "expirationYear" : "expiration_year"] = expirationYear + + 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 { From 65cadd593695818da8f60cb482d72fac6123b2da Mon Sep 17 00:00:00 2001 From: richherrera Date: Tue, 19 Nov 2024 10:21:08 -0600 Subject: [PATCH 26/26] Update UTs --- UnitTests/BraintreeCardTests/BTCard_Tests.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/UnitTests/BraintreeCardTests/BTCard_Tests.swift b/UnitTests/BraintreeCardTests/BTCard_Tests.swift index 96777ea521..edb6c051dc 100644 --- a/UnitTests/BraintreeCardTests/BTCard_Tests.swift +++ b/UnitTests/BraintreeCardTests/BTCard_Tests.swift @@ -191,10 +191,7 @@ class BTCard_Tests: XCTestCase { "query": graphQLQuery, "variables": [ "input": [ - "creditCard": ["cvv": "123", - "expirationMonth": "", - "expirationYear": "", - "number": ""] as [String: String], + "creditCard": ["cvv": "123"] as [String: String], "options": ["validate": false] ] as [String: Any] ]