From ca5e791fd66756f331bd8bee09eb57e1be86aa67 Mon Sep 17 00:00:00 2001 From: Andrei Solovev Date: Mon, 27 May 2024 13:20:26 +0700 Subject: [PATCH 01/11] Bump to the next version --- OmiseSDK/Sources/OmiseSDK.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmiseSDK/Sources/OmiseSDK.swift b/OmiseSDK/Sources/OmiseSDK.swift index 172db936..b01d3a99 100644 --- a/OmiseSDK/Sources/OmiseSDK.swift +++ b/OmiseSDK/Sources/OmiseSDK.swift @@ -6,7 +6,7 @@ public class OmiseSDK { public static var shared = OmiseSDK(publicKey: "pkey_") /// OmiseSDK version - public let version: String = "5.2.0" + public let version: String = "5.2.1" /// Public Key associated with this instance of OmiseSDK public let publicKey: String From 3d69d521e14e9bc96f88332860c6093956444d2c Mon Sep 17 00:00:00 2001 From: Andrei Solovev Date: Mon, 27 May 2024 13:36:38 +0700 Subject: [PATCH 02/11] Add comments to empty non-optional closures --- OmiseSDK/Sources/OmiseSDK.swift | 4 +++- OmiseSDK/Sources/Views/Components/TextFieldView.swift | 6 +++--- .../Views/Components/TextFields/OmiseTextField.swift | 2 +- .../Other Payments/Atome/AtomePaymentFormViewModel.swift | 2 +- .../Other Payments/Atome/Base/PaymentFormController.swift | 2 +- .../Other Payments/FPX/FPXPaymentFormController.swift | 2 +- .../Select Payment From Lists/SelectPaymentController.swift | 4 +++- .../SelectPaymentPresentableProtocol.swift | 2 +- .../ViewModels/SelectDuitNowOBWBankViewModel.swift | 2 +- .../ViewModels/SelectFPXBankViewModel.swift | 2 +- .../ViewModels/SelectInstallmentTermsViewModel.swift | 2 +- .../ViewModels/SelectPaymentMethodViewModel.swift | 2 +- .../ViewModels/SelectSourceTypePaymentViewModel.swift | 2 +- .../Screens/Credit Card Payment/CCVInfoController.swift | 2 +- .../Credit Card Payment/CreditCardPaymentViewModel.swift | 3 ++- 15 files changed, 22 insertions(+), 17 deletions(-) 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/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/CreditCardPaymentViewModel.swift b/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentViewModel.swift index 78ab2a36..74951f52 100644 --- a/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentViewModel.swift +++ b/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentViewModel.swift @@ -42,7 +42,8 @@ class CreditCardPaymentViewModel: CreditCardPaymentViewModelProtocol, CountryLis } } } - var onSelectCountry: (Country) -> 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 } From f781e62a2bec37e94d68438664c1b0dfbcc42b13 Mon Sep 17 00:00:00 2001 From: Andrei Solovev Date: Tue, 28 May 2024 23:14:05 +0700 Subject: [PATCH 03/11] Fix SonarCloud complexity warning in OmiseError.APIErrorCode.BadRequestReason --- OmiseSDK/Sources/Models/OmiseError.swift | 62 ++++++++++++++---------- OmiseSDKTests/OmiseErrorTests.swift | 32 ++++++++++++ dev.xcodeproj/project.pbxproj | 4 ++ 3 files changed, 72 insertions(+), 26 deletions(-) create mode 100644 OmiseSDKTests/OmiseErrorTests.swift diff --git a/OmiseSDK/Sources/Models/OmiseError.swift b/OmiseSDK/Sources/Models/OmiseError.swift index c8a7f05f..e1f4e407 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.. Date: Wed, 12 Jun 2024 16:59:38 +0700 Subject: [PATCH 04/11] Refactor CreditCard's configureAccessibility() --- .../CreditCardPaymentController.swift | 206 ++++++++++-------- dev.xcodeproj/project.pbxproj | 8 + 2 files changed, 122 insertions(+), 92 deletions(-) diff --git a/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift b/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift index fab1dcbb..a252554d 100644 --- a/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift +++ b/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift @@ -736,104 +736,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 +949,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.. Date: Mon, 17 Jun 2024 12:45:07 +0700 Subject: [PATCH 05/11] Mute lint warning for .utf8 data -> string conversion --- OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift | 1 + OmiseSDKTests/3DS/SHA512Tests.swift | 5 +++-- OmiseSDKTests/ClientTests.swift | 2 ++ OmiseSDKTests/Helpers/SampleData.swift | 1 + OmiseSDKTests/Helpers/String+JSON.swift | 1 + .../Source and Token JSON Codable Tests/SourceTests.swift | 1 + 6 files changed, 9 insertions(+), 2 deletions(-) diff --git a/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift b/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift index 459daf7a..dfbba5d7 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 { 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/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) From 9d4fee037b6ddcfd884a1998f368bf36e77cf239 Mon Sep 17 00:00:00 2001 From: Andrei Solovev Date: Mon, 17 Jun 2024 13:05:49 +0700 Subject: [PATCH 06/11] Refactor CardExpiryDateTextField --- .../TextFields/CardExpiryDateTextField.swift | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) 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.. Date: Mon, 17 Jun 2024 13:30:38 +0700 Subject: [PATCH 07/11] Refactor NetceteraThreeDSController --- .../3DS/NetceteraThreeDSController.swift | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift b/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift index dfbba5d7..4e5103e1 100644 --- a/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift +++ b/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift @@ -187,18 +187,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 { @@ -222,6 +212,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? From ec50d36b4fb943b01ae6e5338733e910299e2a70 Mon Sep 17 00:00:00 2001 From: Andrei Solovev Date: Mon, 17 Jun 2024 13:33:47 +0700 Subject: [PATCH 08/11] Remove swiftlint disable command --- OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift b/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift index 4e5103e1..464a3d30 100644 --- a/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift +++ b/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift @@ -149,7 +149,6 @@ extension NetceteraThreeDSController: NetceteraThreeDSControllerProtocol { ThreeDSSDKAppDelegate.shared.appOpened(url: url) } - // swiftlint:disable:next function_body_length func processAuthorizedURL( _ authorizeUrl: URL, threeDSRequestorAppURL: String?, From a16e398bd803b80d495d91e65d20d4532e98a732 Mon Sep 17 00:00:00 2001 From: Andrei Solovev Date: Mon, 17 Jun 2024 13:48:38 +0700 Subject: [PATCH 09/11] Refactor OmiseError file --- OmiseSDK/Sources/Models/OmiseError.swift | 167 ++++++++++++++--------- 1 file changed, 102 insertions(+), 65 deletions(-) diff --git a/OmiseSDK/Sources/Models/OmiseError.swift b/OmiseSDK/Sources/Models/OmiseError.swift index e1f4e407..d9a936f3 100644 --- a/OmiseSDK/Sources/Models/OmiseError.swift +++ b/OmiseSDK/Sources/Models/OmiseError.swift @@ -831,7 +831,6 @@ extension OmiseError.APIErrorCode.BadRequestReason: Decodable { return preferredRecoverySuggestionMessage.isEmpty ? nil : preferredRecoverySuggestionMessage } - // swiftlint:disable:next function_body_length static func parseBadRequestReasonsFromMessage(_ message: String, currency: Currency?) throws -> [OmiseError.APIErrorCode.BadRequestReason] { let reasonMessages = message.components(separatedBy: ", and ") .flatMap { $0.components(separatedBy: ", ") } @@ -840,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 From d34c2389a4755f56b4503c7106b95a3a8c1c6978 Mon Sep 17 00:00:00 2001 From: Andrei Solovev Date: Mon, 17 Jun 2024 14:21:52 +0700 Subject: [PATCH 10/11] Refactor OmiseError for SonarCloud warning --- OmiseSDKTests/OmiseErrorTests.swift | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/OmiseSDKTests/OmiseErrorTests.swift b/OmiseSDKTests/OmiseErrorTests.swift index 9e3341f9..e3eb938e 100644 --- a/OmiseSDKTests/OmiseErrorTests.swift +++ b/OmiseSDKTests/OmiseErrorTests.swift @@ -29,4 +29,54 @@ class OmiseErrorTests: XCTestCase { } } } + + 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)") + } + } + } + } From d64105c595ec4d621635aea25e3eb8650752328f Mon Sep 17 00:00:00 2001 From: AnasNaouchi Date: Thu, 14 Nov 2024 14:31:54 +0700 Subject: [PATCH 11/11] Fix discover card and add unionPay --- .../Assets.xcassets/Credit Card/Contents.json | 6 +++--- .../Credit Card/Discover.imageset/Contents.json | 12 ++++++++++++ .../Credit Card/Discover.imageset/Discover.pdf | Bin 0 -> 3225 bytes .../Credit Card/UnionPay.imageset/Contents.json | 12 ++++++++++++ .../Credit Card/UnionPay.imageset/UnionPay.pdf | Bin 0 -> 15213 bytes OmiseSDK/Sources/Models/CardBrand.swift | 13 ++++++++++--- OmiseSDK/Sources/Models/PAN.swift | 12 +----------- .../TextFields/CardNumberTextField.swift | 6 ++++-- .../CreditCardPaymentController.swift | 4 ++++ 9 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 OmiseSDK/Resources/Assets.xcassets/Credit Card/Discover.imageset/Contents.json create mode 100644 OmiseSDK/Resources/Assets.xcassets/Credit Card/Discover.imageset/Discover.pdf create mode 100644 OmiseSDK/Resources/Assets.xcassets/Credit Card/UnionPay.imageset/Contents.json create mode 100644 OmiseSDK/Resources/Assets.xcassets/Credit Card/UnionPay.imageset/UnionPay.pdf 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 0000000000000000000000000000000000000000..2e1fba7defce5ff2e0f05f1f3a923b9310f4828e GIT binary patch literal 3225 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1760195650df0d85012ecce54caa7a3864bf89d8 GIT binary patch literal 15213 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 F`9Fu#l^6g3 literal 0 HcmV?d00001 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/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/Views/Components/TextFields/CardNumberTextField.swift b/OmiseSDK/Sources/Views/Components/TextFields/CardNumberTextField.swift index 82cec6e4..1498c235 100644 --- a/OmiseSDK/Sources/Views/Components/TextFields/CardNumberTextField.swift +++ b/OmiseSDK/Sources/Views/Components/TextFields/CardNumberTextField.swift @@ -119,7 +119,9 @@ public class CardNumberTextField: OmiseTextField { options: .regularExpression, range: nil) - let panLength = (self.pan.brand?.validLengths.upperBound ?? 16) - (self.text?.count ?? 0) + selectedTextLength + // Using the current detected upper bound for the current brand will cause problems when swtching from + // a brand that has an upper bound that is lower than that of the pasted detected card. (ex from visa to discover). So the upper bound will be set to the highest upper bound available for cards which is 19 + let panLength = 19 - (self.text?.count ?? 0) + selectedTextLength let maxPastingPANLength = min(pan.count, panLength) guard maxPastingPANLength > 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/Screens/Credit Card Payment/CreditCardPaymentController.swift b/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift index fab1dcbb..81e62894 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 }