Skip to content

Commit

Permalink
feat(POM-279): migrate card tokenization to SwiftUI (#179)
Browse files Browse the repository at this point in the history
* Expose needed formatters and utils from ProcessOut target as _spi
* Create UI target that will host all available UI modules
* Move interactor to new target
* Rewrite card tokenization view model and view in SwiftUI
  • Loading branch information
andrii-vysotskyi-cko authored Oct 23, 2023
1 parent 29108fd commit 8ed03cf
Show file tree
Hide file tree
Showing 99 changed files with 1,987 additions and 1,600 deletions.
18 changes: 0 additions & 18 deletions Sources/ProcessOut/Resources/Strings/en.lproj/ProcessOut.strings
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,3 @@
"test-3-d-s.challenge.title" = "Do you want to accept the 3DS2 challenge?";
"test-3-d-s.challenge.accept" = "Accept";
"test-3-d-s.challenge.reject" = "Reject";

"card-tokenization.title" = "Add New Card";
"card-tokenization.card-details.title" = "Card Information";
"card-tokenization.card-details.number.placeholder" = "4242 4242 4242 4242";
"card-tokenization.card-details.expiration.placeholder" = "MM / YY";
"card-tokenization.card-details.cvc.placeholder" = "CVC";
"card-tokenization.card-details.cardholder.placeholder" = "Cardholder Name";
"card-tokenization.preferred-scheme.title" = "Preferred Scheme";
"card-tokenization.preferred-scheme.description" = "%@";
"card-tokenization.error.card" = "Your card information is invalid.";
"card-tokenization.error.card-number" = "Your card number is invalid.";
"card-tokenization.error.card-expiration" = "Your card expiration date is invalid.";
"card-tokenization.error.track-data" = "Your card expiration date and/or CVC is invalid.";
"card-tokenization.error.cvc" = "Your card CVC is invalid.";
"card-tokenization.error.cardholder-name" = "The cardholder name is invalid.";
"card-tokenization.error.generic" = "Something went wrong, please try again.";
"card-tokenization.submit-button.title" = "Submit";
"card-tokenization.cancel-button.title" = "Cancel";
16 changes: 0 additions & 16 deletions Sources/ProcessOut/Resources/Strings/pl.lproj/ProcessOut.strings
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,3 @@
"test-3-d-s.challenge.title" = "Czy chcesz przyjąć wyzwanie 3DS2?";
"test-3-d-s.challenge.accept" = "Zaakceptować";
"test-3-d-s.challenge.reject" = "Odrzucić";

"card-tokenization.title" = "Dodaj nową kartę";
"card-tokenization.card-details.title" = "Dane karty";
"card-tokenization.card-details.number.placeholder" = "4242 4242 4242 4242";
"card-tokenization.card-details.expiration.placeholder" = "MM / RR";
"card-tokenization.card-details.cvc.placeholder" = "CVC";
"card-tokenization.card-details.cardholder.placeholder" = "Imię i nazwisko na karcie";
"card-tokenization.error.card" = "Niepoprawne dane karty.";
"card-tokenization.error.card-number" = "Nieprawidłowy numer karty.";
"card-tokenization.error.card-expiration" = "Data ważności karty jest nieprawidłowa.";
"card-tokenization.error.track-data" = "Data ważności karty oraz/lub kod CVC są nieprawidłowe.";
"card-tokenization.error.cvc" = "Nieprawidłowy numer CVC.";
"card-tokenization.error.cardholder-name" = "Nieprawidłowe imię i nazwisko na karcie.";
"card-tokenization.error.generic" = "Coś poszło nie tak, spróbuj ponownie.";
"card-tokenization.submit-button.title" = "Zatwierdź";
"card-tokenization.cancel-button.title" = "Anuluj";
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,3 @@
"test-3-d-s.challenge.title" = "Você quer aceitar o desafio 3DS2?";
"test-3-d-s.challenge.accept" = "Aceitar";
"test-3-d-s.challenge.reject" = "Rejeitar";

"card-tokenization.title" = "Adicionar novo cartão";
"card-tokenization.card-details.title" = "Dados do cartão";
"card-tokenization.card-details.number.placeholder" = "4242 4242 4242 4242";
"card-tokenization.card-details.expiration.placeholder" = "MM / AA";
"card-tokenization.card-details.cvc.placeholder" = "CVC";
"card-tokenization.card-details.cardholder.placeholder" = "Nome no cartão";
"card-tokenization.error.card" = "Os dados do seu cartão são inválidos.";
"card-tokenization.error.card-number" = "O número do seu cartão é inválido.";
"card-tokenization.error.card-expiration" = "A data de validade do seu cartão é inválida.";
"card-tokenization.error.track-data" = "A data de validade e/ou o código de segurança do seu cartão são inválidos.";
"card-tokenization.error.cvc" = "O código de segurança do seu cartão é inválido.";
"card-tokenization.error.cardholder-name" = "O nome no cartão é inválido.";
"card-tokenization.error.generic" = "Ocorreu um erro ao processar o seu cartão, por favor tente novamente.";
"card-tokenization.submit-button.title" = "Enviar";
"card-tokenization.cancel-button.title" = "Cancelar";
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
//
// CardExpirationFormatter.swift
// POCardExpirationFormatter.swift
// ProcessOut
//
// Created by Andrii Vysotskyi on 21.07.2023.
//

import Foundation

final class CardExpirationFormatter: Formatter {
@_spi(PO) public final class POCardExpirationFormatter: Formatter {

override init() {
override public init() {
regexProvider = RegexProvider.shared
super.init()
}
Expand All @@ -20,37 +20,37 @@ final class CardExpirationFormatter: Formatter {
}

/// Returns formatted version of given expiration string.
func string(from string: String) -> String {
public func string(from string: String) -> String {
let expiration = self.expiration(from: string)
guard !expiration.month.isEmpty else {
return ""
}
return formatted(month: expiration.month, year: expiration.year)
}

func expirationMonth(from string: String) -> Int? {
public func expirationMonth(from string: String) -> Int? {
let monthDescription = expiration(from: string).month
guard let month = Int(monthDescription), month > 0, month <= 12 else {
return nil
}
return month
}

func expirationYear(from string: String) -> Int? {
public func expirationYear(from string: String) -> Int? {
let yearDescription = expiration(from: string).year
return Int(yearDescription)
}

// MARK: - Formatter

override func string(for obj: Any?) -> String? {
override public func string(for obj: Any?) -> String? {
guard let phoneNumber = obj as? String else {
return nil
}
return string(from: phoneNumber)
}

override func isPartialStringValid(
override public func isPartialStringValid(
_ partialStringPtr: AutoreleasingUnsafeMutablePointer<NSString>, // swiftlint:disable:this legacy_objc_type
proposedSelectedRange proposedSelRangePtr: NSRangePointer?,
originalString origString: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
//
// CardNumberFormatter.swift
// POCardNumberFormatter.swift
// ProcessOut
//
// Created by Andrii Vysotskyi on 18.07.2023.
//

import Foundation

final class CardNumberFormatter: Formatter {
@_spi(PO) public final class POCardNumberFormatter: Formatter {

func string(from partialNumber: String) -> String {
public func string(from partialNumber: String) -> String {
let normalizedNumber = normalized(number: partialNumber).prefix(Constants.maxLength)
for format in formats {
if let formattedNumber = attemptToFormat(cardNumber: normalizedNumber, format: format) {
Expand All @@ -19,20 +19,20 @@ final class CardNumberFormatter: Formatter {
return attemptToFormat(cardNumber: normalizedNumber, pattern: Constants.defaultPattern) ?? partialNumber
}

func normalized(number: String) -> String {
public func normalized(number: String) -> String {
number.removingCharacters(in: Constants.significantCharacters.inverted)
}

// MARK: - Formatter

override func string(for obj: Any?) -> String? {
override public func string(for obj: Any?) -> String? {
guard let cardNumber = obj as? String else {
return nil
}
return string(from: cardNumber)
}

override func isPartialStringValid(
override public func isPartialStringValid(
_ partialStringPtr: AutoreleasingUnsafeMutablePointer<NSString>, // swiftlint:disable:this legacy_objc_type
proposedSelectedRange proposedSelRangePtr: NSRangePointer?,
originalString origString: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
//
// LogInterpolation.swift
// POLogInterpolation.swift
// ProcessOut
//
// Created by Andrii Vysotskyi on 25.10.2022.
//

struct LogInterpolation: StringInterpolationProtocol {
@_spi(PO) public struct POLogInterpolation: StringInterpolationProtocol {

/// Privacy options for specifying privacy level of the interpolated expressions
/// in the string interpolations passed to the log APIs.
enum Privacy {
public enum Privacy {

/// Sets the privacy level of an interpolated value to public.
///
Expand All @@ -27,11 +27,11 @@ struct LogInterpolation: StringInterpolationProtocol {
/// Interpolation's content.
private(set) var value: String

mutating func appendLiteral(_ literal: String) {
public mutating func appendLiteral(_ literal: String) {
value.append(literal)
}

mutating func appendInterpolation(_ value: String, privacy: Privacy = .public) {
public mutating func appendInterpolation(_ value: String, privacy: Privacy = .public) {
switch privacy {
case .public:
self.value.append(value)
Expand All @@ -40,15 +40,17 @@ struct LogInterpolation: StringInterpolationProtocol {
}
}

mutating func appendInterpolation<Value: CustomStringConvertible>(_ value: Value, privacy: Privacy = .public) {
public mutating func appendInterpolation<Value: CustomStringConvertible>(
_ value: Value, privacy: Privacy = .public
) {
appendInterpolation(value.description, privacy: privacy)
}

mutating func appendInterpolation(_ error: Error, privacy: Privacy = .public) {
public mutating func appendInterpolation(_ error: Error, privacy: Privacy = .public) {
appendInterpolation(String(describing: error), privacy: privacy)
}

init(literalCapacity: Int, interpolationCount: Int) {
public init(literalCapacity: Int, interpolationCount: Int) {
value = String()
value.reserveCapacity(literalCapacity)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
//
// LogMessage.swift
// POLogMessage.swift
// ProcessOut
//
// Created by Andrii Vysotskyi on 25.10.2022.
//

struct LogMessage: ExpressibleByStringInterpolation {
@_spi(PO) public struct POLogMessage: ExpressibleByStringInterpolation {

let interpolation: LogInterpolation
let interpolation: POLogInterpolation

/// Creates an instance from a string interpolation.
init(stringInterpolation: LogInterpolation) {
public init(stringInterpolation: POLogInterpolation) {
self.interpolation = stringInterpolation
}

/// Creates an instance initialized to the given string value.
///
/// - Parameter value: The value of the new instance.
init(stringLiteral value: String) {
var interpolation = LogInterpolation(literalCapacity: value.count, interpolationCount: 0)
public init(stringLiteral value: String) {
var interpolation = POLogInterpolation(literalCapacity: value.count, interpolationCount: 0)
interpolation.appendLiteral(value)
self.interpolation = interpolation
}
Expand Down
18 changes: 9 additions & 9 deletions Sources/ProcessOut/Sources/Core/Logger/POLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ public struct POLogger {
let category: String

/// Logs a message at the `debug` level.
func debug(
_ message: LogMessage,
@_spi(PO) public func debug(
_ message: POLogMessage,
attributes: [String: String] = [:],
dso: UnsafeRawPointer? = #dsohandle,
file: String = #file,
Expand All @@ -43,8 +43,8 @@ public struct POLogger {
}

/// Logs a message at the `info` level.
func info(
_ message: LogMessage,
@_spi(PO) public func info(
_ message: POLogMessage,
attributes: [String: String] = [:],
dso: UnsafeRawPointer? = #dsohandle,
file: String = #file,
Expand All @@ -54,8 +54,8 @@ public struct POLogger {
}

/// Logs a message at the `error` level.
func error(
_ message: LogMessage,
@_spi(PO) public func error(
_ message: POLogMessage,
attributes: [String: String] = [:],
dso: UnsafeRawPointer? = #dsohandle,
file: String = #file,
Expand All @@ -65,8 +65,8 @@ public struct POLogger {
}

/// Logs a message at the `fault` level.
func fault(
_ message: LogMessage,
@_spi(PO) public func fault(
_ message: POLogMessage,
attributes: [String: String] = [:],
dso: UnsafeRawPointer? = #dsohandle,
file: String = #file,
Expand Down Expand Up @@ -94,7 +94,7 @@ public struct POLogger {
/// - attributes: additional attributes to log alongside primary logger attributes.
private func log(
level: LogLevel,
_ message: LogMessage,
_ message: POLogMessage,
attributes additionalAttributes: [String: String] = [:],
dso: UnsafeRawPointer?,
file: String,
Expand Down
2 changes: 1 addition & 1 deletion Sources/ProcessOut/Sources/Core/Utils/MathUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

/// Returns an integer number raised to a given power.
/// - NOTE: Implementation uses exponentiation by squaring algorithm.
func pow(_ x: Int, _ y: Int) -> Int { // swiftlint:disable:this identifier_name
@_spi(PO) public func pow(_ x: Int, _ y: Int) -> Int { // swiftlint:disable:this identifier_name
assert(y >= 0, "Negative exponent is not supported.")
var base = x, exp = y, result = 1
while true {
Expand Down
56 changes: 0 additions & 56 deletions Sources/ProcessOut/Sources/Generated/Strings+Generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,62 +10,6 @@ import Foundation
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
internal enum Strings {
internal enum CardTokenization {
/// Add New Card
internal static var title: String { return Strings.tr("ProcessOut", "card-tokenization.title", fallback: "Add New Card") }
internal enum CancelButton {
/// Cancel
internal static var title: String { return Strings.tr("ProcessOut", "card-tokenization.cancel-button.title", fallback: "Cancel") }
}
internal enum CardDetails {
/// Card Information
internal static var title: String { return Strings.tr("ProcessOut", "card-tokenization.card-details.title", fallback: "Card Information") }
internal enum Cardholder {
/// Cardholder Name
internal static var placeholder: String { return Strings.tr("ProcessOut", "card-tokenization.card-details.cardholder.placeholder", fallback: "Cardholder Name") }
}
internal enum Cvc {
/// CVC
internal static var placeholder: String { return Strings.tr("ProcessOut", "card-tokenization.card-details.cvc.placeholder", fallback: "CVC") }
}
internal enum Expiration {
/// MM / YY
internal static var placeholder: String { return Strings.tr("ProcessOut", "card-tokenization.card-details.expiration.placeholder", fallback: "MM / YY") }
}
internal enum Number {
/// 4242 4242 4242 4242
internal static var placeholder: String { return Strings.tr("ProcessOut", "card-tokenization.card-details.number.placeholder", fallback: "4242 4242 4242 4242") }
}
}
internal enum Error {
/// Your card information is invalid.
internal static var card: String { return Strings.tr("ProcessOut", "card-tokenization.error.card", fallback: "Your card information is invalid.") }
/// Your card expiration date is invalid.
internal static var cardExpiration: String { return Strings.tr("ProcessOut", "card-tokenization.error.card-expiration", fallback: "Your card expiration date is invalid.") }
/// Your card number is invalid.
internal static var cardNumber: String { return Strings.tr("ProcessOut", "card-tokenization.error.card-number", fallback: "Your card number is invalid.") }
/// The cardholder name is invalid.
internal static var cardholderName: String { return Strings.tr("ProcessOut", "card-tokenization.error.cardholder-name", fallback: "The cardholder name is invalid.") }
/// Your card CVC is invalid.
internal static var cvc: String { return Strings.tr("ProcessOut", "card-tokenization.error.cvc", fallback: "Your card CVC is invalid.") }
/// Something went wrong, please try again.
internal static var generic: String { return Strings.tr("ProcessOut", "card-tokenization.error.generic", fallback: "Something went wrong, please try again.") }
/// Your card expiration date and/or CVC is invalid.
internal static var trackData: String { return Strings.tr("ProcessOut", "card-tokenization.error.track-data", fallback: "Your card expiration date and/or CVC is invalid.") }
}
internal enum PreferredScheme {
/// %@
internal static func description(_ p1: Any) -> String {
return Strings.tr("ProcessOut", "card-tokenization.preferred-scheme.description", String(describing: p1), fallback: "%@")
}
/// Preferred Scheme
internal static var title: String { return Strings.tr("ProcessOut", "card-tokenization.preferred-scheme.title", fallback: "Preferred Scheme") }
}
internal enum SubmitButton {
/// Submit
internal static var title: String { return Strings.tr("ProcessOut", "card-tokenization.submit-button.title", fallback: "Submit") }
}
}
internal enum NativeAlternativePayment {
/// ProcessOut.strings
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,20 @@ public struct POCardIssuerInformation: Decodable {

/// Card category.
public let category: String?

@_spi(PO) public init(
scheme: String,
coScheme: String? = nil,
type: String? = nil,
bankName: String? = nil,
brand: String? = nil,
category: String? = nil
) {
self.scheme = scheme
self.coScheme = coScheme
self.type = type
self.bankName = bankName
self.brand = brand
self.category = category
}
}
Loading

0 comments on commit 8ed03cf

Please sign in to comment.