Skip to content

Commit

Permalink
Merge pull request #177 from verygoodsecurity/canary
Browse files Browse the repository at this point in the history
Public Release 1.6.0
  • Loading branch information
dmytrokhl committed Jul 13, 2020
2 parents 1621748 + 3759c77 commit b2a1b58
Show file tree
Hide file tree
Showing 132 changed files with 7,940 additions and 1,388 deletions.
6 changes: 6 additions & 0 deletions .jazzy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,16 @@ custom_categories:
- VGSResponse
- HTTPMethod
- HTTPHeaders
- name: Payment Cards
children:
- VGSPaymentCards
- VGSPaymentCardModel
- VGSUnknownPaymentCardModel
- name: Validation Rules
children:
- VGSValidationRuleSet
- VGSValidationRuleLength
- VGSValidationRuleLengthMatch
- VGSValidationRulePattern
- VGSValidationRulePaymentCard
- VGSValidationRuleLuhnCheck
Expand Down
34 changes: 26 additions & 8 deletions Sources/VGSCollectSDK/Core/Collector/VGSCollect+internal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ internal extension VGSCollect {
return tenantId.isAlphaNumeric
}

/// Validate data region id
class func regionValid(_ region: String) -> Bool {
return !region.isEmpty && region.range(of: ".*[^a-zA-Z0-9-].*", options: .regularExpression) == nil
}

/// Validate stored textfields input data
func validateStoredInputData() -> VGSError? {
return validate(storage.elements)
Expand Down Expand Up @@ -98,17 +103,30 @@ internal extension VGSCollect {
}
// set focus for textField
textField.focusStatus = true

if textField.fieldType == .cardNumber {
// change cvc format pattern based on card brand
if let cvcField = storage.elements.filter({ $0.fieldType == .cvc }).first {
cvcField.textField.formatPattern = textField.cvcFormatPatternForCardType
cvcField.validationRules = VGSValidationRuleSet(rules: [textField.cvcValidationRule])
}
}

// call observers ONLY after all internal updates done
observeStates?(storage.elements)
observeFieldState?(textField)
}

