-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement security code component UI (#489)
* Add security code component * Add some unit tests * Add test cases for when the card scheme is not set * Add UI tests for the security code component * Hide linter warnings in GitHub UI * Address review comments
- Loading branch information
1 parent
76d9940
commit 285fcb7
Showing
16 changed files
with
828 additions
and
254 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
Source/UI/SecurityCodeComponent/DefaultSecurityCodeFormStyle+Extension.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// | ||
// DefaultSecurityCodeFormStyle+Extension.swift | ||
// | ||
// | ||
// Created by Okhan Okbay on 05/10/2023. | ||
// | ||
|
||
import Foundation | ||
|
||
extension DefaultSecurityCodeFormStyle { | ||
init(securityCodeComponentStyle: SecurityCodeComponentStyle) { | ||
self.isMandatory = false | ||
self.backgroundColor = .clear | ||
self.title = nil | ||
self.hint = nil | ||
self.mandatory = nil | ||
self.error = nil | ||
self.textfield = DefaultTextField(textAlignment: securityCodeComponentStyle.textAlignment, | ||
isHidden: false, | ||
isSupportingNumericKeyboard: true, | ||
text: securityCodeComponentStyle.text, | ||
placeholder: securityCodeComponentStyle.placeholder, | ||
textColor: securityCodeComponentStyle.textColor, | ||
backgroundColor: .clear, | ||
tintColor: securityCodeComponentStyle.tintColor, | ||
width: .zero, | ||
height: .zero, | ||
font: securityCodeComponentStyle.font, | ||
borderStyle: DefaultBorderStyle(cornerRadius: .zero, | ||
borderWidth: .zero, | ||
normalColor: .clear, | ||
focusColor: .clear, | ||
errorColor: .clear)) | ||
|
||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// | ||
// SecurityCodeComponent.swift | ||
// | ||
// | ||
// Created by Okhan Okbay on 26/09/2023. | ||
// | ||
|
||
import Checkout | ||
import UIKit | ||
|
||
public final class SecurityCodeComponent: UIView { | ||
private var view: SecurityCodeView! | ||
|
||
private var configuration: SecurityCodeComponentConfiguration! | ||
private var isSecurityCodeValid: ((Bool) -> Void)! | ||
private var cardValidator: CardValidating! | ||
|
||
override init(frame: CGRect) { | ||
super.init(frame: frame) | ||
} | ||
|
||
required init?(coder aDecoder: NSCoder) { | ||
super.init(coder: aDecoder) | ||
} | ||
} | ||
|
||
extension SecurityCodeComponent { | ||
/** | ||
Method to configure SecurityCodeComponent and get the validation updates | ||
- configuration: See SecurityCodeComponentConfiguration documentation for the details | ||
- isSecurityCodeValid: A boolean value that indicates if the security code that was input by the user is valid or not. | ||
If a cardScheme is passed in the configuration, validation is being evaluated for the scheme. If no cardScheme is passed, then the security code is considered as valid for 3 and 4 digits. | ||
*/ | ||
public func configure(with configuration: SecurityCodeComponentConfiguration, | ||
isSecurityCodeValid: @escaping (Bool) -> Void) { | ||
self.configuration = configuration | ||
self.isSecurityCodeValid = isSecurityCodeValid | ||
|
||
self.cardValidator = CardValidator(environment: configuration.environment.checkoutEnvironment) | ||
|
||
let viewModel = SecurityCodeViewModel(cardValidator: cardValidator) | ||
if let initialCardScheme = configuration.cardScheme { | ||
viewModel.updateScheme(to: initialCardScheme) | ||
} | ||
|
||
let view = SecurityCodeView(viewModel: viewModel) | ||
view.update(style: DefaultSecurityCodeFormStyle(securityCodeComponentStyle: configuration.style)) | ||
view.accessibilityIdentifier = AccessibilityIdentifiers.SecurityCodeComponent.textField | ||
view.delegate = self | ||
|
||
view.frame = bounds | ||
view.autoresizingMask = [.flexibleWidth, .flexibleHeight] | ||
addSubview(view) | ||
|
||
self.view = view | ||
} | ||
} | ||
|
||
extension SecurityCodeComponent: SecurityCodeViewDelegate { | ||
func update(securityCode: String) { | ||
guard !securityCode.isEmpty else { | ||
isSecurityCodeValid(false) | ||
return | ||
} | ||
isSecurityCodeValid(cardValidator.isValid(cvv: securityCode, for: configuration.cardScheme ?? .unknown)) | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
Source/UI/SecurityCodeComponent/SecurityCodeComponentConfiguration.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// | ||
// SecurityCodeConfiguration.swift | ||
// | ||
// | ||
// Created by Okhan Okbay on 05/10/2023. | ||
// | ||
|
||
import Checkout | ||
import UIKit | ||
|
||
/** | ||
Configures and styles the SecurityCodeComponent | ||
|
||
- apiKey: The API Key you receive from checkout.com | ||
- environment: Production or sandbox | ||
- cardScheme: Optional card scheme | ||
- If provided, card scheme's validation rules apply (e.g. VISA = 3 digits, American Express = 4 digits etc.) | ||
- If not provided, security code is treated as valid for 3 and 4 digits | ||
- style: Security Code Component wraps a text field in a secure way. | ||
To style the inner properties like font, textColor etc, you must alter the style. | ||
*/ | ||
|
||
public struct SecurityCodeComponentConfiguration { | ||
let apiKey: String | ||
let environment: Environment | ||
public var style: SecurityCodeComponentStyle | ||
public var cardScheme: Card.Scheme? | ||
|
||
public init(apiKey: String, | ||
environment: Environment, | ||
style: SecurityCodeComponentStyle? = nil, | ||
cardScheme: Card.Scheme? = nil) { | ||
self.apiKey = apiKey | ||
self.environment = environment | ||
self.cardScheme = cardScheme | ||
|
||
if let style = style { | ||
self.style = style | ||
} else { | ||
self.style = .init(text: .init(), | ||
font: FramesUIStyle.Font.inputLabel, | ||
textAlignment: .natural, | ||
textColor: FramesUIStyle.Color.textPrimary, | ||
tintColor: FramesUIStyle.Color.textPrimary, | ||
placeholder: nil) | ||
} | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
Source/UI/SecurityCodeComponent/SecurityCodeComponentStyle.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// | ||
// SecurityCodeComponentStyle.swift | ||
// | ||
// | ||
// Created by Okhan Okbay on 05/10/2023. | ||
// | ||
|
||
import UIKit | ||
|
||
/** | ||
All the other UI relevant changes shall be done on the UIView instance of your project. | ||
The reason that these properties are presented to be modified here is that | ||
they are embedded in SecureDisplayView and shouldn't be reachable other than | ||
via SecurityCodeComponentStyle. | ||
*/ | ||
|
||
public struct SecurityCodeComponentStyle { | ||
public let text: String | ||
public let font: UIFont | ||
public let textAlignment: NSTextAlignment | ||
public let textColor: UIColor | ||
public let tintColor: UIColor | ||
public let placeholder: String? | ||
|
||
public init(text: String, | ||
font: UIFont, | ||
textAlignment: NSTextAlignment, | ||
textColor: UIColor, | ||
tintColor: UIColor, | ||
placeholder: String?) { | ||
self.text = text | ||
self.font = font | ||
self.textAlignment = textAlignment | ||
self.textColor = textColor | ||
self.tintColor = tintColor | ||
self.placeholder = placeholder | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
Tests/UI/SecurityCodeComponent/SecurityCodeComponentTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// | ||
// SecurityCodeComponentTests.swift | ||
// | ||
// | ||
// Created by Okhan Okbay on 20/10/2023. | ||
// | ||
|
||
@testable import Frames | ||
import XCTest | ||
|
||
final class SecurityCodeComponentTests: XCTestCase { | ||
let sut = SecurityCodeComponent() | ||
var mockconfig = SecurityCodeComponentConfiguration(apiKey: "some_api_key", environment: .sandbox) | ||
|
||
func test_whenUpdateIsCalledWith_thenISSecurityCodeValidCalledWithCorrectResult() { | ||
let testMatrix: [(scheme: Card.Scheme?, securityCode: String, expectedResult: Bool)] = [ | ||
(.visa, "", false), | ||
(.visa, "1", false), | ||
(.visa, "12", false), | ||
(.visa, "123", true), | ||
(.visa, "1234", false), | ||
(.americanExpress, "", false), | ||
(.americanExpress, "1", false), | ||
(.americanExpress, "12", false), | ||
(.americanExpress, "123", false), | ||
(.americanExpress, "1234", true), | ||
(.unknown, "", false), | ||
(.unknown, "1", false), | ||
(.unknown, "12", false), | ||
(.unknown, "123", true), | ||
(.unknown, "1234", true), | ||
(nil, "", false), | ||
(nil, "1", false), | ||
(nil, "12", false), | ||
(nil, "123", true), | ||
(nil, "1234", true), | ||
] | ||
|
||
testMatrix.forEach { testData in | ||
mockconfig.cardScheme = testData.scheme | ||
verify(securityCode: testData.securityCode, expectedResult: testData.expectedResult) | ||
} | ||
} | ||
|
||
func verify(securityCode: String, | ||
expectedResult: Bool, | ||
file: StaticString = #file, | ||
line: UInt = #line) { | ||
sut.configure(with: mockconfig) { [weak self] isSecurityCodeValid in | ||
XCTAssertEqual(expectedResult, | ||
isSecurityCodeValid, | ||
"Security code: \(securityCode) Card Scheme: \(self?.mockconfig.cardScheme?.rawValue ?? "nil") \n Expected: \(expectedResult) but found: \(isSecurityCodeValid)", | ||
file: file, | ||
line: line) | ||
} | ||
sut.update(securityCode: securityCode) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.