diff --git a/OmiseSDK/Resources/Assets.xcassets/Credit Card/Contents.json b/OmiseSDK/Resources/Assets.xcassets/Credit Card/Contents.json index da4a164c..73c00596 100644 --- a/OmiseSDK/Resources/Assets.xcassets/Credit Card/Contents.json +++ b/OmiseSDK/Resources/Assets.xcassets/Credit Card/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/OmiseSDK/Resources/Assets.xcassets/Credit Card/Discover.imageset/Contents.json b/OmiseSDK/Resources/Assets.xcassets/Credit Card/Discover.imageset/Contents.json new file mode 100644 index 00000000..312e77d2 --- /dev/null +++ b/OmiseSDK/Resources/Assets.xcassets/Credit Card/Discover.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images": [ + { + "filename": "Discover.pdf", + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/OmiseSDK/Resources/Assets.xcassets/Credit Card/Discover.imageset/Discover.pdf b/OmiseSDK/Resources/Assets.xcassets/Credit Card/Discover.imageset/Discover.pdf new file mode 100644 index 00000000..2e1fba7d Binary files /dev/null and b/OmiseSDK/Resources/Assets.xcassets/Credit Card/Discover.imageset/Discover.pdf differ diff --git a/OmiseSDK/Resources/Assets.xcassets/Credit Card/UnionPay.imageset/Contents.json b/OmiseSDK/Resources/Assets.xcassets/Credit Card/UnionPay.imageset/Contents.json new file mode 100644 index 00000000..36ddf0ef --- /dev/null +++ b/OmiseSDK/Resources/Assets.xcassets/Credit Card/UnionPay.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images": [ + { + "filename": "UnionPay.pdf", + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/OmiseSDK/Resources/Assets.xcassets/Credit Card/UnionPay.imageset/UnionPay.pdf b/OmiseSDK/Resources/Assets.xcassets/Credit Card/UnionPay.imageset/UnionPay.pdf new file mode 100644 index 00000000..17601956 Binary files /dev/null and b/OmiseSDK/Resources/Assets.xcassets/Credit Card/UnionPay.imageset/UnionPay.pdf differ diff --git a/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift b/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift index 459daf7a..464a3d30 100644 --- a/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift +++ b/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift @@ -104,6 +104,7 @@ func apiKey(config: NetceteraConfig) throws -> String { key: decryptionKey ) + // swiftlint:disable:next non_optional_string_data_conversion if let apiKey = String(data: encrypted, encoding: .utf8) { return apiKey } else { @@ -148,7 +149,6 @@ extension NetceteraThreeDSController: NetceteraThreeDSControllerProtocol { ThreeDSSDKAppDelegate.shared.appOpened(url: url) } - // swiftlint:disable:next function_body_length func processAuthorizedURL( _ authorizeUrl: URL, threeDSRequestorAppURL: String?, @@ -186,18 +186,8 @@ extension NetceteraThreeDSController: NetceteraThreeDSControllerProtocol { return } - switch response.status { - case .success: - onComplete(.success(())) + guard !Self.processAuthenticationResponse(response, onComplete: onComplete) else { return - case .failed: - onComplete(.failure(NetceteraThreeDSController.Errors.authResStatusFailed)) - return - case .unknown: - onComplete(.failure(NetceteraThreeDSController.Errors.authResStatusUnknown(response.serverStatus))) - return - case .challenge: - break } DispatchQueue.main.async { @@ -221,6 +211,22 @@ extension NetceteraThreeDSController: NetceteraThreeDSControllerProtocol { } } + static func processAuthenticationResponse(_ response: AuthResponse, onComplete: ((Result) -> Void)) -> Bool { + switch response.status { + case .success: + onComplete(.success(())) + return true + case .failed: + onComplete(.failure(NetceteraThreeDSController.Errors.authResStatusFailed)) + return true + case .unknown: + onComplete(.failure(NetceteraThreeDSController.Errors.authResStatusUnknown(response.serverStatus))) + return true + case .challenge: + return false + } + } + func prepareChallengeParameters( aRes: AuthResponse.ARes, threeDSRequestorAppURL: String? diff --git a/OmiseSDK/Sources/Models/CardBrand.swift b/OmiseSDK/Sources/Models/CardBrand.swift index a2e873f8..2b074f5c 100644 --- a/OmiseSDK/Sources/Models/CardBrand.swift +++ b/OmiseSDK/Sources/Models/CardBrand.swift @@ -17,6 +17,8 @@ public enum CardBrand: String, CustomStringConvertible, Codable { case laser = "Laser" /// Maestro card newtwork brand case maestro = "Maestro" + /// UnionPay card network brand + case unionPay = "UnionPay" /// Discover card newtwork brand case discover = "Discover" @@ -28,7 +30,8 @@ public enum CardBrand: String, CustomStringConvertible, Codable { diners, laser, maestro, - discover + discover, + unionPay ] /// Regular expression pattern that can detect cards issued by the brand. @@ -48,8 +51,10 @@ public enum CardBrand: String, CustomStringConvertible, Codable { return "^(6304|670[69]|6771)" case .maestro: return "^(5[0,6-8]|6304|6759|676[1-3])" + case .unionPay: + return "^62\\d{14,17}$" case .discover: - return "^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)" + return "^(6011\\d{12,15}|65\\d{14,17}|64[4-9]\\d{13,16}|6221[2-9]\\d{11,14}|622[3-9]\\d{12,15})$" } } @@ -70,8 +75,10 @@ public enum CardBrand: String, CustomStringConvertible, Codable { return 16...19 case .maestro: return 12...19 + case .unionPay: + return 16...19 case .discover: - return 16...16 + return 16...19 } } diff --git a/OmiseSDK/Sources/Models/OmiseError.swift b/OmiseSDK/Sources/Models/OmiseError.swift index c8a7f05f..d9a936f3 100644 --- a/OmiseSDK/Sources/Models/OmiseError.swift +++ b/OmiseSDK/Sources/Models/OmiseError.swift @@ -500,27 +500,9 @@ extension OmiseError.APIErrorCode.BadRequestReason: Decodable { static let nameIsTooLong: NSRegularExpression! = try? NSRegularExpression(pattern: "name is too long \\(maximum is ([\\d]+) characters\\)", options: []) } - // swiftlint:disable:next cyclomatic_complexity init(message: String, currency: Currency?) throws { if message.hasPrefix("amount must be ") { - if let lessThanValidAmountMatch = ErrorMessageRegularExpression.amountLessThanValidAmount - .firstMatch(in: message, range: NSRange(message.startIndex.. Self { + if let lessThanValidAmountMatch = ErrorMessageRegularExpression.amountLessThanValidAmount + .firstMatch(in: message, range: NSRange(message.startIndex.. Self? { + let matchRange = NSRange(message.startIndex.. [OmiseError.APIErrorCode.BadRequestReason] { let reasonMessages = message.components(separatedBy: ", and ") .flatMap { $0.components(separatedBy: ", ") } @@ -830,86 +839,124 @@ extension OmiseError.APIErrorCode.BadRequestReason: Decodable { try OmiseError.APIErrorCode.BadRequestReason(message: $0, currency: currency) }) - // swiftlint:disable:next closure_body_length return parsedReasons.sorted { switch $0 { case .amountIsLessThanValidAmount: return true case .other: return false - case .amountIsGreaterThanValidAmount: - switch $1 { - case .amountIsGreaterThanValidAmount, .invalidCurrency, .emptyName, .nameIsTooLong, .invalidName, - .invalidEmail, .invalidPhoneNumber, .typeNotSupported, .currencyNotSupported: - return true - case .amountIsLessThanValidAmount, .other: - return false - } + return Self.sortByAmountIsGreaterThanValidAmount(reason: $1) case .invalidCurrency: - switch $1 { - case .invalidCurrency, .emptyName, .nameIsTooLong, .invalidName, .invalidEmail, - .invalidPhoneNumber, .typeNotSupported, .currencyNotSupported: - return true - case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .other: - return false - } + return Self.sortByInvalidCurrency(reason: $1) case .emptyName: - switch $1 { - case .emptyName, .nameIsTooLong, .invalidName, .invalidEmail, .invalidPhoneNumber, - .typeNotSupported, .currencyNotSupported: - return true - case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .invalidCurrency, .other: - return false - } + return Self.sortByEmptyName(reason: $1) case .nameIsTooLong: - switch $1 { - case .nameIsTooLong, .invalidName, .invalidEmail, .invalidPhoneNumber, .typeNotSupported, .currencyNotSupported: - return true - case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .invalidCurrency, .emptyName, .other: - return false - } + return Self.sortByNameIsTooLong(reason: $1) case .invalidName: - switch $1 { - case .invalidName, .invalidEmail, .invalidPhoneNumber, .typeNotSupported, .currencyNotSupported: - return true - case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .invalidCurrency, .emptyName, .nameIsTooLong, .other: - return false - } + return Self.sortByInvalidName(reason: $1) case .invalidEmail: - switch $1 { - case .invalidEmail, .invalidPhoneNumber, .typeNotSupported, .currencyNotSupported: - return true - case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, - .invalidCurrency, .emptyName, .nameIsTooLong, .invalidName, .other: - return false - } + return Self.sortByInvalidEmail(reason: $1) case .invalidPhoneNumber: - switch $1 { - case .invalidPhoneNumber, .typeNotSupported, .currencyNotSupported: - return true - case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .invalidCurrency, - .emptyName, .nameIsTooLong, .invalidEmail, .invalidName, .other: - return false - } + return Self.sortByInvalidPhoneNumber(reason: $1) case .typeNotSupported: - switch $1 { - case .typeNotSupported, .currencyNotSupported: - return true - case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .invalidCurrency, - .emptyName, .nameIsTooLong, .invalidEmail, .invalidName, .invalidPhoneNumber, .other: - return false - } + return Self.sortByTypeNotSupported(reason: $1) case .currencyNotSupported: - switch $1 { - case .currencyNotSupported: - return true - case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .invalidCurrency, .emptyName, - .nameIsTooLong, .invalidName, .invalidEmail, .invalidPhoneNumber, .typeNotSupported, .other: - return false - } + return Self.sortByCurrencyNotSupported(reason: $1) } } } - // swiftlint:enable line_length } + +extension OmiseError.APIErrorCode.BadRequestReason { + static func sortByAmountIsGreaterThanValidAmount(reason: OmiseError.APIErrorCode.BadRequestReason) -> Bool { + switch reason { + case .amountIsGreaterThanValidAmount, .invalidCurrency, .emptyName, .nameIsTooLong, .invalidName, + .invalidEmail, .invalidPhoneNumber, .typeNotSupported, .currencyNotSupported: + return true + case .amountIsLessThanValidAmount, .other: + return false + } + } + + static func sortByInvalidCurrency(reason: OmiseError.APIErrorCode.BadRequestReason) -> Bool { + switch reason { + case .invalidCurrency, .emptyName, .nameIsTooLong, .invalidName, .invalidEmail, + .invalidPhoneNumber, .typeNotSupported, .currencyNotSupported: + return true + case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .other: + return false + } + + } + static func sortByEmptyName(reason: OmiseError.APIErrorCode.BadRequestReason) -> Bool { + switch reason { + case .emptyName, .nameIsTooLong, .invalidName, .invalidEmail, .invalidPhoneNumber, + .typeNotSupported, .currencyNotSupported: + return true + case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .invalidCurrency, .other: + return false + } + + } + + static func sortByNameIsTooLong(reason: OmiseError.APIErrorCode.BadRequestReason) -> Bool { + switch reason { + case .nameIsTooLong, .invalidName, .invalidEmail, .invalidPhoneNumber, .typeNotSupported, .currencyNotSupported: + return true + case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .invalidCurrency, .emptyName, .other: + return false + } + } + + static func sortByInvalidName(reason: OmiseError.APIErrorCode.BadRequestReason) -> Bool { + switch reason { + case .invalidName, .invalidEmail, .invalidPhoneNumber, .typeNotSupported, .currencyNotSupported: + return true + case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .invalidCurrency, .emptyName, .nameIsTooLong, .other: + return false + } + } + + static func sortByInvalidEmail(reason: OmiseError.APIErrorCode.BadRequestReason) -> Bool { + switch reason { + case .invalidEmail, .invalidPhoneNumber, .typeNotSupported, .currencyNotSupported: + return true + case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, + .invalidCurrency, .emptyName, .nameIsTooLong, .invalidName, .other: + return false + } + } + + static func sortByInvalidPhoneNumber(reason: OmiseError.APIErrorCode.BadRequestReason) -> Bool { + switch reason { + case .invalidPhoneNumber, .typeNotSupported, .currencyNotSupported: + return true + case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .invalidCurrency, + .emptyName, .nameIsTooLong, .invalidEmail, .invalidName, .other: + return false + } + } + + static func sortByTypeNotSupported(reason: OmiseError.APIErrorCode.BadRequestReason) -> Bool { + switch reason { + case .typeNotSupported, .currencyNotSupported: + return true + case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .invalidCurrency, + .emptyName, .nameIsTooLong, .invalidEmail, .invalidName, .invalidPhoneNumber, .other: + return false + } + } + + static func sortByCurrencyNotSupported(reason: OmiseError.APIErrorCode.BadRequestReason) -> Bool { + switch reason { + case .currencyNotSupported: + return true + case .amountIsLessThanValidAmount, .amountIsGreaterThanValidAmount, .invalidCurrency, .emptyName, + .nameIsTooLong, .invalidName, .invalidEmail, .invalidPhoneNumber, .typeNotSupported, .other: + return false + } + } +} + +// swiftlint:enable line_length diff --git a/OmiseSDK/Sources/Models/PAN.swift b/OmiseSDK/Sources/Models/PAN.swift index 568098a5..958e7a86 100644 --- a/OmiseSDK/Sources/Models/PAN.swift +++ b/OmiseSDK/Sources/Models/PAN.swift @@ -39,17 +39,7 @@ public struct PAN { /// The suggested of where the space should be displayed string indexes public var suggestedSpaceFormattedIndexes: IndexSet { - switch self { - case CardBrand.amex.pattern, "^5[6-8]": - return [ 4, 10 ] - case "^50": - return [ 4, 8 ] - case "^3[0,6,8-9]": - return [ 4, 10 ] - case "^[0-9]": - return [ 4, 8, 12 ] - default: return [] - } + return IndexSet(stride(from: 4, to: 19, by: 4)) } /// The last 4 digits of the PAN number diff --git a/OmiseSDK/Sources/OmiseSDK.swift b/OmiseSDK/Sources/OmiseSDK.swift index b01d3a99..0ae67f15 100644 --- a/OmiseSDK/Sources/OmiseSDK.swift +++ b/OmiseSDK/Sources/OmiseSDK.swift @@ -219,7 +219,9 @@ public class OmiseSDK { private extension OmiseSDK { private func preloadCapabilityAPI() { - client.capability { _ in } + client.capability { _ in + // Preload capability and auto cache it as client.latestLoadedCapability + } } } diff --git a/OmiseSDK/Sources/Views/Components/TextFieldView.swift b/OmiseSDK/Sources/Views/Components/TextFieldView.swift index 89540798..bee01702 100644 --- a/OmiseSDK/Sources/Views/Components/TextFieldView.swift +++ b/OmiseSDK/Sources/Views/Components/TextFieldView.swift @@ -46,9 +46,9 @@ class TextFieldView: UIView { }() var onTextFieldShouldReturn: () -> (Bool) = { return false } - var onTextChanged: () -> Void = { } - var onBeginEditing: () -> Void = { } - var onEndEditing: () -> Void = { } + var onTextChanged: () -> Void = { /* Non-optional default empty implementation */ } + var onBeginEditing: () -> Void = { /* Non-optional default empty implementation */ } + var onEndEditing: () -> Void = { /* Non-optional default empty implementation */ } // swiftlint:disable attributes @ProxyProperty(\TextFieldView.textField.keyboardType) var keyboardType: UIKeyboardType diff --git a/OmiseSDK/Sources/Views/Components/TextFields/CardExpiryDateTextField.swift b/OmiseSDK/Sources/Views/Components/TextFields/CardExpiryDateTextField.swift index 113d48a8..09e09d95 100644 --- a/OmiseSDK/Sources/Views/Components/TextFields/CardExpiryDateTextField.swift +++ b/OmiseSDK/Sources/Views/Components/TextFields/CardExpiryDateTextField.swift @@ -175,7 +175,22 @@ public class CardExpiryDateTextField: OmiseTextField { static let monthStringRegularExpression: NSRegularExpression! = try? NSRegularExpression(pattern: "^([0-1]?\\d)", options: []) - // swiftlint:disable:next cyclomatic_complexity function_body_length + var parsedSelectedYear: Int? { + get { + return selectedYear + } + set { + guard let value = newValue else { + return + } + if value < 100 { + self.selectedYear = 2000 + value + } else { + self.selectedYear = value + } + } + } + public override func paste(_ sender: Any?) { let pasteboard = UIPasteboard.general @@ -198,39 +213,35 @@ public class CardExpiryDateTextField: OmiseTextField { defer { typingAttributes = defaultTextAttributes } - - var parsedSelectedYear: Int? { - get { - return selectedYear - } - set { - guard let value = newValue else { - return - } - if value < 100 { - self.selectedYear = 2000 + value - } else { - self.selectedYear = value - } - } + + parseCardExpiryDate(text: text) + + if let attributedText = self.attributedText.map(NSMutableAttributedString.init(attributedString:)), + let separatorTextColor = self.dateSeparatorTextColor, + let dateSeparatorIndex = attributedText.string.firstIndex(of: "/") { + let range = NSRange(dateSeparatorIndex...dateSeparatorIndex, in: attributedText.string) + attributedText.addAttribute(.foregroundColor, value: separatorTextColor, range: range) + self.attributedText = attributedText } - + } + + func parseCardExpiryDate(text: String) { if let separatorIndex = text.firstIndex(of: "/") { selectedMonth = Int(text[text.startIndex.. 0 else { return @@ -306,7 +308,7 @@ extension CardNumberTextField { guard range.length >= 0 else { return true } - let maxLength = (pan.brand?.validLengths.upperBound ?? 16) + let maxLength = (pan.brand?.validLengths.upperBound ?? 19) return maxLength >= (self.text?.count ?? 0) - range.length + string.count } diff --git a/OmiseSDK/Sources/Views/Components/TextFields/OmiseTextField.swift b/OmiseSDK/Sources/Views/Components/TextFields/OmiseTextField.swift index e730b489..33fca0b2 100644 --- a/OmiseSDK/Sources/Views/Components/TextFields/OmiseTextField.swift +++ b/OmiseSDK/Sources/Views/Components/TextFields/OmiseTextField.swift @@ -21,7 +21,7 @@ public class OmiseTextField: UITextField { } public var onTextFieldShouldReturn: () -> (Bool) = { return false } - public var onValueChanged: () -> Void = { } + public var onValueChanged: () -> Void = { /* Non-optional default empty implementation */ } @IBInspectable var borderWidth: CGFloat { get { diff --git a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Other Payments/Atome/AtomePaymentFormViewModel.swift b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Other Payments/Atome/AtomePaymentFormViewModel.swift index 649448ab..8cfb7e42 100644 --- a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Other Payments/Atome/AtomePaymentFormViewModel.swift +++ b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Other Payments/Atome/AtomePaymentFormViewModel.swift @@ -27,7 +27,7 @@ class AtomePaymentFormViewModel: AtomePaymentFormViewModelProtocol, CountryListV } } - var onSelectCountry: (Country) -> Void = { _ in } + var onSelectCountry: (Country) -> Void = { _ in /* Non-optional default empty implementation */ } var countryListViewModel: CountryListViewModelProtocol { return self } diff --git a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Other Payments/Atome/Base/PaymentFormController.swift b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Other Payments/Atome/Base/PaymentFormController.swift index c05f63a0..2e6ff0be 100644 --- a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Other Payments/Atome/Base/PaymentFormController.swift +++ b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Other Payments/Atome/Base/PaymentFormController.swift @@ -2,7 +2,7 @@ import Foundation import UIKit class PaymentFormController: UIViewController { - var onSubmitButtonTappedClosure: () -> Void = { } + var onSubmitButtonTappedClosure: () -> Void = { /* Non-optional default empty implementation */ } @ProxyProperty(\PaymentFormController.headerTextLabel.text) var details: String? diff --git a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Other Payments/FPX/FPXPaymentFormController.swift b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Other Payments/FPX/FPXPaymentFormController.swift index 7f69d5da..1311aa79 100644 --- a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Other Payments/FPX/FPXPaymentFormController.swift +++ b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Other Payments/FPX/FPXPaymentFormController.swift @@ -133,7 +133,7 @@ class FPXPaymentFormController: UIViewController, PaymentFormUIController { @IBAction private func submitForm(_ sender: AnyObject) { emailValue = emailTextField.text?.trimmingCharacters(in: CharacterSet.whitespaces) - delegate?.fpxDidCompleteWith(email: emailValue) {} + delegate?.fpxDidCompleteWith(email: emailValue) { /* no action is required */ } } @IBAction private func validateFieldData(_ textField: OmiseTextField) { diff --git a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/SelectPaymentController.swift b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/SelectPaymentController.swift index e3793cdf..66b93079 100644 --- a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/SelectPaymentController.swift +++ b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/SelectPaymentController.swift @@ -4,7 +4,9 @@ class SelectPaymentController: UITableViewController { let viewModel: SelectPaymentPresentableProtocol - var customizeCellAtIndexPathClosure: (UITableViewCell, IndexPath) -> Void = { _, _ in } + var customizeCellAtIndexPathClosure: (UITableViewCell, IndexPath) -> Void = { _, _ in + // Non-optional default empty implementation + } init(viewModel: SelectPaymentPresentableProtocol) { self.viewModel = viewModel diff --git a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/SelectPaymentPresentableProtocol.swift b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/SelectPaymentPresentableProtocol.swift index fb19f2d2..51ab5736 100644 --- a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/SelectPaymentPresentableProtocol.swift +++ b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/SelectPaymentPresentableProtocol.swift @@ -19,5 +19,5 @@ extension SelectPaymentPresentableProtocol { var errorMessage: String? { nil } var viewShowsCloseButton: Bool { false } var viewDisplayLargeTitle: Bool { false } - func viewDidTapClose() {} + func viewDidTapClose() { /* Default empty implementation */ } } diff --git a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectDuitNowOBWBankViewModel.swift b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectDuitNowOBWBankViewModel.swift index 1fc5c7ef..06955395 100644 --- a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectDuitNowOBWBankViewModel.swift +++ b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectDuitNowOBWBankViewModel.swift @@ -3,7 +3,7 @@ import UIKit class SelectDuitNowOBWBankViewModel { private weak var delegate: SelectSourcePaymentDelegate? - private var viewOnDataReloadHandler: () -> Void = { } { + private var viewOnDataReloadHandler: () -> Void = { /* Non-optional default empty implementation */ } { didSet { self.viewOnDataReloadHandler() } diff --git a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectFPXBankViewModel.swift b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectFPXBankViewModel.swift index 938766f3..bde82a62 100644 --- a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectFPXBankViewModel.swift +++ b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectFPXBankViewModel.swift @@ -6,7 +6,7 @@ class SelectFPXBankViewModel { let errorMessage: String? - private var viewOnDataReloadHandler: () -> Void = { } { + private var viewOnDataReloadHandler: () -> Void = { /* Non-optional default empty implementation */ } { didSet { self.viewOnDataReloadHandler() } diff --git a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectInstallmentTermsViewModel.swift b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectInstallmentTermsViewModel.swift index 156f2fe7..81ef32ed 100644 --- a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectInstallmentTermsViewModel.swift +++ b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectInstallmentTermsViewModel.swift @@ -4,7 +4,7 @@ class SelectInstallmentTermsViewModel { weak var delegate: SelectSourcePaymentDelegate? let sourceType: SourceType - private var viewOnDataReloadHandler: () -> Void = { } { + private var viewOnDataReloadHandler: () -> Void = { /* Non-optional default empty implementation */ } { didSet { self.viewOnDataReloadHandler() } diff --git a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectPaymentMethodViewModel.swift b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectPaymentMethodViewModel.swift index b8215e14..2a907d5f 100644 --- a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectPaymentMethodViewModel.swift +++ b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectPaymentMethodViewModel.swift @@ -12,7 +12,7 @@ class SelectPaymentMethodViewModel { private let filter: Filter private weak var delegate: SelectPaymentMethodDelegate? - private var viewOnDataReloadHandler: () -> Void = { } { + private var viewOnDataReloadHandler: () -> Void = { /* Non-optional default empty implementation */ } { didSet { self.viewOnDataReloadHandler() } diff --git a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectSourceTypePaymentViewModel.swift b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectSourceTypePaymentViewModel.swift index 4bf1f8a8..40d3e3fb 100644 --- a/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectSourceTypePaymentViewModel.swift +++ b/OmiseSDK/Sources/Views/Screens/Choose Payment Methods/Select Payment From Lists/ViewModels/SelectSourceTypePaymentViewModel.swift @@ -4,7 +4,7 @@ class SelectSourceTypePaymentViewModel { private weak var delegate: SelectSourceTypeDelegate? private let title: String - private var viewOnDataReloadHandler: () -> Void = { } { + private var viewOnDataReloadHandler: () -> Void = { /* Non-optional default empty implementation */ } { didSet { self.viewOnDataReloadHandler() } diff --git a/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CCVInfoController.swift b/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CCVInfoController.swift index e2ffac4b..945b6c6f 100644 --- a/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CCVInfoController.swift +++ b/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CCVInfoController.swift @@ -16,7 +16,7 @@ class CCVInfoController: UIViewController { } } - var onCloseTapped: () -> Void = { } + var onCloseTapped: () -> Void = { /* Non-optional default empty implementation */ } override func viewDidLoad() { super.viewDidLoad() diff --git a/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift b/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift index fab1dcbb..b8c6c568 100644 --- a/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift +++ b/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift @@ -547,6 +547,10 @@ class CreditCardPaymentController: UIViewController { cardBrandIconName = "AMEX" case .diners?: cardBrandIconName = "Diners" + case .unionPay: + cardBrandIconName = "UnionPay" + case .discover?: + cardBrandIconName = "Discover" default: cardBrandIconName = nil } @@ -736,104 +740,28 @@ extension CreditCardPaymentController { updateSupplementaryUI() } - // swiftlint:disable:next function_body_length func configureAccessibility() { - formLabels.forEach { - $0.adjustsFontForContentSizeCategory = true - } - formFields.forEach { - $0.adjustsFontForContentSizeCategory = true - } - - let fields = formFields - + formLabels.forEach { $0.adjustsFontForContentSizeCategory = true } + formFields.forEach { $0.adjustsFontForContentSizeCategory = true } submitButton.titleLabel?.adjustsFontForContentSizeCategory = true - // swiftlint:disable:next function_body_length - func accessiblityElementAfter( - _ element: NSObjectProtocol?, - matchingPredicate predicate: (OmiseTextField) -> Bool, - direction: UIAccessibilityCustomRotor.Direction - ) -> NSObjectProtocol? { - guard let element = element else { - switch direction { - case .previous: - return fields.reversed().first(where: predicate)?.accessibilityElements?.last as? NSObjectProtocol - ?? fields.reversed().first(where: predicate) - case .next: - fallthrough - @unknown default: - return fields.first(where: predicate)?.accessibilityElements?.first as? NSObjectProtocol - ?? fields.first(where: predicate) - } - } - - let fieldOfElement = fields.first { field in - guard let accessibilityElements = field.accessibilityElements as? [NSObjectProtocol] else { - return element === field - } - - return accessibilityElements.contains { $0 === element } - } ?? cardNumberTextField! // swiftlint:disable:this force_unwrapping - - func filedAfter( - _ field: OmiseTextField, - matchingPredicate predicate: (OmiseTextField) -> Bool, - direction: UIAccessibilityCustomRotor.Direction - ) -> OmiseTextField? { - guard let indexOfField = fields.firstIndex(of: field) else { return nil } - switch direction { - case .previous: - return fields[fields.startIndex.. currentAccessibilityElements.startIndex { - return currentAccessibilityElements[currentAccessibilityElements.index(before: indexOfAccessibilityElement)] - } else { - return nextField?.accessibilityElements?.last as? NSObjectProtocol ?? nextField - } - case .next: - fallthrough - @unknown default: - if predicate(fieldOfElement) && indexOfAccessibilityElement < currentAccessibilityElements.endIndex - 1 { - return currentAccessibilityElements[currentAccessibilityElements.index(after: indexOfAccessibilityElement)] - } else { - return nextField?.accessibilityElements?.first as? NSObjectProtocol ?? nextField - } - } - } - + /* write unit tests for the code below */ accessibilityCustomRotors = [ - UIAccessibilityCustomRotor(name: "Fields") { (predicate) -> UIAccessibilityCustomRotorItemResult? in - return accessiblityElementAfter(predicate.currentItem.targetElement, - matchingPredicate: { _ in true }, - direction: predicate.searchDirection) + UIAccessibilityCustomRotor(name: "Fields") { [weak self] (predicate) -> UIAccessibilityCustomRotorItemResult? in + let fields = self?.formFields ?? [] + return self?.accessibilityElementAfter(predicate.currentItem.targetElement, + fields: fields, + matchingPredicate: { _ in true }, + direction: predicate.searchDirection) .map { UIAccessibilityCustomRotorItemResult(targetElement: $0, targetRange: nil) } }, - UIAccessibilityCustomRotor(name: "Invalid Data Fields") { (predicate) -> UIAccessibilityCustomRotorItemResult? in - return accessiblityElementAfter(predicate.currentItem.targetElement, - matchingPredicate: { !$0.isValid }, - direction: predicate.searchDirection) + + UIAccessibilityCustomRotor(name: "Invalid Data Fields") { [weak self] (predicate) -> UIAccessibilityCustomRotorItemResult? in + let fields = self?.formFields ?? [] + return self?.accessibilityElementAfter(predicate.currentItem.targetElement, + fields: fields, + matchingPredicate: { !$0.isValid }, + direction: predicate.searchDirection) .map { UIAccessibilityCustomRotorItemResult(targetElement: $0, targetRange: nil) } } ] @@ -1025,3 +953,101 @@ extension CreditCardPaymentController { view.endEditing(true) } } + +extension CreditCardPaymentController { + // Move out to the same level as configureAccessibility + func accessibilityElementAfter( + _ element: NSObjectProtocol?, + fields: [OmiseTextField], + matchingPredicate predicate: (OmiseTextField) -> Bool, + direction: UIAccessibilityCustomRotor.Direction + ) -> NSObjectProtocol? { + guard let element = element else { + return handleNoElement(direction, fields: fields, matchingPredicate: predicate) + } + return findAccessibilityElement(element, fields: fields, matchingPredicate: predicate, direction: direction) + } + + // This could be the new helper function handling cases when no element is provided + func handleNoElement( + _ direction: UIAccessibilityCustomRotor.Direction, + fields: [OmiseTextField], + matchingPredicate predicate: (OmiseTextField) -> Bool + ) -> NSObjectProtocol? { + + switch direction { + case .previous: + return fields.reversed().first(where: predicate)?.accessibilityElements?.last as? NSObjectProtocol + ?? fields.reversed().first(where: predicate) + case .next: + fallthrough + @unknown default: + return fields.first(where: predicate)?.accessibilityElements?.first as? NSObjectProtocol + ?? fields.first(where: predicate) + } + } + + // This could be another helper function finding an accessibility element + func findAccessibilityElement( + _ element: NSObjectProtocol, + fields: [OmiseTextField], + matchingPredicate predicate: (OmiseTextField) -> Bool, + direction: UIAccessibilityCustomRotor.Direction + ) -> NSObjectProtocol? { + let fieldOfElement = fields.first { field in + guard let accessibilityElements = field.accessibilityElements as? [NSObjectProtocol] else { + return element === field + } + + return accessibilityElements.contains { $0 === element } + } ?? cardNumberTextField! // swiftlint:disable:this force_unwrapping + + let nextField = filedAfter(fieldOfElement, fields: fields, matchingPredicate: predicate, direction: direction) + + guard let currentAccessibilityElements = (fieldOfElement.accessibilityElements as? [NSObjectProtocol]), + let indexOfAccessibilityElement = currentAccessibilityElements.firstIndex(where: { $0 === element }) else { + switch direction { + case .previous: + return nextField?.accessibilityElements?.last as? NSObjectProtocol ?? nextField + case .next: + fallthrough + @unknown default: + return nextField?.accessibilityElements?.first as? NSObjectProtocol ?? nextField + } + } + + switch direction { + case .previous: + if predicate(fieldOfElement) && indexOfAccessibilityElement > currentAccessibilityElements.startIndex { + return currentAccessibilityElements[currentAccessibilityElements.index(before: indexOfAccessibilityElement)] + } else { + return nextField?.accessibilityElements?.last as? NSObjectProtocol ?? nextField + } + case .next: + fallthrough + @unknown default: + if predicate(fieldOfElement) && indexOfAccessibilityElement < currentAccessibilityElements.endIndex - 1 { + return currentAccessibilityElements[currentAccessibilityElements.index(after: indexOfAccessibilityElement)] + } else { + return nextField?.accessibilityElements?.first as? NSObjectProtocol ?? nextField + } + } + } + + func filedAfter( + _ field: OmiseTextField, + fields: [OmiseTextField], + matchingPredicate predicate: (OmiseTextField) -> Bool, + direction: UIAccessibilityCustomRotor.Direction + ) -> OmiseTextField? { + guard let indexOfField = fields.firstIndex(of: field) else { return nil } + switch direction { + case .previous: + return fields[fields.startIndex.. Void = { _ in } + + var onSelectCountry: (Country) -> Void = { _ in /* Non-optional default empty implementation */ } func error(for field: AddressField, validate text: String?) -> String? { guard isAddressFieldsVisible else { return nil } diff --git a/OmiseSDKTests/3DS/SHA512Tests.swift b/OmiseSDKTests/3DS/SHA512Tests.swift index 5537188a..a910850d 100644 --- a/OmiseSDKTests/3DS/SHA512Tests.swift +++ b/OmiseSDKTests/3DS/SHA512Tests.swift @@ -6,9 +6,10 @@ class SHA512Tests: XCTestCase { func testSHA512EmptyString() { let emptyString = "" + + // expected SHA-512 hash of empty string let expectedHash = Data( - // swiftlint:disable:next multiline_literal_brackets - [ // expected SHA-512 hash of empty string + [ 0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07, 0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, diff --git a/OmiseSDKTests/ClientTests.swift b/OmiseSDKTests/ClientTests.swift index 58a0a571..87cae453 100644 --- a/OmiseSDKTests/ClientTests.swift +++ b/OmiseSDKTests/ClientTests.swift @@ -54,6 +54,7 @@ class ClientTests: XCTestCase { defer { expectation.fulfill() } func validateURLRequest() { + // swiftlint:disable:next non_optional_string_data_conversion if let data = urlRequest.httpBody, let jsonString = String(data: data, encoding: .utf8) { do { let decodedCardPayload: CreateTokenPayload = try parse(jsonString: jsonString) @@ -88,6 +89,7 @@ class ClientTests: XCTestCase { defer { expectation.fulfill() } func validateURLRequest() { + // swiftlint:disable:next non_optional_string_data_conversion if let data = urlRequest.httpBody, let jsonString = String(data: data, encoding: .utf8) { do { let decodedCardPayload: CreateSourcePayload = try parse(jsonString: jsonString) diff --git a/OmiseSDKTests/Helpers/SampleData.swift b/OmiseSDKTests/Helpers/SampleData.swift index 403325a9..49068655 100644 --- a/OmiseSDKTests/Helpers/SampleData.swift +++ b/OmiseSDKTests/Helpers/SampleData.swift @@ -62,6 +62,7 @@ func encodeToJson(_ object: T) throws -> String { encoder.outputFormatting = [.sortedKeys] let data = try encoder.encode(object) + // swiftlint:disable:next non_optional_string_data_conversion guard let result = String(data: data, encoding: .utf8) else { throw DataToStringCastError() } diff --git a/OmiseSDKTests/Helpers/String+JSON.swift b/OmiseSDKTests/Helpers/String+JSON.swift index 58b63215..0289cfbe 100644 --- a/OmiseSDKTests/Helpers/String+JSON.swift +++ b/OmiseSDKTests/Helpers/String+JSON.swift @@ -27,6 +27,7 @@ extension String { encoder.outputFormatting = [.sortedKeys] let data = try encoder.encode(encodable) + // swiftlint:disable:next non_optional_string_data_conversion guard let string = String(data: data, encoding: .utf8) else { throw StringFromDateError() } diff --git a/OmiseSDKTests/OmiseErrorTests.swift b/OmiseSDKTests/OmiseErrorTests.swift new file mode 100644 index 00000000..e3eb938e --- /dev/null +++ b/OmiseSDKTests/OmiseErrorTests.swift @@ -0,0 +1,82 @@ +import Foundation +import XCTest +@testable import OmiseSDK + +class OmiseErrorTests: XCTestCase { + + func testBadRequestReason() throws { + let currencies: [Currency?] = [nil, .jpy, .myr, .thb, .usd] + for currency in currencies { + let expectedResults: [String: OmiseError.APIErrorCode.BadRequestReason] = [ + "amount must be at least 500": .amountIsLessThanValidAmount(validAmount: 500, currency: currency), + "amount must be less than 500": .amountIsGreaterThanValidAmount(validAmount: 500, currency: currency), + "amount must be greater than 500": .amountIsLessThanValidAmount(validAmount: 500, currency: currency), + "name is too long (maximum is 50 characters)": .nameIsTooLong(maximum: 50), + "name is too long ... 20": .nameIsTooLong(maximum: nil), // check if it should be .invalidName instead, + "... name ... blank ...": .emptyName, + "... name ...": .nameIsTooLong(maximum: nil), // check if it should be .invalidName instead, + "... email ...": .invalidEmail, + "... phone ...": .invalidPhoneNumber, + "... type ...": .typeNotSupported, + "... currency must be...": .invalidCurrency, + "... currency ...": .currencyNotSupported, + "Something else": .other("Something else") + ] + + for (testString, expectedResult) in expectedResults { + let result = try OmiseError.APIErrorCode.BadRequestReason(message: testString, currency: currency) + XCTAssertEqual(result, expectedResult) + } + } + } + + typealias BadRequestReason = OmiseError.APIErrorCode.BadRequestReason + func testParseBadRequestReasonsFromMessage() { + // Array of tuples for test cases + let currency: Currency = .thb + let testCases: [(message: String, expected: Set)] = [ + ( + "amount must be at least 100 THB", + [.amountIsLessThanValidAmount(validAmount: 100, currency: currency)] + ), + ( + "amount must be less than 5000 USD", + [.amountIsGreaterThanValidAmount(validAmount: 5000, currency: currency)] + ), + ( + "currency must be.., empty name", + [.invalidCurrency, .nameIsTooLong(maximum: nil)] + ), + ( + "other currency error, empty name", + [.currencyNotSupported, .nameIsTooLong(maximum: nil)] + ), + ( + "name is too long (maximum is 25 characters), invalid email", + [.nameIsTooLong(maximum: 25), .invalidEmail] + ), + ( + "invalid phone number", + [.invalidPhoneNumber] + ), + ( + "type not supported, currency not supported", + [.typeNotSupported, .currencyNotSupported] + ), + ( + "unknown issue", + [.other("unknown issue")] + ) + ] + + for (message, expected) in testCases { + do { + let results = try BadRequestReason.parseBadRequestReasonsFromMessage(message, currency: currency) + XCTAssertEqual(Set(results), expected, "Failed to parse or incorrect order for message: \(message)") + } catch { + XCTFail("Unexpected error for message: \(message): \(error)") + } + } + } + +} diff --git a/OmiseSDKTests/Source and Token JSON Codable Tests/SourceTests.swift b/OmiseSDKTests/Source and Token JSON Codable Tests/SourceTests.swift index d62c5a35..f932a385 100644 --- a/OmiseSDKTests/Source and Token JSON Codable Tests/SourceTests.swift +++ b/OmiseSDKTests/Source and Token JSON Codable Tests/SourceTests.swift @@ -11,6 +11,7 @@ class SourceTests: XCTestCase { /// Test Source.Payload's Codable protocol func validatePayloadCodable(_ payload: Payment) throws { let encodedPayload = try JSONEncoder().encode(payload) + // swiftlint:disable:next non_optional_string_data_conversion let encodedPayloadJson = String(data: encodedPayload, encoding: .utf8) ?? "" if payload.sourceType == .duitNowOBW { print(encodedPayloadJson) diff --git a/dev.xcodeproj/project.pbxproj b/dev.xcodeproj/project.pbxproj index bce17807..c77448f9 100644 --- a/dev.xcodeproj/project.pbxproj +++ b/dev.xcodeproj/project.pbxproj @@ -170,6 +170,8 @@ 75D13E1D2B86FF8C0073A831 /* CreditCardPaymentDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D13E1C2B86FF8C0073A831 /* CreditCardPaymentDelegate.swift */; }; 75D13E202B8703F80073A831 /* CreditCardPaymentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D13E1E2B8703F70073A831 /* CreditCardPaymentController.swift */; }; 75D13E212B8703F80073A831 /* CreditCardPaymentController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 75D13E1F2B8703F80073A831 /* CreditCardPaymentController.xib */; }; + 75D2C3EE2C19A80C006072D9 /* ThreeDS_SDK in Frameworks */ = {isa = PBXBuildFile; productRef = 75D2C3ED2C19A80C006072D9 /* ThreeDS_SDK */; }; + 75D4E7062C05F50500ECCE72 /* OmiseErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D4E7052C05F50500ECCE72 /* OmiseErrorTests.swift */; }; 75DAD8902A0BB8D80098AF96 /* LocalConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DAD88F2A0BB8D80098AF96 /* LocalConfig.swift */; }; 75E0EB712B7A904100E3198A /* SourceFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75E0EB702B7A904100E3198A /* SourceFlowTests.swift */; }; 75E0EB722B7A962600E3198A /* CreateSourcePayloadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B4208D2B78C2120036134D /* CreateSourcePayloadTests.swift */; }; @@ -435,6 +437,7 @@ 75D13E1C2B86FF8C0073A831 /* CreditCardPaymentDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardPaymentDelegate.swift; sourceTree = ""; }; 75D13E1E2B8703F70073A831 /* CreditCardPaymentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardPaymentController.swift; sourceTree = ""; }; 75D13E1F2B8703F80073A831 /* CreditCardPaymentController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CreditCardPaymentController.xib; sourceTree = ""; }; + 75D4E7052C05F50500ECCE72 /* OmiseErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmiseErrorTests.swift; sourceTree = ""; }; 75DAD88F2A0BB8D80098AF96 /* LocalConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalConfig.swift; sourceTree = ""; }; 75DAD8922A0BC9540098AF96 /* Config.local.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Config.local.plist; sourceTree = ""; }; 75E0EB702B7A904100E3198A /* SourceFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceFlowTests.swift; sourceTree = ""; }; @@ -509,6 +512,7 @@ buildActionMask = 2147483647; files = ( 75D13E0F2B8678530073A831 /* OmiseSwiftUIKit in Frameworks */, + 75D2C3EE2C19A80C006072D9 /* ThreeDS_SDK in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1213,6 +1217,7 @@ 75CFC4D32B73B21100422A8F /* ClientTests.swift */, 750708F32B7A765500A48DD0 /* OmiseSDKTests.swift */, 75F8C0B62B1F78E300AE78D9 /* PaymentChooserViewControllerTests.swift */, + 75D4E7052C05F50500ECCE72 /* OmiseErrorTests.swift */, ); path = OmiseSDKTests; sourceTree = ""; @@ -1291,6 +1296,7 @@ name = ExampleApp; packageProductDependencies = ( 75D13E0E2B8678530073A831 /* OmiseSwiftUIKit */, + 75D2C3ED2C19A80C006072D9 /* ThreeDS_SDK */, ); productName = ExampleApp; productReference = 22D4809D1D0EB29C00544CE1 /* ExampleApp.app */; @@ -1691,6 +1697,7 @@ 7509D4E72A1C8E3D0050AB38 /* AtomeFormViewModelTests.swift in Sources */, 7509D4E22A1C876B0050AB38 /* AtomeFormViewContextMockup.swift in Sources */, 750708E32B790B8300A48DD0 /* String+JSON.swift in Sources */, + 75D4E7062C05F50500ECCE72 /* OmiseErrorTests.swift in Sources */, 750708E52B790BD600A48DD0 /* SampleData.swift in Sources */, 758A4E972BE38AFD005E7B5A /* SHA512Tests.swift in Sources */, 750708E02B7909BB00A48DD0 /* SourceTests.swift in Sources */, @@ -2225,6 +2232,11 @@ isa = XCSwiftPackageProductDependency; productName = OmiseSwiftUIKit; }; + 75D2C3ED2C19A80C006072D9 /* ThreeDS_SDK */ = { + isa = XCSwiftPackageProductDependency; + package = 758A4E822BE38AC3005E7B5A /* XCRemoteSwiftPackageReference "SPM" */; + productName = ThreeDS_SDK; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2259310F1CE3210700841B86 /* Project object */;