diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example.xcodeproj/project.pbxproj b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example.xcodeproj/project.pbxproj index 592c6dd..5fe7b8a 100644 --- a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example.xcodeproj/project.pbxproj +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 04345A442B209A30006B40CC /* ButtonProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04345A432B209A30006B40CC /* ButtonProgressView.swift */; }; 04345A472B20DD0C006B40CC /* Formatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04345A462B20DD0C006B40CC /* Formatters.swift */; }; 04345A4A2B21D6F1006B40CC /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04345A492B21D6F1006B40CC /* Appearance.swift */; }; + 043701CF2B88A9330019696B /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043701CE2B88A9330019696B /* Constants.swift */; }; 04420E752B11005000EA8790 /* TextDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04420E742B11005000EA8790 /* TextDivider.swift */; }; 04420E772B110DD100EA8790 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 04420E762B110DD100EA8790 /* Localizable.xcstrings */; }; 04420E792B14A84500EA8790 /* ExampleAppLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04420E782B14A84500EA8790 /* ExampleAppLogger.swift */; }; @@ -79,6 +80,7 @@ 04345A432B209A30006B40CC /* ButtonProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonProgressView.swift; sourceTree = ""; }; 04345A462B20DD0C006B40CC /* Formatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Formatters.swift; sourceTree = ""; }; 04345A492B21D6F1006B40CC /* Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appearance.swift; sourceTree = ""; }; + 043701CE2B88A9330019696B /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 04420E742B11005000EA8790 /* TextDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextDivider.swift; sourceTree = ""; }; 04420E762B110DD100EA8790 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; 04420E782B14A84500EA8790 /* ExampleAppLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleAppLogger.swift; sourceTree = ""; }; @@ -149,6 +151,7 @@ children = ( 04345A462B20DD0C006B40CC /* Formatters.swift */, 04345A492B21D6F1006B40CC /* Appearance.swift */, + 043701CE2B88A9330019696B /* Constants.swift */, ); path = Utilities; sourceTree = ""; @@ -463,6 +466,7 @@ 042391112AF3B17F00833025 /* LaunchScreenViewController.swift in Sources */, 04D781562AF517BD00A3B29B /* ImageColorInverterModifier.swift in Sources */, 04FAF9FA2AE81563002E4BAE /* ExampleApp.swift in Sources */, + 043701CF2B88A9330019696B /* Constants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/ContentView.swift b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/ContentView.swift index bbd10fa..44d4f8a 100644 --- a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/ContentView.swift +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/ContentView.swift @@ -9,18 +9,15 @@ import SwiftUI struct ContentView: View { - let service: PrimerDataService - - @StateObject var settingsModel: SettingsModel = .init() - + @StateObject var settingsModel: SettingsModel + + init(service: PrimerDataService) { + _settingsModel = StateObject(wrappedValue: SettingsModel(service: service)) + } + var body: some View { - StartPage(service: service, settingsModel: settingsModel) + StartPage(settingsModel: settingsModel) .navigationTitle("App.Title") .navigationBarTitleDisplayMode(.inline) - .onReceive(settingsModel.$clientToken, perform: onReceive(clientToken:)) - } - - func onReceive(clientToken: String) { - service.clientToken = clientToken } } diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/ExampleApp.swift b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/ExampleApp.swift index 4cb1949..198d18f 100644 --- a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/ExampleApp.swift +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/ExampleApp.swift @@ -16,7 +16,7 @@ struct ExampleApp: App { static var clientToken = "" // 👇 You can point to a server that provides the client token here - static var clientTokenUrl = "" + static var clientTokenUrl = "https://my.glitch.server/" let service = PrimerDataService(clientToken: Self.clientToken) diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Models/PrimerCardDataErrorsModel.swift b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Models/PrimerCardDataErrorsModel.swift index 97190fe..54b9282 100644 --- a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Models/PrimerCardDataErrorsModel.swift +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Models/PrimerCardDataErrorsModel.swift @@ -10,9 +10,9 @@ import PrimerSDK class PrimerCardDataErrorsModel: PrimerBaseCardDataModel { - override init() { + init(service: PrimerDataService) { super.init() - logger.info("[PrimerCardDataErrorsModel.init]") + service.errorsDelegate = self } fileprivate func clearErrors() { diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Models/PrimerCardDataModel.swift b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Models/PrimerCardDataModel.swift index 7e8ff66..282f41c 100644 --- a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Models/PrimerCardDataModel.swift +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Models/PrimerCardDataModel.swift @@ -47,31 +47,38 @@ class PrimerCardDataModel: PrimerBaseCardDataModel { var shouldDisplayCardSelectionView: Bool { return !cardNumber.isEmpty && !cardNetworksModel.cardNetworks.isEmpty } + + let service: PrimerDataService + + init(service: PrimerDataService) { + self.service = service + super.init() + objectWillChange.sink { + DispatchQueue.main.async { + self.service.update(withModel: self) + } + }.store(in: &cancellables) + service.modelsDelegate = self + } - weak var service: PrimerDataService? - + func makePayment(_ completion: @escaping (PaymentResultModel) -> Void) { + service.makePayment { result in + completion(result) + } + } + func updateCardNetworks(with networks: [CardDisplayModel]) { cardNetworksModel.cardNetworks = networks if !networks.isEmpty { selectCardNetwork(at: 0) } } - + func selectCardNetwork(at index: Int) { selectedCardNetwork = cardNetworksModel.cardNetworks[index].value objectWillChange.send() } - - override init() { - super.init() - logger.info("[PrimerCardDataModel.init]") - objectWillChange.sink { - DispatchQueue.main.async { - self.service?.update(withModel: self) - } - }.store(in: &cancellables) - } - + var isEmpty: Bool { [cardNumber, expiryDate, cvvNumber, cardholderName].allSatisfy { $0.isEmpty } } diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Models/SettingsModel.swift b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Models/SettingsModel.swift index 7dab212..fa23cc4 100644 --- a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Models/SettingsModel.swift +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Models/SettingsModel.swift @@ -15,6 +15,8 @@ class SettingsModel: ObservableObject { case clientTokenUrl = "CLIENT_TOKEN_URL" } + let service: PrimerDataService + @Published var clientToken: String = "" @Published var clientTokenUrl: String = "" { @@ -27,7 +29,9 @@ class SettingsModel: ObservableObject { @Published var fetchErrorMessage: String? = nil - init() { + init(service: PrimerDataService) { + self.service = service + if let clientToken = UserDefaults.standard.string(forKey: Key.clientToken.rawValue) { self.clientToken = clientToken } else if !ExampleApp.clientToken.isEmpty { @@ -38,13 +42,51 @@ class SettingsModel: ObservableObject { } else { self.clientTokenUrl = ExampleApp.clientTokenUrl } - } var isClientTokenValid: Bool { return !clientToken.isEmpty && isValidJWT(clientToken) } - + + func updateClientToken() async throws { + DispatchQueue.main.sync { + fetchErrorMessage = nil + } + do { + let clientToken = try await service.fetchClientToken(from: clientTokenUrl) + DispatchQueue.main.sync { + self.clientToken = clientToken + } + } catch { + DispatchQueue.main.sync { + fetchErrorMessage = ErrorMessages.clientTokenFetch(clientTokenUrl: clientTokenUrl) + clientToken = "" + } + throw error + } + } + + func setup() async throws { + if !isClientTokenValid { + try await updateClientToken() + } + + do { + try await service.start() + service.configureForPayments() + } catch { + logger.error(error.localizedDescription) + fetchErrorMessage = ErrorMessages.sdkStart + clientToken = "" + + if let error = error as? PrimerDataService.Error { + logger.error(error.message) + } + + throw error + } + } + // MARK: Helpers var isConfiguredForMakingPayment: Bool { diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Services/PrimerDataService.swift b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Services/PrimerDataService.swift index 10e80ca..5010c2e 100644 --- a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Services/PrimerDataService.swift +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Services/PrimerDataService.swift @@ -37,6 +37,15 @@ class PrimerDataService: NSObject { case failedToFetchClientToken(error: Swift.Error) case failedToInitialiseSDK(error: Swift.Error) case paymentFailed(error: Swift.Error) + + var message: String { + switch self { + case .failedToInitialiseSDK(let error), + .failedToFetchClientToken(let error), + .paymentFailed(let error): + return error.localizedDescription + } + } } var clientToken: String diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Utilities/Appearance.swift b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Utilities/Appearance.swift index 38902e0..b0451e3 100644 --- a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Utilities/Appearance.swift +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Utilities/Appearance.swift @@ -8,7 +8,6 @@ import UIKit final class Appearance { - static func setup() { UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.white] UINavigationBar.appearance().tintColor = .white @@ -17,5 +16,4 @@ final class Appearance { UINavigationBar.appearance().isTranslucent = false UIBarButtonItem.appearance().tintColor = .white } - } diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Utilities/Constants.swift b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Utilities/Constants.swift new file mode 100644 index 0000000..d8d6488 --- /dev/null +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Utilities/Constants.swift @@ -0,0 +1,23 @@ +// +// Constants.swift +// Co-Badged Cards Example +// +// Created by Jack Newcombe on 23/02/2024. +// + +import Foundation + +struct ErrorMessages { + private init() {} + + static let sdkStart = "There was an error starting the SDK - check your configuration." + + static func clientTokenFetch(clientTokenUrl: String) -> String { +""" +Could not fetch a client token from: +POST \(clientTokenUrl) +Make sure the server is running and that your network connection is working. +""" + } + +} diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Pages/CardFormFullPageView.swift b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Pages/CardFormFullPageView.swift index acc5645..25f39ee 100644 --- a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Pages/CardFormFullPageView.swift +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Pages/CardFormFullPageView.swift @@ -10,16 +10,19 @@ import Combine struct CardFormFullPageView: View { - let service: PrimerDataService + let model: PrimerCardDataModel - var model: PrimerCardDataModel = .init() - - @StateObject var errorsModel: PrimerCardDataErrorsModel = .init() + @StateObject var errorsModel: PrimerCardDataErrorsModel var cancellables: Set = [] @State var paymentModel: PaymentResultModel? - + + init(model: PrimerCardDataModel, errorsModel: PrimerCardDataErrorsModel) { + self.model = model + self._errorsModel = StateObject(wrappedValue: errorsModel) + } + var body: some View { VStack(spacing: 12) { Image("primer-icon") @@ -36,13 +39,10 @@ struct CardFormFullPageView: View { } func onAppear() { - self.model.service = service - self.service.errorsDelegate = errorsModel - self.service.modelsDelegate = model } func onSubmit(_ completion: @escaping () -> Void) { - service.makePayment { result in + model.makePayment { result in self.paymentModel = result completion() } @@ -51,7 +51,6 @@ struct CardFormFullPageView: View { } #Preview { - CardFormFullPageView(service: .init(clientToken: ""), - model: .init(), - errorsModel: .init()) + CardFormFullPageView(model: .init(service: .init(clientToken: "")), + errorsModel: .init(service: .init(clientToken: ""))) } diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Pages/SettingsView.swift b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Pages/SettingsView.swift index 4a38a40..33996d1 100644 --- a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Pages/SettingsView.swift +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Pages/SettingsView.swift @@ -9,8 +9,6 @@ import SwiftUI struct SettingsView: View { - let service: PrimerDataService - @StateObject var settingsModel: SettingsModel @State var isFetchingClientToken: Bool = false @@ -74,24 +72,10 @@ struct SettingsView: View { private func onFetchClientToken() { Task { - settingsModel.fetchErrorMessage = nil isFetchingClientToken = true + defer { isFetchingClientToken = false } - defer { - isFetchingClientToken = false - } - - do { - settingsModel.clientToken = try await service.fetchClientToken(from: settingsModel.clientTokenUrl) - } catch { - settingsModel.fetchErrorMessage = """ -Could not fetch a client token from: -POST \(settingsModel.clientTokenUrl) -Make sure the server is running and that your network connection is working. -""" - settingsModel.clientToken = "" - throw error - } + try await settingsModel.updateClientToken() } } @@ -104,6 +88,6 @@ Make sure the server is running and that your network connection is working. #Preview { Form { - SettingsView(service: .init(clientToken: ""), settingsModel: .init()) + SettingsView(settingsModel: .init(service: .init(clientToken: ""))) } } diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Pages/StartPage.swift b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Pages/StartPage.swift index 6f5de6c..8503010 100644 --- a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Pages/StartPage.swift +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Pages/StartPage.swift @@ -15,8 +15,6 @@ enum SDKState { } struct StartPage: View { - - let service: PrimerDataService @StateObject var settingsModel: SettingsModel @@ -30,7 +28,7 @@ struct StartPage: View { Text(LocalizedStringKey("Start.Text.About")).font(.body).fontWeight(.light) } - SettingsView(service: service, settingsModel: settingsModel) + SettingsView(settingsModel: settingsModel) Section { NavigationLink(destination: fullPageView) { @@ -52,7 +50,8 @@ struct StartPage: View { } var fullPageView: some View { - CardFormFullPageView(service: service) + CardFormFullPageView(model: .init(service: settingsModel.service), + errorsModel: .init(service: settingsModel.service)) } var isReadyForPaymentCreation: Bool { @@ -67,26 +66,10 @@ struct StartPage: View { settingsModel.fetchErrorMessage = nil do { - if !settingsModel.isClientTokenValid { - settingsModel.clientToken = try await service.fetchClientToken(from: settingsModel.clientTokenUrl) - } - try await service.start() - service.configureForPayments() + try await settingsModel.setup() sdkState = .ready } catch { sdkState = .error - logger.error(error.localizedDescription) - settingsModel.fetchErrorMessage = """ -There was an error starting the SDK - check your configuration. -""" - settingsModel.clientToken = "" - - switch (error as? PrimerDataService.Error) { - case .failedToFetchClientToken(let error), - .failedToInitialiseSDK(let error): - logger.error(error.localizedDescription) - default: break - } } } } diff --git a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Views/CardDetailsFormView.swift b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Views/CardDetailsFormView.swift index 2436ded..4ab1776 100644 --- a/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Views/CardDetailsFormView.swift +++ b/Co-Badged Cards/SwiftUI/Co-Badged Cards Example/Views/Views/CardDetailsFormView.swift @@ -81,5 +81,6 @@ struct CardDetailsFormView: View { } #Preview { - CardDetailsFormView(model: .init(), errors: .init()) { _ in } + CardDetailsFormView(model: .init(service: .init(clientToken: "")), + errors: .init(service: .init(clientToken: ""))) { _ in } }