diff --git a/VGSCollectSDK.podspec b/VGSCollectSDK.podspec index 2ff48073..39792d56 100644 --- a/VGSCollectSDK.podspec +++ b/VGSCollectSDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'VGSCollectSDK' - spec.version = '1.2.2' + spec.version = '1.2.3' spec.summary = 'VGS Collect - is a product suite that allows customers to collect information securely without possession of it.' spec.swift_version = '5.0' spec.description = <<-DESC diff --git a/framework/Sources/VGSFramework/Core/Utils.swift b/framework/Sources/VGSFramework/Core/Utils.swift new file mode 100644 index 00000000..cf92a3e6 --- /dev/null +++ b/framework/Sources/VGSFramework/Core/Utils.swift @@ -0,0 +1,55 @@ +// +// Utils.swift +// VGSFramework +// +// Created by Dima on 10.03.2020. +// Copyright © 2020 VGS. All rights reserved. +// + +import Foundation + +/// Merge two objects and their nested values. Returns [String: Any]. Values in d2 will override values in d1 if keys are same!!!! +func deepMerge(_ d1: [String: Any], _ d2: [String: Any]) -> [String: Any] { + var result = d1 + for (k2, v2) in d2 { + if let v2 = v2 as? [String: Any], let v1 = result[k2] as? [String: Any] { + result[k2] = deepMerge(v1, v2) + } else { + result[k2] = v2 + } + } + return result +} + +/// Convert string key with separator into dictionary. Ex.: user.name : "Joe" -> ["user": ["name": " Joe"]] +func mapStringKVOToDictionary(key: String, value: Any, separator: String.Element) -> [String: Any] { + let components = key.split(separator: separator).map { String($0) } + + var dict = [String: Any]() + // swiftlint:disable identifier_name + var i = components.count - 1 + + while i >= 0 { + if i == components.count - 1 { + dict[components[i]] = value + } else { + let newDict = [components[i]: dict] + dict = newDict + } + i -= 1 + } + return dict +} + +extension Dictionary { + /// Resturn JSON string representation of dictionary with sorted keys + @available(iOS 11.0, *) + var jsonStringRepresentation: String? { + guard let theJSONData = try? JSONSerialization.data(withJSONObject: self, + options: [.prettyPrinted, .sortedKeys]) else { + return nil + } + + return String(data: theJSONData, encoding: .ascii) + } +} diff --git a/framework/Sources/VGSFramework/Core/VGSCollect+internal.swift b/framework/Sources/VGSFramework/Core/VGSCollect+internal.swift new file mode 100644 index 00000000..daa8d9e3 --- /dev/null +++ b/framework/Sources/VGSFramework/Core/VGSCollect+internal.swift @@ -0,0 +1,72 @@ +// +// VGSCollect+internal.swift +// VGSFramework +// +// Created by Dima on 10.03.2020. +// Copyright © 2020 VGS. All rights reserved. +// + +import Foundation + +internal extension VGSCollect { + + /// Validate tenant id + class func tenantIDValid(_ tenantId: String) -> Bool { + return tenantId.isAlphaNumeric + } + + /// Validate stored textfields input data + func validateStoredInputData() -> Error? { + return validate(storage.elements) + } + + /// Validate specific textfields input data + func validate(_ input: [VGSTextField]) -> Error? { + var isRequiredErrorFields = [String]() + var isRequiredValidOnlyErrorFields = [String]() + + for textField in input { + if textField.isRequired, textField.text.isNilOrEmpty { + isRequiredErrorFields.append(textField.fieldName) + } + if textField.isRequiredValidOnly && !textField.state.isValid { + isRequiredValidOnlyErrorFields.append(textField.fieldName) + } + } + + if isRequiredErrorFields.count > 0 { + return VGSError(type: .inputDataRequired, userInfo: VGSErrorInfo(key: VGSSDKErrorInputDataRequired, description: "Input data can't be nil or empty", extraInfo: ["fields": isRequiredErrorFields])) + } else if isRequiredValidOnlyErrorFields.count > 0 { + return VGSError(type: .inputDataRequiredValidOnly, userInfo: VGSErrorInfo(key: VGSSDKErrorInputDataRequiredValid, description: "Input data should be valid only", extraInfo: ["fields": isRequiredValidOnlyErrorFields])) + } + return nil + } + + /// Turns textfields data saved in Storage and extra data in format ready to submit + func mapStoredInputDataForSubmit(with extraData: [String: Any]? = nil) -> [String: Any] { + + let textFieldsData: BodyData = storage.elements.reduce(into: BodyData()) { (dict, element) in + dict[element.fieldName] = element.rawText + } + + var body = mapInputFieldsDataToDictionary(textFieldsData) + + if let customData = extraData, customData.count != 0 { + // NOTE: If there are similar keys on same level, body values will override customvalues values for that keys + body = deepMerge(customData, body) + } + + return body + } + + /// Maps textfield string key with separator into nesting Dictionary + func mapInputFieldsDataToDictionary(_ body: [String: Any]) -> [String: Any] { + var resultDict = [String: Any]() + for (key, value) in body { + let mappedDict = mapStringKVOToDictionary(key: key, value: value, separator: ".") + let newDict = deepMerge(resultDict, mappedDict) + resultDict = newDict + } + return resultDict + } +} diff --git a/framework/Sources/VGSFramework/Core/VGSCollect.swift b/framework/Sources/VGSFramework/Core/VGSCollect.swift index b0fec96b..15a2fd3f 100644 --- a/framework/Sources/VGSFramework/Core/VGSCollect.swift +++ b/framework/Sources/VGSFramework/Core/VGSCollect.swift @@ -84,64 +84,21 @@ extension VGSCollect { extension VGSCollect { public func submit(path: String, method: HTTPMethod = .post, extraData: [String: Any]? = nil, completion block:@escaping (_ data: JsonData?, _ error: Error?) -> Void) { - let elements = storage.elements - if let error = validate(elements) { + if let error = validateStoredInputData() { block(nil, error) return } - var body: BodyData = elements.reduce(into: BodyData()) { (dict, element) in - dict[element.fieldName] = element.rawText - } - - if extraData?.count != 0 { - extraData?.forEach { (key, value) in body[key] = value } - } + let body = mapStoredInputDataForSubmit(with: extraData) apiClient.sendRequest(path: path, method: method, value: body) { (json, error) in - if let error = error { block(json, error) return } else { - let allKeys = json?.keys - allKeys?.forEach({ key in - if let element = elements.filter({ $0.fieldName == key }).first { - element.token = json?[key] as? String - } - }) block(json, nil) return } } } } - -// MARK: - Validation -internal extension VGSCollect { - - func validate(_ input: [VGSTextField]) -> Error? { - var isRequiredErrorFields = [String]() - var isRequiredValidOnlyErrorFields = [String]() - - for textField in input { - if textField.isRequired, textField.text.isNilOrEmpty { - isRequiredErrorFields.append(textField.fieldName) - } - if textField.isRequiredValidOnly && !textField.state.isValid { - isRequiredValidOnlyErrorFields.append(textField.fieldName) - } - } - - if isRequiredErrorFields.count > 0 { - return VGSError(type: .inputDataRequired, userInfo: VGSErrorInfo(key: VGSSDKErrorInputDataRequired, description: "Input data can't be nil or empty", extraInfo: ["fields": isRequiredErrorFields])) - } else if isRequiredValidOnlyErrorFields.count > 0 { - return VGSError(type: .inputDataRequiredValidOnly, userInfo: VGSErrorInfo(key: VGSSDKErrorInputDataRequiredValid, description: "Input data should be valid only", extraInfo: ["fields": isRequiredValidOnlyErrorFields])) - } - return nil - } - - class func tenantIDValid(_ tenantId: String) -> Bool { - return tenantId.isAlphaNumeric - } -} diff --git a/framework/Tests/FrameworkTests/UtilsTest.swift b/framework/Tests/FrameworkTests/UtilsTest.swift new file mode 100644 index 00000000..9e46f312 --- /dev/null +++ b/framework/Tests/FrameworkTests/UtilsTest.swift @@ -0,0 +1,124 @@ +// +// UtilsTest.swift +// VGSFramework +// +// Created by Dima on 10.03.2020. +// Copyright © 2020 VGS. All rights reserved. +// + +import XCTest +@testable import VGSFramework + +class UtilsTest: XCTestCase { + + func testMapStringKVOToDictionary() { + let stringToMap = "user.data.card_number" + let val = "any" + let separator: String.Element = "." + let expectedResult = [ + "user": [ + "data": [ + "card_number": val + ] + ] + ] + + let result = mapStringKVOToDictionary(key: stringToMap, value: val, separator: separator) + XCTAssertTrue(result.jsonStringRepresentation == expectedResult.jsonStringRepresentation) + } + + func testMapStringKVOToDictionaryWithExtraSeparators() { + let stringToMap = ".user.data...card_number." + let val = "any" + let separator: String.Element = "." + let expectedResult = [ + "user": [ + "data": [ + "card_number": val + ] + ] + ] + + let result = mapStringKVOToDictionary(key: stringToMap, value: val, separator: separator) + XCTAssertTrue(result.jsonStringRepresentation == expectedResult.jsonStringRepresentation) + } + + func testMapStringKVOToDictionaryWithNoSeparators() { + let stringToMap = "card_number" + let val = ["anyValue": "anyKey"] + let separator: String.Element = "." + let expectedResult = [ + "card_number": val + ] + + let result = mapStringKVOToDictionary(key: stringToMap, value: val, separator: separator) + XCTAssertTrue(result.jsonStringRepresentation == expectedResult.jsonStringRepresentation) + } + + func testDeepMergeWithoutSimilarKeys() { + let d1 = [ + "user": [ + "data": [ + "card_number": "any" + ] + ] + ] + + let d2 = [ + "data": [ + "user": [ + "id": "any" + ] + ] + ] + + let expectedResult = [ + "user": [ + "data": [ + "card_number": "any" + ] + ], + "data": [ + "user": [ + "id": "any" + ] + ] + ] + let result = deepMerge(d1, d2) + XCTAssertTrue(expectedResult.jsonStringRepresentation == result.jsonStringRepresentation) + } + + func testDeepMergeWithSimilarKeys() { + let d1 = [ + "user": [ + "data": [ + "card_number": "any", + "Id": "CapitalId" + ], + "accountNumber": 1111 + ] + ] + + let d2 = [ + "user": [ + "data": [ + "id": "anyId" + ], + "accountNumber": 2222 + ] + ] + + let expectedResult = [ + "user": [ + "data": [ + "card_number": "any", + "id": "anyId", + "Id": "CapitalId" + ], + "accountNumber": 2222 + ] + ] + let result = deepMerge(d1, d2) + XCTAssertTrue(expectedResult.jsonStringRepresentation == result.jsonStringRepresentation) + } +} diff --git a/framework/Tests/FrameworkTests/CollectorTests.swift b/framework/Tests/FrameworkTests/VGSCollectTest+Validation.swift similarity index 84% rename from framework/Tests/FrameworkTests/CollectorTests.swift rename to framework/Tests/FrameworkTests/VGSCollectTest+Validation.swift index 14ecf42f..b29adeac 100644 --- a/framework/Tests/FrameworkTests/CollectorTests.swift +++ b/framework/Tests/FrameworkTests/VGSCollectTest+Validation.swift @@ -1,67 +1,41 @@ // -// FormTests.swift +// VGSCollectTest+Validation.swift // FrameworkTests // -// Created by Vitalii Obertynskyi on 9/17/19. -// Copyright © 2019 Vitalii Obertynskyi. All rights reserved. +// Created by Dima on 11.03.2020. +// Copyright © 2020 VGS. All rights reserved. // import XCTest @testable import VGSFramework -class CollectorTests: XCTestCase { +class VGSCollectValidationTests: XCTestCase { + var collector: VGSCollect! override func setUp() { collector = VGSCollect(id: "tntva5wfdrp") } - + override func tearDown() { collector = nil } - - func testEnvByDefault() { - let host = collector.apiClient.baseURL.host ?? "" - XCTAssertTrue(host.contains("sandbox")) - } - - func testLiveEnvirinment() { - let liveForm = VGSCollect(id: "testID", environment: .live) - let host = liveForm.apiClient.baseURL.host ?? "" - XCTAssertTrue(host.contains("live")) - } - - func testCustomHeader() { - let headerKey = "costom-header" - let headerValue = "custom header value" - - collector.customHeaders = [ - headerKey: headerValue - ] - - XCTAssertNotNil(collector.customHeaders) - XCTAssert(collector.customHeaders![headerKey] == headerValue) - } - - func testJail() { - XCTAssertFalse(VGSCollect.isJailbroken()) - } - func testCanOpen() { - let path = "." - XCTAssertTrue(VGSCollect.canOpen(path: path)) + func testTenantIdValideReturnsFalse() { + XCTAssertFalse(VGSCollect.tenantIDValid("")) + XCTAssertFalse(VGSCollect.tenantIDValid(" ")) + XCTAssertFalse(VGSCollect.tenantIDValid("tnt_123456789")) + XCTAssertFalse(VGSCollect.tenantIDValid("tnt 123456789")) + XCTAssertFalse(VGSCollect.tenantIDValid("tnt@123456789")) + XCTAssertFalse(VGSCollect.tenantIDValid("tenant/tenant")) + XCTAssertFalse(VGSCollect.tenantIDValid("tenant:tenant")) } - func testRegistrationTextField() { - let config = VGSConfiguration(collector: collector, fieldName: "test") - let tf = VGSTextField() - tf.configuration = config - - XCTAssertTrue(collector.storage.elements.count == 1) - - collector.unregisterTextFields(textField: [tf]) - - XCTAssertTrue(collector.storage.elements.count == 0) + func testTenantIdValideReturnsTrue() { + XCTAssertTrue(VGSCollect.tenantIDValid("1234567890")) + XCTAssertTrue(VGSCollect.tenantIDValid("abcdefghijklmnopqarstuvwxyz")) + XCTAssertTrue(VGSCollect.tenantIDValid("tnt1234567890")) + XCTAssertTrue(VGSCollect.tenantIDValid("1234567890tnt")) } func testSubmitValidRequiredFieldsReturnsNotNil() { diff --git a/framework/Tests/FrameworkTests/VGSCollectTests.swift b/framework/Tests/FrameworkTests/VGSCollectTests.swift index e44ccfc3..6242923c 100644 --- a/framework/Tests/FrameworkTests/VGSCollectTests.swift +++ b/framework/Tests/FrameworkTests/VGSCollectTests.swift @@ -1,36 +1,154 @@ // -// VGSCollectTests.swift +// FormTests.swift // FrameworkTests // -// Created by Dima on 13.01.2020. -// Copyright © 2020 VGS. All rights reserved. +// Created by Vitalii Obertynskyi on 9/17/19. +// Copyright © 2019 Vitalii Obertynskyi. All rights reserved. // import XCTest @testable import VGSFramework class VGSCollectTests: XCTestCase { + var collector: VGSCollect! - var vgsCollect: VGSCollect? + override func setUp() { + collector = VGSCollect(id: "tntva5wfdrp") + } override func tearDown() { - vgsCollect = nil + collector = nil + } + + func testEnvByDefault() { + let host = collector.apiClient.baseURL.host ?? "" + XCTAssertTrue(host.contains("sandbox")) + } + + func testLiveEnvirinment() { + let liveForm = VGSCollect(id: "testID", environment: .live) + let host = liveForm.apiClient.baseURL.host ?? "" + XCTAssertTrue(host.contains("live")) + } + + func testCustomHeader() { + let headerKey = "costom-header" + let headerValue = "custom header value" + + collector.customHeaders = [ + headerKey: headerValue + ] + + XCTAssertNotNil(collector.customHeaders) + XCTAssert(collector.customHeaders![headerKey] == headerValue) } - func testTenantIdValideReturnsFalse() { - XCTAssertFalse(VGSCollect.tenantIDValid("")) - XCTAssertFalse(VGSCollect.tenantIDValid(" ")) - XCTAssertFalse(VGSCollect.tenantIDValid("tnt_123456789")) - XCTAssertFalse(VGSCollect.tenantIDValid("tnt 123456789")) - XCTAssertFalse(VGSCollect.tenantIDValid("tnt@123456789")) - XCTAssertFalse(VGSCollect.tenantIDValid("tenant/tenant")) - XCTAssertFalse(VGSCollect.tenantIDValid("tenant:tenant")) + func testJail() { + XCTAssertFalse(VGSCollect.isJailbroken()) } - func testTenantIdValideReturnsTrue() { - XCTAssertTrue(VGSCollect.tenantIDValid("1234567890")) - XCTAssertTrue(VGSCollect.tenantIDValid("abcdefghijklmnopqarstuvwxyz")) - XCTAssertTrue(VGSCollect.tenantIDValid("tnt1234567890")) - XCTAssertTrue(VGSCollect.tenantIDValid("1234567890tnt")) + func testCanOpen() { + let path = "." + XCTAssertTrue(VGSCollect.canOpen(path: path)) + } + + func testRegistrationTextField() { + let config = VGSConfiguration(collector: collector, fieldName: "test") + let tf = VGSTextField() + tf.configuration = config + + XCTAssertTrue(collector.storage.elements.count == 1) + + collector.unregisterTextFields(textField: [tf]) + + XCTAssertTrue(collector.storage.elements.count == 0) + } + + func testCustomJsonMapping() { + let cardConfiguration = VGSConfiguration(collector: collector, fieldName: "user.card_data.card_number") + let cardTextField = VGSCardTextField() + cardTextField.configuration = cardConfiguration + cardTextField.textField.text = "4111 1111 1111 1112" + + let cvcConfiguration = VGSConfiguration(collector: collector, fieldName: "user.card_data.cvc") + let cvcTextField = VGSTextField() + cvcTextField.configuration = cvcConfiguration + cvcTextField.textField.text = "123" + + let nameConfiguration = VGSConfiguration(collector: collector, fieldName: "user.name") + let nameTextField = VGSTextField() + nameTextField.configuration = nameConfiguration + nameTextField.textField.text = "Joe Business" + + let ssnConfiguration = VGSConfiguration(collector: collector, fieldName: "ssn") + let ssnTextField = VGSTextField() + ssnTextField.configuration = ssnConfiguration + ssnTextField.textField.text = "UA411111111111XZ" + + let result = collector.mapStoredInputDataForSubmit(with: nil) + let expectedResult: [String: Any] = [ + "user": [ + "card_data": [ + "card_number": "4111 1111 1111 1112", + "cvc": "123" + ], + "name": "Joe Business" + ], + "ssn": "UA411111111111XZ" + ] + XCTAssertTrue(expectedResult.jsonStringRepresentation == result.jsonStringRepresentation) + } + + func testCustomJsonMappingWithExtraData() { + let cardConfiguration = VGSConfiguration(collector: collector, fieldName: "user.card_data.card_number") + let cardTextField = VGSCardTextField() + cardTextField.configuration = cardConfiguration + cardTextField.textField.text = "4111 1111 1111 1112" + + let cvcConfiguration = VGSConfiguration(collector: collector, fieldName: "user.card_data.cvc") + let cvcTextField = VGSTextField() + cvcTextField.configuration = cvcConfiguration + cvcTextField.textField.text = "123" + + let nameConfiguration = VGSConfiguration(collector: collector, fieldName: "user.name") + let nameTextField = VGSTextField() + nameTextField.configuration = nameConfiguration + nameTextField.textField.text = "Joe Business" + + let ssnConfiguration = VGSConfiguration(collector: collector, fieldName: "ssn") + let ssnTextField = VGSTextField() + ssnTextField.configuration = ssnConfiguration + ssnTextField.textField.text = "UA411111111111XZ" + + let expDateConfiguration = VGSConfiguration(collector: collector, fieldName: "date") + let expDateTextField = VGSTextField() + expDateTextField.configuration = expDateConfiguration + expDateTextField.textField.text = "2030-03-30" + + let extraData: [String: Any] = [ + "user": [ + "id": 1234567890, + "name": "unknown" + ], + "ssn": "not valid", + "Date": "05-05-1990" + ] + + let result = collector.mapStoredInputDataForSubmit(with: extraData) + + let expectedResult: [String: Any] = [ + "user": [ + "card_data": [ + "card_number": "4111 1111 1111 1112", + "cvc": "123" + ], + "name": "Joe Business", + "id": 1234567890 + ], + "ssn": "UA411111111111XZ", + "date": "2030-03-30", + "Date": "05-05-1990" + ] + XCTAssertTrue(expectedResult.jsonStringRepresentation == result.jsonStringRepresentation) } } diff --git a/framework/VGSFramework.xcodeproj/project.pbxproj b/framework/VGSFramework.xcodeproj/project.pbxproj index 9a5a2418..f3f874ee 100644 --- a/framework/VGSFramework.xcodeproj/project.pbxproj +++ b/framework/VGSFramework.xcodeproj/project.pbxproj @@ -7,10 +7,16 @@ objects = { /* Begin PBXBuildFile section */ + 32059CD02417E02E003E9481 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32059CCF2417E02E003E9481 /* Utils.swift */; }; + 32059CD12417E02E003E9481 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32059CCF2417E02E003E9481 /* Utils.swift */; }; + 32059CD42417EACC003E9481 /* UtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32059CD22417EACC003E9481 /* UtilsTest.swift */; }; 320E885323A94E1800A9C1FA /* CharacterSet+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 320E885223A94E1800A9C1FA /* CharacterSet+extension.swift */; }; + 321300172417F2B70062FEF0 /* VGSTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05F9442316CE5A000EAF52 /* VGSTextField.swift */; }; + 321300182417F2C70062FEF0 /* VGSCollect.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05F93B2316CE5A000EAF52 /* VGSCollect.swift */; }; + 321300192417F2E80062FEF0 /* VGSCollect+internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 323105622417DF07009C3360 /* VGSCollect+internal.swift */; }; + 3213001B24190BE50062FEF0 /* VGSCollectTest+Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3213001A24190BE50062FEF0 /* VGSCollectTest+Validation.swift */; }; 3219D73D2404279700F4A7E5 /* VGSError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3219D73C2404279700F4A7E5 /* VGSError.swift */; }; 3219D73E2404279700F4A7E5 /* VGSError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3219D73C2404279700F4A7E5 /* VGSError.swift */; }; - 3222A47B23CC6A4D00836A8D /* VGSCollectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3222A47A23CC6A4D00836A8D /* VGSCollectTests.swift */; }; 327C9E142407EF92004C641C /* VGSErrorInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 327C9E132407EF92004C641C /* VGSErrorInfo.swift */; }; 327C9E162407F43A004C641C /* VGSErrorInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 327C9E152407F43A004C641C /* VGSErrorInfoKey.swift */; }; 327C9E172407FC58004C641C /* VGSErrorInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 327C9E132407EF92004C641C /* VGSErrorInfo.swift */; }; @@ -25,10 +31,8 @@ D06AC61B18447F6EC1968FDE /* Pods_VGSFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F581972075FB28A79E9D585 /* Pods_VGSFramework.framework */; }; FD05F9472316CE5A000EAF52 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05F9392316CE5A000EAF52 /* Storage.swift */; }; FD05F9482316CE5A000EAF52 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05F93A2316CE5A000EAF52 /* Enums.swift */; }; - FD05F9492316CE5A000EAF52 /* VGSCollect.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05F93B2316CE5A000EAF52 /* VGSCollect.swift */; }; FD05F94A2316CE5A000EAF52 /* VGSConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05F93C2316CE5A000EAF52 /* VGSConfiguration.swift */; }; FD05F94C2316CE5A000EAF52 /* VGSFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FD05F93F2316CE5A000EAF52 /* VGSFramework.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FD05F94F2316CE5A000EAF52 /* VGSTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05F9442316CE5A000EAF52 /* VGSTextField.swift */; }; FD1489FC232D4F6B00FD7781 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1489FB232D4F6B00FD7781 /* APIClient.swift */; }; FD1489FF232D4F8000FD7781 /* MaskedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1489FE232D4F8000FD7781 /* MaskedTextField.swift */; }; FD148A07232EC53600FD7781 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD148A06232EC53600FD7781 /* State.swift */; }; @@ -37,7 +41,7 @@ FD1BE48723462F1A006D8658 /* CardNumerTextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD24956F2330E834009024E6 /* CardNumerTextFieldTests.swift */; }; FD1BE48823462F1E006D8658 /* ExpDateTextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE8907A2333A16A00FA170D /* ExpDateTextFieldTests.swift */; }; FD1BE48923462F21006D8658 /* CVVTextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE8907C2333A1D400FA170D /* CVVTextFieldTests.swift */; }; - FD24955E2330CC62009024E6 /* CollectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD24955D2330CC62009024E6 /* CollectorTests.swift */; }; + FD24955E2330CC62009024E6 /* VGSCollectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD24955D2330CC62009024E6 /* VGSCollectTests.swift */; }; FD24955F2330DA39009024E6 /* VGSFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD12B9AF2304619100B670DD /* VGSFramework.framework */; }; FD2495692330E313009024E6 /* SwiftLuhn.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2495642330E313009024E6 /* SwiftLuhn.swift */; }; FD24956A2330E313009024E6 /* String+SwiftLuhn.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2495652330E313009024E6 /* String+SwiftLuhn.swift */; }; @@ -71,9 +75,12 @@ 0F581972075FB28A79E9D585 /* Pods_VGSFramework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_VGSFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 10E5359F4D38E4EE0F07C6BD /* Pods-VGSFramework-FrameworkTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VGSFramework-FrameworkTests.debug.xcconfig"; path = "Target Support Files/Pods-VGSFramework-FrameworkTests/Pods-VGSFramework-FrameworkTests.debug.xcconfig"; sourceTree = ""; }; 1CF8407A4E7AC1C8DE5FDF53 /* Pods-VGSFramework.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VGSFramework.release.xcconfig"; path = "Target Support Files/Pods-VGSFramework/Pods-VGSFramework.release.xcconfig"; sourceTree = ""; }; + 32059CCF2417E02E003E9481 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; + 32059CD22417EACC003E9481 /* UtilsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilsTest.swift; sourceTree = ""; }; 320E885223A94E1800A9C1FA /* CharacterSet+extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CharacterSet+extension.swift"; sourceTree = ""; }; + 3213001A24190BE50062FEF0 /* VGSCollectTest+Validation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VGSCollectTest+Validation.swift"; sourceTree = ""; }; 3219D73C2404279700F4A7E5 /* VGSError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VGSError.swift; sourceTree = ""; }; - 3222A47A23CC6A4D00836A8D /* VGSCollectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VGSCollectTests.swift; sourceTree = ""; }; + 323105622417DF07009C3360 /* VGSCollect+internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VGSCollect+internal.swift"; sourceTree = ""; }; 327C9E132407EF92004C641C /* VGSErrorInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VGSErrorInfo.swift; sourceTree = ""; }; 327C9E152407F43A004C641C /* VGSErrorInfoKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VGSErrorInfoKey.swift; sourceTree = ""; }; 328B5C6F23C8DA2600A39515 /* String+extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+extension.swift"; sourceTree = ""; }; @@ -102,7 +109,7 @@ FD1B44652327D9B0009AA04A /* VGSTextField+state.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VGSTextField+state.swift"; sourceTree = ""; }; FD2495512330CB49009024E6 /* FrameworkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FrameworkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; FD2495552330CB49009024E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - FD24955D2330CC62009024E6 /* CollectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectorTests.swift; sourceTree = ""; }; + FD24955D2330CC62009024E6 /* VGSCollectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VGSCollectTests.swift; sourceTree = ""; }; FD2495642330E313009024E6 /* SwiftLuhn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftLuhn.swift; sourceTree = ""; }; FD2495652330E313009024E6 /* String+SwiftLuhn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SwiftLuhn.swift"; sourceTree = ""; }; FD2495672330E313009024E6 /* VGSValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VGSValidation.swift; sourceTree = ""; }; @@ -210,9 +217,11 @@ FD05F93C2316CE5A000EAF52 /* VGSConfiguration.swift */, FD05F93B2316CE5A000EAF52 /* VGSCollect.swift */, FD1B445F2327A6A4009AA04A /* VGSCollect+extension.swift */, + 323105622417DF07009C3360 /* VGSCollect+internal.swift */, 3219D73C2404279700F4A7E5 /* VGSError.swift */, 327C9E132407EF92004C641C /* VGSErrorInfo.swift */, 327C9E152407F43A004C641C /* VGSErrorInfoKey.swift */, + 32059CCF2417E02E003E9481 /* Utils.swift */, ); path = Core; sourceTree = ""; @@ -269,10 +278,11 @@ isa = PBXGroup; children = ( FDE890802333A75800FA170D /* Text Fields Tests */, - FD24955D2330CC62009024E6 /* CollectorTests.swift */, FDE890812333AC6D00FA170D /* ApiClientTests.swift */, FDF696E42346461D00063507 /* StorageTests.swift */, - 3222A47A23CC6A4D00836A8D /* VGSCollectTests.swift */, + FD24955D2330CC62009024E6 /* VGSCollectTests.swift */, + 3213001A24190BE50062FEF0 /* VGSCollectTest+Validation.swift */, + 32059CD22417EACC003E9481 /* UtilsTest.swift */, FD2495552330CB49009024E6 /* Info.plist */, ); path = FrameworkTests; @@ -561,27 +571,29 @@ FD1B44662327D9B0009AA04A /* VGSTextField+state.swift in Sources */, 327C9E162407F43A004C641C /* VGSErrorInfoKey.swift in Sources */, FD24956B2330E313009024E6 /* VGSValidation.swift in Sources */, + 32059CD02417E02E003E9481 /* Utils.swift in Sources */, FD24956A2330E313009024E6 /* String+SwiftLuhn.swift in Sources */, FD05F94A2316CE5A000EAF52 /* VGSConfiguration.swift in Sources */, 328B5C7023C8DA2600A39515 /* String+extension.swift in Sources */, FD2495692330E313009024E6 /* SwiftLuhn.swift in Sources */, FD6F5657238E7FBB00C24123 /* CardType+icon.swift in Sources */, 32FD980C23D20E11005FA989 /* VGSCardIOHandler.swift in Sources */, - FD05F94F2316CE5A000EAF52 /* VGSTextField.swift in Sources */, - FD05F9492316CE5A000EAF52 /* VGSCollect.swift in Sources */, FD05F9482316CE5A000EAF52 /* Enums.swift in Sources */, FD1489FC232D4F6B00FD7781 /* APIClient.swift in Sources */, FD1B44612327A6C6009AA04A /* VGSCollect+extension.swift in Sources */, FD24956C2330E313009024E6 /* VGSValidation+type.swift in Sources */, FD05F9472316CE5A000EAF52 /* Storage.swift in Sources */, + 321300182417F2C70062FEF0 /* VGSCollect.swift in Sources */, FDE7447F238AF13F003AA46B /* VGSCardTextField.swift in Sources */, 327C9E142407EF92004C641C /* VGSErrorInfo.swift in Sources */, + 321300172417F2B70062FEF0 /* VGSTextField.swift in Sources */, FD1489FF232D4F8000FD7781 /* MaskedTextField.swift in Sources */, 320E885323A94E1800A9C1FA /* CharacterSet+extension.swift in Sources */, 32FD980823D1FAF9005FA989 /* VGSCardIOScanController.swift in Sources */, FD4ED0E623736BB100AEAD24 /* MaskedTextField+security.swift in Sources */, FD148A07232EC53600FD7781 /* State.swift in Sources */, 32FD980E23D5E3A9005FA989 /* VGSCardIOScanControllerDelegate.swift in Sources */, + 321300192417F2E80062FEF0 /* VGSCollect+internal.swift in Sources */, FDFCA7FE233F7C1E00C81126 /* MaskedTextField+padding.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -596,15 +608,17 @@ FDF696E3234643F300063507 /* LuhnTests.swift in Sources */, FDF696E123463ACB00063507 /* TextFielsStyleUI.swift in Sources */, 328B5C7123C8DA2600A39515 /* String+extension.swift in Sources */, + 32059CD42417EACC003E9481 /* UtilsTest.swift in Sources */, FDA680DF239844FC00372817 /* CardTextFieldTests.swift in Sources */, 327C9E172407FC58004C641C /* VGSErrorInfo.swift in Sources */, FDF696E52346461D00063507 /* StorageTests.swift in Sources */, FD1BE48823462F1E006D8658 /* ExpDateTextFieldTests.swift in Sources */, + 3213001B24190BE50062FEF0 /* VGSCollectTest+Validation.swift in Sources */, 3219D73E2404279700F4A7E5 /* VGSError.swift in Sources */, - 3222A47B23CC6A4D00836A8D /* VGSCollectTests.swift in Sources */, FD4ED0E52373666500AEAD24 /* TextFieldSecurity.swift in Sources */, - FD24955E2330CC62009024E6 /* CollectorTests.swift in Sources */, + FD24955E2330CC62009024E6 /* VGSCollectTests.swift in Sources */, 3299585D23DF043D0018FA50 /* MaskedTextFieldTest.swift in Sources */, + 32059CD12417E02E003E9481 /* Utils.swift in Sources */, 327C9E182407FC62004C641C /* VGSErrorInfoKey.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -766,7 +780,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.2.2; + MARKETING_VERSION = 1.2.3; PRODUCT_BUNDLE_IDENTIFIER = com.vgs.framework; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -799,7 +813,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.2.2; + MARKETING_VERSION = 1.2.3; PRODUCT_BUNDLE_IDENTIFIER = com.vgs.framework; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = "";