diff --git a/Sources/ForageSDK/Component/ForagePANTextField/ForagePANValidations.swift b/Sources/ForageSDK/Component/ForagePANTextField/ForagePANValidations.swift index 20a29587..9c9018c5 100644 --- a/Sources/ForageSDK/Component/ForagePANTextField/ForagePANValidations.swift +++ b/Sources/ForageSDK/Component/ForagePANTextField/ForagePANValidations.swift @@ -12,8 +12,8 @@ class ForagePANValidations { /// Check ebt card number length static func checkPANLength(_ panNumber: String) -> StateIIN? { if panNumber.count >= 6 { - let pan = panNumber.prefix(6) - return panNumbers.first { $0.panNumber == pan } + let iin = panNumber.prefix(6) + return panNumbers.first { $0.iin == iin } } return nil @@ -21,66 +21,68 @@ class ForagePANValidations { /// List of allowed ebt card numbers and its respective length static var panNumbers: [StateIIN] = [ - StateIIN(state: USState.alabama, panNumber: "507680", panLength: 16), - StateIIN(state: USState.alaska, panNumber: "507695", panLength: 16), - StateIIN(state: USState.arizona, panNumber: "507706", panLength: 16), - StateIIN(state: USState.arkansas, panNumber: "610093", panLength: 16), - StateIIN(state: USState.california, panNumber: "507719", panLength: 16), - StateIIN(state: USState.colorado, panNumber: "507681", panLength: 16), - StateIIN(state: USState.connecticut, panNumber: "600890", panLength: 18), - StateIIN(state: USState.delaware, panNumber: "507713", panLength: 16), - StateIIN(state: USState.districtOfColumbia, panNumber: "507707", panLength: 16), - StateIIN(state: USState.florida, panNumber: "508139", panLength: 16), - StateIIN(state: USState.georgia, panNumber: "508148", panLength: 16), - StateIIN(state: USState.guam, panNumber: "578036", panLength: 16), - StateIIN(state: USState.hawaii, panNumber: "507698", panLength: 16), - StateIIN(state: USState.idaho, panNumber: "507692", panLength: 16), - StateIIN(state: USState.illinois, panNumber: "601453", panLength: 16), - StateIIN(state: USState.indiana, panNumber: "507704", panLength: 16), - StateIIN(state: USState.iowa, panNumber: "627485", panLength: 19), - StateIIN(state: USState.kansas, panNumber: "601413", panLength: 16), - StateIIN(state: USState.kentucky, panNumber: "507709", panLength: 16), - StateIIN(state: USState.louisiana, panNumber: "504476", panLength: 16), - StateIIN(state: USState.maine, panNumber: "507703", panLength: 19), - StateIIN(state: USState.maryland, panNumber: "600528", panLength: 16), - StateIIN(state: USState.massachusetts, panNumber: "600875", panLength: 18), - StateIIN(state: USState.michigan, panNumber: "507711", panLength: 16), - StateIIN(state: USState.minnesota, panNumber: "610423", panLength: 16), - StateIIN(state: USState.mississippi, panNumber: "507718", panLength: 16), - StateIIN(state: USState.missouri, panNumber: "507683", panLength: 16), - StateIIN(state: USState.montana, panNumber: "507714", panLength: 16), - StateIIN(state: USState.nebraska, panNumber: "507716", panLength: 16), - StateIIN(state: USState.nevada, panNumber: "507715", panLength: 16), - StateIIN(state: USState.newHampshire, panNumber: "507701", panLength: 16), - StateIIN(state: USState.newJersey, panNumber: "610434", panLength: 16), - StateIIN(state: USState.newMexico, panNumber: "586616", panLength: 16), - StateIIN(state: USState.newYork, panNumber: "600486", panLength: 19), - StateIIN(state: USState.northCarolina, panNumber: "508161", panLength: 16), - StateIIN(state: USState.ohio, panNumber: "507700", panLength: 16), - StateIIN(state: USState.oklahoma, panNumber: "508147", panLength: 16), - StateIIN(state: USState.oregon, panNumber: "507693", panLength: 16), - StateIIN(state: USState.pennsylvania, panNumber: "600760", panLength: 19), - StateIIN(state: USState.rhodeIsland, panNumber: "507682", panLength: 16), - StateIIN(state: USState.southCarolina, panNumber: "610470", panLength: 16), - StateIIN(state: USState.southDakota, panNumber: "508132", panLength: 16), - StateIIN(state: USState.tennessee, panNumber: "507702", panLength: 16), - StateIIN(state: USState.texas, panNumber: "610098", panLength: 19), - StateIIN(state: USState.usVirginIslands, panNumber: "507721", panLength: 16), - StateIIN(state: USState.utah, panNumber: "601036", panLength: 16), - StateIIN(state: USState.vermont, panNumber: "507705", panLength: 16), - StateIIN(state: USState.virginia, panNumber: "622044", panLength: 16), - StateIIN(state: USState.washington, panNumber: "507710", panLength: 16), - StateIIN(state: USState.westVirginia, panNumber: "507720", panLength: 16), - StateIIN(state: USState.wisconsin, panNumber: "507708", panLength: 16), - StateIIN(state: USState.wyoming, panNumber: "505349", panLength: 16), + StateIIN(state: USState.alabama, iin: "507680", panLengths: [16]), + StateIIN(state: USState.alaska, iin: "507695", panLengths: [16]), + StateIIN(state: USState.arizona, iin: "507706", panLengths: [16]), + StateIIN(state: USState.arkansas, iin: "610093", panLengths: [16]), + StateIIN(state: USState.california, iin: "507719", panLengths: [16]), + StateIIN(state: USState.colorado, iin: "507681", panLengths: [16]), + StateIIN(state: USState.connecticut, iin: "600890", panLengths: [18]), + StateIIN(state: USState.delaware, iin: "507713", panLengths: [16]), + StateIIN(state: USState.districtOfColumbia, iin: "507707", panLengths: [16]), + StateIIN(state: USState.florida, iin: "508139", panLengths: [16]), + StateIIN(state: USState.georgia, iin: "508148", panLengths: [16]), + StateIIN(state: USState.guam, iin: "578036", panLengths: [16]), + StateIIN(state: USState.hawaii, iin: "507698", panLengths: [16]), + StateIIN(state: USState.idaho, iin: "507692", panLengths: [16]), + StateIIN(state: USState.illinois, iin: "601453", panLengths: [16]), + StateIIN(state: USState.indiana, iin: "507704", panLengths: [16]), + StateIIN(state: USState.iowa, iin: "627485", panLengths: [19]), + StateIIN(state: USState.kansas, iin: "601413", panLengths: [16]), + StateIIN(state: USState.kentucky, iin: "507709", panLengths: [16]), + StateIIN(state: USState.louisiana, iin: "504476", panLengths: [16]), + // Maine cards can be 16 or 19 digits long! + StateIIN(state: USState.maine, iin: "507703", panLengths: [16, 19]), + StateIIN(state: USState.maryland, iin: "600528", panLengths: [16]), + StateIIN(state: USState.massachusetts, iin: "600875", panLengths: [18]), + StateIIN(state: USState.michigan, iin: "507711", panLengths: [16]), + StateIIN(state: USState.minnesota, iin: "610423", panLengths: [16]), + StateIIN(state: USState.mississippi, iin: "507718", panLengths: [16]), + StateIIN(state: USState.missouri, iin: "507683", panLengths: [16]), + StateIIN(state: USState.montana, iin: "507714", panLengths: [16]), + StateIIN(state: USState.nebraska, iin: "507716", panLengths: [16]), + StateIIN(state: USState.nevada, iin: "507715", panLengths: [16]), + StateIIN(state: USState.newHampshire, iin: "507701", panLengths: [16]), + StateIIN(state: USState.newJersey, iin: "610434", panLengths: [16]), + StateIIN(state: USState.newMexico, iin: "586616", panLengths: [16]), + StateIIN(state: USState.newYork, iin: "600486", panLengths: [19]), + StateIIN(state: USState.northCarolina, iin: "508161", panLengths: [16]), + StateIIN(state: USState.ohio, iin: "507700", panLengths: [16]), + StateIIN(state: USState.oklahoma, iin: "508147", panLengths: [16]), + StateIIN(state: USState.oregon, iin: "507693", panLengths: [16]), + StateIIN(state: USState.pennsylvania, iin: "600760", panLengths: [19]), + StateIIN(state: USState.rhodeIsland, iin: "507682", panLengths: [16]), + StateIIN(state: USState.southCarolina, iin: "610470", panLengths: [16]), + StateIIN(state: USState.southDakota, iin: "508132", panLengths: [16]), + StateIIN(state: USState.tennessee, iin: "507702", panLengths: [16]), + StateIIN(state: USState.texas, iin: "610098", panLengths: [19]), + StateIIN(state: USState.usVirginIslands, iin: "507721", panLengths: [16]), + StateIIN(state: USState.utah, iin: "601036", panLengths: [16]), + StateIIN(state: USState.vermont, iin: "507705", panLengths: [16]), + StateIIN(state: USState.virginia, iin: "622044", panLengths: [16]), + StateIIN(state: USState.washington, iin: "507710", panLengths: [16]), + StateIIN(state: USState.westVirginia, iin: "507720", panLengths: [16]), + StateIIN(state: USState.wisconsin, iin: "507708", panLengths: [16]), + StateIIN(state: USState.wyoming, iin: "505349", panLengths: [16]), ] } /// StateINN object to identify card object valid data struct StateIIN { let state: USState - let panNumber: String - let panLength: Int + // first 6 digits of EBT card + let iin: String + let panLengths: [Int] } extension StateIIN: Equatable {} diff --git a/Sources/ForageSDK/Component/ForagePANTextField/MaskedUITextField.swift b/Sources/ForageSDK/Component/ForagePANTextField/MaskedUITextField.swift index 0421daef..46392139 100644 --- a/Sources/ForageSDK/Component/ForagePANTextField/MaskedUITextField.swift +++ b/Sources/ForageSDK/Component/ForagePANTextField/MaskedUITextField.swift @@ -89,7 +89,10 @@ class MaskedUITextField: FloatingTextField, ObservableState { // MARK: - Text Handling private func trimTrailingDigits(_ newUnmaskedText: String, stateIIN: StateIIN?) -> String { - let maxPanLength = min(newUnmaskedText.count, stateIIN?.panLength ?? 19) + let maxPanLength = min( + newUnmaskedText.count, + stateIIN?.panLengths.max() ?? 19 + ) return String(newUnmaskedText.prefix(maxPanLength)) } @@ -118,7 +121,7 @@ class MaskedUITextField: FloatingTextField, ObservableState { if let stateIIN = stateIIN { isValid = true - isComplete = newUnmaskedText.count == stateIIN.panLength + isComplete = stateIIN.panLengths.contains(newUnmaskedText.count) derivedCardInfo.usState = stateIIN.state return } @@ -134,12 +137,13 @@ class MaskedUITextField: FloatingTextField, ObservableState { return MaskPattern.unset } if let stateIIN = stateIIN { - switch stateIIN.panLength { - case 16: + if stateIIN.panLengths.contains(16) && stateIIN.panLengths.contains(19) { + return newUnmaskedText.count <= 16 ? MaskPattern.sixteenDigits : MaskPattern.nineteenDigits + } else if stateIIN.panLengths.contains(16) { return MaskPattern.sixteenDigits - case 18: + } else if stateIIN.panLengths.contains(18) { return MaskPattern.eighteenDigits - default: + } else if stateIIN.panLengths.contains(19) { return MaskPattern.nineteenDigits } } diff --git a/Tests/ForageSDKTests/ForagePANTextFieldTests.swift b/Tests/ForageSDKTests/ForagePANTextFieldTests.swift index 3d98d100..fe72ea60 100644 --- a/Tests/ForageSDKTests/ForagePANTextFieldTests.swift +++ b/Tests/ForageSDKTests/ForagePANTextFieldTests.swift @@ -78,6 +78,31 @@ final class ForagePANTextFieldTests: XCTestCase { XCTAssertEqual(validTextField.derivedCardInfo.usState, .southDakota) } + func test_maine_shouldBeValidCardFor16or19Digits() { + let validTextField = ForagePANTextField() + + // 16 digits + validTextField.enhancedTextField.text = "5077031234123412" + validTextField.enhancedTextField.textFieldDidChange() + XCTAssertEqual(validTextField.enhancedTextField.actualPAN, "5077031234123412") + + XCTAssertEqual(validTextField.enhancedTextField.text, "5077 0312 3412 3412") + XCTAssertTrue(validTextField.isValid) + XCTAssertTrue(validTextField.isComplete) + XCTAssertFalse(validTextField.isEmpty) + XCTAssertEqual(validTextField.derivedCardInfo.usState, .maine) + + validTextField.enhancedTextField.text = "5077031234123412345" + validTextField.enhancedTextField.textFieldDidChange() + XCTAssertEqual(validTextField.enhancedTextField.actualPAN, "5077031234123412345") + + XCTAssertEqual(validTextField.enhancedTextField.text, "507703 1234 1234 123 45") + XCTAssertTrue(validTextField.isValid) + XCTAssertTrue(validTextField.isComplete) + XCTAssertFalse(validTextField.isEmpty) + XCTAssertEqual(validTextField.derivedCardInfo.usState, .maine) + } + func test_textField_enterNumericString_shouldReturnTrue() { let changesAllowed = foragePANTextField.textField(UITextField(), shouldChangeCharactersIn: NSRange(), replacementString: "1234") diff --git a/Tests/ForageSDKTests/ForagePANValidationsTests.swift b/Tests/ForageSDKTests/ForagePANValidationsTests.swift index 1b2031c8..74041f1e 100644 --- a/Tests/ForageSDKTests/ForagePANValidationsTests.swift +++ b/Tests/ForageSDKTests/ForagePANValidationsTests.swift @@ -22,20 +22,27 @@ final class ForagePANValidationsTests: XCTestCase { func test_checkPANLength_withValid6digitsPan_shouldReturnState() { let stateINN = ForagePANValidations.checkPANLength("507680") - let expectedResult = StateIIN(state: .alabama, panNumber: "507680", panLength: 16) + let expectedResult = StateIIN(state: .alabama, iin: "507680", panLengths: [16]) XCTAssertEqual(expectedResult, stateINN) } func test_checkPANLength_givenValidCard_shouldRespectMaxDigits() { let stateINN = ForagePANValidations.checkPANLength("507680") - let expectedResult = 16 - XCTAssertEqual(expectedResult, stateINN?.panLength) + let expectedResult = [16] + XCTAssertEqual(expectedResult, stateINN?.panLengths) + } + + func test_checkPANLength_givenMainCard_shouldSupport16and19DigitCards() { + let stateINN = ForagePANValidations.checkPANLength("507703") + let expectedResult = [16, 19] + XCTAssertEqual(expectedResult, stateINN?.panLengths) + XCTAssertEqual(USState.maine, stateINN?.state) } func test_checkPANLength_givenValidCard_shouldRespectMaxDigits_forDifferentCards() { let stateINN = ForagePANValidations.checkPANLength("600890") - let expectedResult = 18 - XCTAssertEqual(expectedResult, stateINN?.panLength) + let expectedResult = [18] + XCTAssertEqual(expectedResult, stateINN?.panLengths) } func test_panNumbers_shouldHaveAllUsaStates() { diff --git a/Tests/ForageSDKTests/ForagePINTextFieldTests.swift b/Tests/ForageSDKTests/ForagePINTextFieldTests.swift index 6cea7f95..d61eeaa8 100644 --- a/Tests/ForageSDKTests/ForagePINTextFieldTests.swift +++ b/Tests/ForageSDKTests/ForagePINTextFieldTests.swift @@ -207,7 +207,7 @@ final class ForagePINTextFieldTests: XCTestCase { let textColor = foragePinTextField.textColor XCTAssertEqual(color, textColor) - + // Test RosettaPINTextField text color let rosettaPINTextField = RosettaPINTextField() rosettaPINTextField.textColor = .blue diff --git a/Tests/ForageSDKTests/MaskedUITextFieldTests.swift b/Tests/ForageSDKTests/MaskedUITextFieldTests.swift index ffb50105..c97823e2 100644 --- a/Tests/ForageSDKTests/MaskedUITextFieldTests.swift +++ b/Tests/ForageSDKTests/MaskedUITextFieldTests.swift @@ -287,6 +287,37 @@ final class MaskedUITextFieldTests: XCTestCase { XCTAssertEqual(maskedTextField.actualPAN, "9999123412341234123") } + func testMasking_shouldMaskForMultiplePanLengths() { + // Maine cards can be 16 or 19 digits long + + func assertMaskedTextField(input: String, expectedOutput: String) { + maskedTextField.text = input + maskedTextField.textFieldDidChange() + XCTAssertEqual(maskedTextField.text, expectedOutput) + } + + assertMaskedTextField( + input: "507703111111123", + expectedOutput: "5077 0311 1111 123" + ) // 15 digits + assertMaskedTextField( + input: "5077031111111234", + expectedOutput: "5077 0311 1111 1234" + ) // 16 digits + assertMaskedTextField( + input: "50770311111112345", + expectedOutput: "507703 1111 1112 345" + ) // 17 digits + assertMaskedTextField( + input: "507703111111123456", + expectedOutput: "507703 1111 1112 345 6" + ) // 18 digits + assertMaskedTextField( + input: "5077031111111234567", + expectedOutput: "507703 1111 1112 345 67" + ) // 19 digits + } + // MARK: Masking with backspace func testMasking_backspaceAtEnd_shouldRemoveLastChar() {