class func generateVaultURL(tenantId: String, environment: Environment, region: String?) -> URL {

var environmentString = environment.rawValue

if let region = region, !region.isEmpty {
if environment == .live {
assert(Self.regionValid(region), "ERROR: DATA REGION IS NOT VALID!!!")
environmentString += "-" + region
} else {
print("NOTE: DATA REGION SHOULD BE USED WITH LIVE ENVIRONMENT ONLY!!!")
}
}
assert(Self.tenantIDValid(tenantId), "ERROR: TENANT ID IS NOT VALID!!!")

let strUrl = "https://" + tenantId + "." + environmentString + ".verygoodproxy.com"
guard let url = URL(string: strUrl) else {
fatalError("ERROR: NOT VALID ORGANIZATION PARAMETERS!!!")
}
return url
}
}
13 changes: 5 additions & 8 deletions Sources/VGSCollectSDK/Core/Collector/VGSCollect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,12 @@ public class VGSCollect {
/// - Parameters:
/// - id: your organization vault id.
/// - environment: your organization vault environment. By default `Environment.sandbox`.
public init(id: String, environment: Environment = .sandbox) {
assert(Self.tenantIDValid(id), "Error: vault id is not valid!")
let strUrl = "https://" + id + "." + environment.rawValue + ".verygoodproxy.com"
guard let url = URL(string: strUrl) else {
fatalError("Upstream Host is broken. Can't to converting to URL!")
}
apiClient = APIClient(baseURL: url)
/// - dataRegion: id of data storage region (e.g. "eu-123"). Effects ONLY `Environment.live` vaults.
public init(id: String, environment: Environment = .sandbox, dataRegion: String? = nil) {
let url = Self.generateVaultURL(tenantId: id, environment: environment, region: dataRegion)
apiClient = APIClient(baseURL: url)
}

// MARK: - Helper functions

/// Detach files for associated `VGSCollect` instance.
Expand Down
43 changes: 23 additions & 20 deletions Sources/VGSCollectSDK/Core/Enums.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,22 @@ internal extension FieldType {
}
}

var regex: String {
var defaultRegex: String {
switch self {
case .cardNumber:
return "^(?:4[0-9]{12}(?:[0-9]{3})?|[25][1-7][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$"
case .cvc:
return VGSTextField.cvcRegexForAnyCardType
case .expDate:
return "^(0[1-9]|1[0-2])\\/?([0-9]{4}|[0-9]{2})$"
case .cardHolderName:
return "^([a-zA-Z0-9\\ \\,\\.\\-\\']{2,})$"
case .ssn:
return
"^(?!\\b(\\d)\\1+\\b)(?!(123456789|219099999|078051120|457555462))(?!(000|666|9))(\\d{3}-?(?!(00))\\d{2}-?(?!(0000))\\d{4})$"
default:
return ""
}
case .cvc:
return "\\d*$"
case .none:
return ""
}
}

var keyboardType: UIKeyboardType {
Expand All @@ -97,21 +97,24 @@ internal extension FieldType {
}
}

var defaultValidation: VGSValidationRuleSet {
var rules = VGSValidationRuleSet()
switch self {
case .cardHolderName, .ssn, .cvc:
rules.add(rule: VGSValidationRulePattern(pattern: self.regex, error: VGSValidationErrorType.pattern.rawValue))
case .expDate:
rules.add(rule: VGSValidationRulePattern(pattern: self.regex, error: VGSValidationErrorType.pattern.rawValue))
rules.add(rule: VGSValidationRuleCardExpirationDate(error: VGSValidationErrorType.expDate.rawValue))
case .cardNumber:
rules.add(rule: VGSValidationRulePaymentCard(error: VGSValidationErrorType.cardNumber.rawValue))
case .none:
rules = VGSValidationRuleSet()
var defaultValidation: VGSValidationRuleSet {
var rules = VGSValidationRuleSet()
switch self {
case .cardHolderName, .ssn:
rules.add(rule: VGSValidationRulePattern(pattern: self.defaultRegex, error: VGSValidationErrorType.pattern.rawValue))
case .expDate:
rules.add(rule: VGSValidationRulePattern(pattern: self.defaultRegex, error: VGSValidationErrorType.pattern.rawValue))
rules.add(rule: VGSValidationRuleCardExpirationDate(error: VGSValidationErrorType.expDate.rawValue))
case .cardNumber:
rules.add(rule: VGSValidationRulePaymentCard(error: VGSValidationErrorType.cardNumber.rawValue))
case .cvc:
rules.add(rule: VGSValidationRulePattern(pattern: self.defaultRegex, error: VGSValidationErrorType.pattern.rawValue))
rules.add(rule: VGSValidationRuleLengthMatch(lengths: [3, 4], error: VGSValidationErrorType.lengthMathes.rawValue))
case .none:
rules = VGSValidationRuleSet()
}
return rules
}
return rules
}
}

internal enum DateFormatPattern: String {
Expand Down
160 changes: 160 additions & 0 deletions Sources/VGSCollectSDK/Core/PaymentCards/CardBrand+default.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//
// CardBrand+default.swift
// VGSCollectSDK
//
// Created by Dima on 10.07.2020.
// Copyright © 2020 VGS. All rights reserved.
//

import Foundation

/// Default CardBrand settings
internal extension VGSPaymentCards.CardBrand {

/// Returns regex for specific card brand detection
var defaultRegex: String {
switch self {
case .amex:
return "^3[47]\\d*$"
case .dinersClub:
return "^3(?:[689]|(?:0[059]+))\\d*$"
case .discover:
return "^(6011|65|64[4-9]|622)\\d*$"
case .unionpay:
return "^62\\d*$"
case .jcb:
return "^35\\d*$"
case .mastercard:
return "^(5[1-5]|677189)\\d*$|^(222[1-9]|2[3-6]\\d{2,}|27[0-1]\\d|2720)([0-9]{2,})\\d*$"
case .visaElectron:
return "^4(026|17500|405|508|844|91[37])\\d*$"
case .visa:
return "^4\\d*$"
case .maestro:
return "^(5018|5020|5038|6304|6390[0-9]{2,}|67[0-9]{4,})\\d*$"
case .forbrugsforeningen:
return "^600\\d*$"
case .dankort:
return "^5019\\d*$"
case .elo:
return "^(4011(78|79)|43(1274|8935)|45(1416|7393|763(1|2))|50(4175|6699|67[0-7][0-9]|9000)|627780|63(6297|6368)|650(03([^4])|04([0-9])|05(0|1)|4(0[5-9]|3[0-9]|8[5-9]|9[0-9])|5([0-2][0-9]|3[0-8])|9([2-6][0-9]|7[0-8])|541|700|720|901)|651652|655000|655021)\\d*$"
case .hipercard:
return "^(384100|384140|384160|606282|637095|637568|60(?!11))\\d*$"
case .unknown:
return "^\\d*$"
case .custom(brandName: _):
return ""
}
}

var defaultCardLengths: [Int] {
switch self {
case .amex:
return [15]
case .dinersClub:
return [14, 16]
case .discover:
return [16]
case .unionpay:
return [16, 17, 18, 19]
case .jcb:
return [16, 17, 18, 19]
case .mastercard:
return [16]
case .visaElectron:
return [16]
case .visa:
return [13, 16, 19]
case .maestro:
return [12, 13, 14, 15, 16, 17, 18, 19]
case .elo:
return [16]
case .forbrugsforeningen:
return [16]
case .dankort:
return [16]
case .hipercard:
return [14, 15, 16, 17, 18, 19]
case .unknown:
return [16, 17, 18, 19]
case .custom(brandName: _):
return []
}
}

var defaultCVCLengths: [Int] {
switch self {
case .amex:
return [4]
case .unknown:
return [3, 4]
default:
return [3]
}
}

var cvcFormatPattern: String {
var maxLength = 0
if let cardBrand = VGSPaymentCards.availableCards.first(where: { $0.brand == self }) {
maxLength = cardBrand.cvcLengths.max() ?? 0
} else {
maxLength = VGSPaymentCards.unknown.cvcLengths.max() ?? 0
}
return String(repeating: "#", count: maxLength)
}

var defaultFormatPattern: String {
switch self {
case .amex:
return "#### ###### #####"
case .dinersClub:
return "#### ###### ######"
default:
return "#### #### #### #### ###"
}
}

var defaultName: String {
switch self {
case .amex:
return "American Express"
case .visa:
return "Visa"
case .visaElectron:
return "Visa Electron"
case .mastercard:
return "Mastercard"
case .discover:
return "Discover"
case .dinersClub:
return "Diners Club"
case .unionpay:
return "UnionPay"
case .jcb:
return "JCB"
case .maestro:
return "Maestro"
case .elo:
return "ELO"
case .forbrugsforeningen:
return "Forbrugsforeningen"
case .dankort:
return "Dankort"
case .hipercard:
return "HiperCard"
case .unknown:
return "Unknown"
case .custom(brandName: _):
return ""
}
}

var defaultCheckSumAlgorithm: CheckSumAlgorithmType? {
switch self {
case .unionpay:
return nil
default:
return .luhn
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// VGSCustomPaymentCardModel.swift
// VGSCollectSDK
//
// Created by Dima on 09.07.2020.
// Copyright © 2020 VGS. All rights reserved.
//

import Foundation

public struct VGSCustomPaymentCardModel: VGSPaymentCardModelProtocol {

/// Payment Card Brand
public let brand: VGSPaymentCards.CardBrand

/// Payment Card Name
public var name: String

/// Regex Pattern required to detect Payment Card Brand
public var regex: String

/// Valid Card Number Lengths
public var cardNumberLengths: [Int]

/// Valid Card CVC/CVV Lengths. For most brands valid cvc lengths is [3], while for Amex is [4]. For unknown brands can be set as [3, 4]
public var cvcLengths: [Int]

/// Check sum validation algorithm. For most brands card number can be validated by `CheckSumAlgorithmType.luhn` algorithm. If `none` - result of Checksum Algorithm validation will be `true`.
public var checkSumAlgorithm: CheckSumAlgorithmType?

/// Payment Card Number visual format pattern.
/// - Note: format pattern length limits input length.
public var formatPattern: String

/// Image, associated with Payment Card Brand.
public var brandIcon: UIImage?

// MARK: - Initialzation

public init(name: String, regex: String, formatPattern: String, cardNumberLengths: [Int], cvcLengths: [Int] = [3], checkSumAlgorithm: CheckSumAlgorithmType? = .luhn, brandIcon: UIImage?) {
self.brand = .custom(brandName: name)
self.name = name
self.regex = regex
self.formatPattern = formatPattern
self.cardNumberLengths = cardNumberLengths
self.cvcLengths = cvcLengths
self.checkSumAlgorithm = checkSumAlgorithm
self.brandIcon = brandIcon
}
}
Loading

0 comments on commit b2a1b58

Please sign in to comment.