diff --git a/CHANGELOG.md b/CHANGELOG.md index 2116d8887a..775f9c2a22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ * Update `BTSEPADirectDebitRequest` to make all properties accessible on the initializer only vs via the dot syntax. * BraintreeLocalPayment * Update `BTLocalPaymentRequest` to make all properties accessible on the initializer only vs via the dot syntax. - * BraintreeCard + * BraintreeCard + * Update `BTCard` to make all properties accessible on the initializer only vs via the dot syntax. * Remove `BTCardRequest`, use `BTCard` directly instead ## unreleased 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") 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/Helpers/CardHelpers.swift b/Demo/Application/Features/Helpers/CardHelpers.swift index 3af657ef71..de369ba9a1 100644 --- a/Demo/Application/Features/Helpers/CardHelpers.swift +++ b/Demo/Application/Features/Helpers/CardHelpers.swift @@ -8,26 +8,19 @@ 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 + static func newCard(from cardFormView: BTCardFormView) -> BTCard? { + let cardNumber = cardFormView.cardNumber ?? "" + let expirationMonth = cardFormView.expirationMonth ?? "" + let expirationYear = cardFormView.expirationYear ?? "" + + guard let cvv = cardFormView.cvv else { return nil } + + return BTCard( + number: cardNumber, + expirationMonth: expirationMonth, + expirationYear: expirationYear, + cvv: cvv + ) } static func generateFuture(_ format: Format) -> String { 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 diff --git a/IntegrationTests/BTCardClient_IntegrationTests.swift b/IntegrationTests/BTCardClient_IntegrationTests.swift index 7968e16e45..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 @@ -27,11 +33,13 @@ 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(), + cvv: "1234", + shouldValidate: true + ) let expectation = expectation(description: "Tokenize card") @@ -56,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 @@ -89,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 @@ -114,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") @@ -137,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") @@ -156,45 +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() - card.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() - card.number = "123123" - card.expirationMonth = "XX" - card.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" - return card - } } 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) diff --git a/Sources/BraintreeCard/BTCard.swift b/Sources/BraintreeCard/BTCard.swift index 420dd4fdfd..70a7da941c 100644 --- a/Sources/BraintreeCard/BTCard.swift +++ b/Sources/BraintreeCard/BTCard.swift @@ -4,80 +4,116 @@ import Foundation /// Its main purpose is to serve as the input for tokenization. @objcMembers public class BTCard: NSObject { - // MARK: - Public Properties - - /// The card number - public var number: String? - - /// The expiration month as a one or two-digit number on the Gregorian calendar - public var expirationMonth: String? - - /// The expiration year as a two or four-digit number on the Gregorian calendar - public var 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? - - /// The postal code associated with the card's billing address - public var postalCode: String? - - /// Optional: the cardholder's name. - public var cardholderName: String? - - /// Optional: first name on the card. - public var firstName: String? - - /// Optional: last name on the card. - public var lastName: String? - - /// Optional: company name associated with the card. - public var company: String? - - /// Optional: the street address associated with the card's billing address - public var streetAddress: String? - - /// Optional: the extended address associated with the card's billing address - public var extendedAddress: String? - - /// Optional: the city associated with the card's billing address - public var 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? - - /// 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? - - /// 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? - - /// 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? - - /// 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? - - /// 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 - - /// 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 - - /// Optional: The merchant account ID. - public var merchantAccountID: String? - + // MARK: - Internal Properties + + let number: String + let expirationMonth: String + let expirationYear: String + let cvv: String + let postalCode: String? + let cardholderName: String? + let firstName: String? + let lastName: String? + let company: 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? + let shouldValidate: Bool + let authenticationInsightRequested: Bool + 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). + /// - 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. + /// - 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, + expirationMonth: String, + expirationYear: String, + cvv: String, + 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 + } + + /// 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) { + self.init(number: "", expirationMonth: "", expirationYear: "", cvv: cvv) + } + // MARK: - Internal Methods func parameters() -> [String: Any] { @@ -124,22 +160,20 @@ import Foundation private func buildCardDictionary(isGraphQL: Bool) -> [String: Any] { var cardDictionary: [String: Any] = [:] - - if let number { + + if !number.isEmpty { cardDictionary["number"] = number } - - if let expirationMonth { + + if !expirationMonth.isEmpty { cardDictionary[isGraphQL ? "expirationMonth" : "expiration_month"] = expirationMonth } - - if let expirationYear { + + if !expirationYear.isEmpty { cardDictionary[isGraphQL ? "expirationYear" : "expiration_year"] = expirationYear } - - if let cvv { - cardDictionary["cvv"] = cvv - } + + cardDictionary["cvv"] = cvv if let cardholderName { cardDictionary[isGraphQL ? "cardholderName" : "cardholder_name"] = cardholderName diff --git a/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift b/UnitTests/BraintreeCardTests/BTCardClient_Tests.swift index 2024e8383b..0900851962 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,12 @@ 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", + cvv: "1234" + ) cardClient.tokenize(card) { (tokenizedCard, error) -> Void in guard let tokenizedCard = tokenizedCard else { @@ -134,10 +139,12 @@ 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", + cvv: "1234" + ) cardClient.tokenize(card) { (tokenizedCard, error) -> Void in XCTAssertNil(tokenizedCard) @@ -177,11 +184,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 +241,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 +319,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 +351,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 +373,12 @@ 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", + cvv: "1234" + ) mockAPIClient.cannedConfigurationResponseBody = BTJSON(value: [:] as [String?: Any]) @@ -392,10 +405,12 @@ 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", + cvv: "1234" + ) let expectation = self.expectation(description: "Tokenized card") cardClient.tokenize(card) { _, _ -> Void in @@ -436,10 +451,12 @@ 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", + cvv: "1234" + ) let expectation = self.expectation(description: "Tokenized card") cardClient.tokenize(card) { _, _ -> Void in @@ -464,13 +481,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 +513,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 +542,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 +570,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 +625,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 +694,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 +744,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" + let card = BTCard( + number: "4111111111111111", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234" + ) let expectation = self.expectation(description: "Tokenized card") cardClient.tokenize(card) { _, _ -> Void in diff --git a/UnitTests/BraintreeCardTests/BTCard_Tests.swift b/UnitTests/BraintreeCardTests/BTCard_Tests.swift index 74ffad696c..edb6c051dc 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", @@ -180,10 +182,9 @@ class BTCard_Tests: XCTestCase { ] ] as [String: Any] as NSObject) } - + 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,14 @@ 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", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234", + authenticationInsightRequested: true, + merchantAccountID: "some id" + ) XCTAssertEqual(card.graphQLParameters() as NSObject, [ "operationName": "TokenizeCreditCard", @@ -209,6 +214,9 @@ class BTCard_Tests: XCTestCase { "variables": [ "input": [ "creditCard": [ + "cvv": "1234", + "expirationMonth": "12", + "expirationYear": "2038", "number": "4111111111111111", ], "options": [ "validate": false ], @@ -221,17 +229,24 @@ 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", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234", + authenticationInsightRequested: false, + merchantAccountID: "some id" + ) XCTAssertEqual(card.graphQLParameters() as NSObject, [ "operationName": "TokenizeCreditCard", "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] ] @@ -239,10 +254,14 @@ 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", + expirationMonth: "12", + expirationYear: "2038", + cvv: "1234", + authenticationInsightRequested: true, + merchantAccountID: nil + ) XCTAssertEqual(card.graphQLParameters() as NSObject, [ "operationName": "TokenizeCreditCard", @@ -250,6 +269,9 @@ class BTCard_Tests: XCTestCase { "variables": [ "input": [ "creditCard": [ + "cvv": "1234", + "expirationMonth": "12", + "expirationYear": "2038", "number": "4111111111111111", ], "options": [ "validate": false ], @@ -260,17 +282,24 @@ 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", + expirationMonth: "12", + expirationYear: "2038", + cvv: "123", + authenticationInsightRequested: false, + merchantAccountID: nil + ) XCTAssertEqual(card.graphQLParameters() as NSObject, [ "operationName": "TokenizeCreditCard", "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] ] diff --git a/V7_MIGRATION.md b/V7_MIGRATION.md index 5e57c5e12f..f0db2bbf2d 100644 --- a/V7_MIGRATION.md +++ b/V7_MIGRATION.md @@ -7,6 +7,7 @@ _Documentation for v7 will be published to https://developer.paypal.com/braintre ## Table of Contents 1. [Supported Versions](#supported-versions) +1. [Card](#card) 1. [Venmo](#venmo) 1. [SEPA Direct Debit](#sepa-direct-debit) 1. [Local Payments](#local-payments) @@ -16,6 +17,9 @@ _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. + ## Venmo All properties within `BTVenmoRequest` can only be accessed on the initializer vs via the dot syntax.