diff --git a/Hardware/Hardware/CardReader/StripeCardReader/StripeCardReaderService.swift b/Hardware/Hardware/CardReader/StripeCardReader/StripeCardReaderService.swift index 28fa79d0cb0..0387ce26ded 100644 --- a/Hardware/Hardware/CardReader/StripeCardReader/StripeCardReaderService.swift +++ b/Hardware/Hardware/CardReader/StripeCardReader/StripeCardReaderService.swift @@ -174,7 +174,8 @@ extension StripeCardReaderService: CardReaderService { self?.internalError(error) discoveryLock.unlock() - promise(.failure(error)) + let underlyingError = UnderlyingError(with: error) + promise(.failure(CardReaderServiceError.discovery(underlyingError: underlyingError))) } } } @@ -838,7 +839,10 @@ private extension StripeCardReaderService { func resetDiscoveredReadersSubject(error: Error? = nil) { if let error = error { - discoveredReadersSubject.send(completion: .failure(error)) + let underlyingError = UnderlyingError(with: error) + discoveredReadersSubject.send(completion: + .failure(CardReaderServiceError.discovery(underlyingError: underlyingError)) + ) } discoveredReadersSubject.send(completion: .finished) discoveredReadersSubject = CurrentValueSubject<[CardReader], Error>([]) diff --git a/Hardware/Hardware/CardReader/StripeCardReader/UnderlyingError+Stripe.swift b/Hardware/Hardware/CardReader/StripeCardReader/UnderlyingError+Stripe.swift index 63decf10cbf..915699280d0 100644 --- a/Hardware/Hardware/CardReader/StripeCardReader/UnderlyingError+Stripe.swift +++ b/Hardware/Hardware/CardReader/StripeCardReader/UnderlyingError+Stripe.swift @@ -89,6 +89,36 @@ extension UnderlyingError { self = .readerSessionExpired case ErrorCode.Code.stripeAPIError.rawValue: self = .processorAPIError + case ErrorCode.Code.passcodeNotEnabled.rawValue: + self = .passcodeNotEnabled + case ErrorCode.Code.appleBuiltInReaderTOSAcceptanceRequiresiCloudSignIn.rawValue: + self = .appleBuiltInReaderTOSAcceptanceRequiresiCloudSignIn + case ErrorCode.Code.nfcDisabled.rawValue: + self = .nfcDisabled + case ErrorCode.Code.appleBuiltInReaderFailedToPrepare.rawValue: + self = .appleBuiltInReaderFailedToPrepare + case ErrorCode.Code.appleBuiltInReaderTOSAcceptanceCanceled.rawValue: + self = .appleBuiltInReaderTOSAcceptanceCanceled + case ErrorCode.Code.appleBuiltInReaderTOSNotYetAccepted.rawValue: + self = .appleBuiltInReaderTOSNotYetAccepted + case ErrorCode.Code.appleBuiltInReaderTOSAcceptanceFailed.rawValue: + self = .appleBuiltInReaderTOSAcceptanceFailed + case ErrorCode.Code.appleBuiltInReaderMerchantBlocked.rawValue: + self = .appleBuiltInReaderMerchantBlocked + case ErrorCode.Code.appleBuiltInReaderInvalidMerchant.rawValue: + self = .appleBuiltInReaderInvalidMerchant + case ErrorCode.Code.appleBuiltInReaderDeviceBanned.rawValue: + self = .appleBuiltInReaderDeviceBanned + case ErrorCode.Code.unsupportedMobileDeviceConfiguration.rawValue: + self = .unsupportedMobileDeviceConfiguration + case ErrorCode.Code.readerNotAccessibleInBackground.rawValue: + self = .readerNotAccessibleInBackground + case ErrorCode.Code.commandNotAllowedDuringCall.rawValue: + self = .commandNotAllowedDuringCall + case ErrorCode.Code.invalidAmount.rawValue: + self = .invalidAmount + case ErrorCode.Code.invalidCurrency.rawValue: + self = .invalidCurrency default: return nil } diff --git a/Hardware/Hardware/CardReader/UnderlyingError.swift b/Hardware/Hardware/CardReader/UnderlyingError.swift index 2c7bdbfc2bb..902f38c6183 100644 --- a/Hardware/Hardware/CardReader/UnderlyingError.swift +++ b/Hardware/Hardware/CardReader/UnderlyingError.swift @@ -140,6 +140,72 @@ public enum UnderlyingError: Error, Equatable { /// There was no refund in progress to cancel case noRefundInProgress + + // MARK: - Built-in reader related errors + + /// The device must have a passcode in order to use the built-in reader + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorPasscodeNotEnabled + case passcodeNotEnabled + + /// The phone must have a signed-in iCloud account in order to accept the TOS for the built in reader. + /// The signed-in account does not need to be the one used to connect the reader. + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorAppleBuiltInReaderTOSAcceptanceRequiresiCloudSignIn + case appleBuiltInReaderTOSAcceptanceRequiresiCloudSignIn + + /// NFC is disabled on the device. This could be a permissions issue, in particular due to a device management profile. + /// It's unlikely that the user can directly correct this issue + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorNFCDisabled + case nfcDisabled + + /// Preparing the built-in reader failed. This is a retriable error + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorAppleBuiltInReaderFailedToPrepare + case appleBuiltInReaderFailedToPrepare + + /// The user cancelled the built-in reader Terms of Service acceptance + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorAppleBuiltInReaderTOSAcceptanceCanceled + case appleBuiltInReaderTOSAcceptanceCanceled + + /// The built-in reader Terms of Service have not been accepted. This error is retriable + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorAppleBuiltInReaderTOSNotYetAccepted + case appleBuiltInReaderTOSNotYetAccepted + + /// The built-in reader Terms of Service could not be accepted. This may indicate an issue with the Apple ID used. + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorAppleBuiltInReaderTOSAcceptanceFailed + case appleBuiltInReaderTOSAcceptanceFailed + + /// This (Stripe) merchant account cannot be used with the built-in reader as it has been blocked + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorAppleBuiltInReaderMerchantBlocked + case appleBuiltInReaderMerchantBlocked + + /// The merchant account is invalid and cannot be used with the built-in reader + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorAppleBuiltInReaderInvalidMerchant + case appleBuiltInReaderInvalidMerchant + + /// The built-in reader on this device cannot be used because it has been banned + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorAppleBuiltInReaderDeviceBanned + case appleBuiltInReaderDeviceBanned + + /// The device does not meet the minimum requirements for using the built-in reader + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorUnsupportedMobileDeviceConfiguration + case unsupportedMobileDeviceConfiguration + + /// The built-in reader cannot be used while the app is in the background + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorReaderNotAccessibleInBackground + case readerNotAccessibleInBackground + + /// The built-in reader cannot be used during a phone call + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorCommandNotAllowedDuringCall + case commandNotAllowedDuringCall + + /// The amount charged was not supported by the reader. + /// (This may be a different amount than the minimum for a payment with Stripe. There is a maximum too.) + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorInvalidAmount + case invalidAmount + + /// The currency used was not supported by the reader. + /// The reader may support a different set of currencies than WCPay or Stripe. + /// https://stripe.dev/stripe-terminal-ios/docs/Enums/SCPError.html#/c:@E@SCPError@SCPErrorInvalidCurrency + case invalidCurrency } extension UnderlyingError { @@ -328,6 +394,61 @@ extension UnderlyingError: LocalizedError { return NSLocalizedString("Sorry, this refund could not be canceled", comment: "Error message shown when a refund could not be canceled (likely because " + "it had already completed)") + + // MARK: - Built-in reader errors + case .passcodeNotEnabled: + return NSLocalizedString("Your device needs a lock screen passcode set to use the built-in card reader", + comment: "Error message shown when the built-in reader cannot be used because " + + "the device does not have a passcode set.") + case .appleBuiltInReaderTOSAcceptanceRequiresiCloudSignIn: + return NSLocalizedString("Please sign in to iCloud on this device, so you can use the built-in card reader", + comment: "Error message shown when the built-in reader cannot be used because " + + "the device is not signed in to iCloud.") + case .nfcDisabled: + return NSLocalizedString("The app could not enable the card reader, because the NFC chip is disabled. " + + "Please contact support for more details.", + comment: "Error message shown when the built-in reader cannot be used because " + + "the device's NFC chipset has been disabled by a device management policy.") + case .appleBuiltInReaderFailedToPrepare, .readerNotAccessibleInBackground: + return NSLocalizedString("There was an issue preparing the built in reader for payment – please try again.", + comment: "Error message shown when the built-in reader cannot be used because " + + "there was some issue with the connection. Retryable.") + case .appleBuiltInReaderTOSAcceptanceCanceled, .appleBuiltInReaderTOSNotYetAccepted: + return NSLocalizedString("Please try again, and accept Apple's Terms of Service, so you can use the " + + "built-in card reader", + comment: "Error message shown when the built-in reader cannot be used because " + + "the merchant cancelled or did not complete the Terms of Service acceptance flow") + case .appleBuiltInReaderTOSAcceptanceFailed: + return NSLocalizedString("Please check your Apple ID is valid, and then try again. A valid Apple ID is " + + "required to accept Apple's Terms of Service", + comment: "Error message shown when the built-in reader cannot be used because " + + "the Terms of Service acceptance flow failed, possibly due to issues with " + + "the Apple ID") + case .appleBuiltInReaderMerchantBlocked, .appleBuiltInReaderInvalidMerchant, .appleBuiltInReaderDeviceBanned: + return NSLocalizedString("Please contact support – there was an issue connecting to the built-in reader", + comment: "Error message shown when the built-in reader cannot be used because " + + "there is an issue with the merchant account or device") + case .unsupportedMobileDeviceConfiguration: + return NSLocalizedString("Please check that your phone meets these requirements: " + + "iPhone XS or newer running iOS 16.0 or above. Contact support if this error " + + "shows on a supported device.", + comment: "Error message shown when the built-in reader cannot be used because " + + "the device does not meet minimum requirements.") + case .commandNotAllowedDuringCall: + return NSLocalizedString("The built-in reader cannot be used during a phone call. Please try again after " + + "you finish your call", + comment: "Error message shown when the built-in reader cannot be used because " + + "there is a call in progress") + case .invalidAmount: + return NSLocalizedString("The amount is not supported by the built in reader – please try a hardware " + + "reader or another payment method.", + comment: "Error message shown when the built-in reader cannot be used because " + + "the amount for payment is not supported by the built in reader.") + case .invalidCurrency: + return NSLocalizedString("The currency is not supported by the built in reader – please try a hardware " + + "reader or another payment method.", + comment: "Error message shown when the built-in reader cannot be used because " + + "the currency for payment is not supported by the built in reader.") } } } diff --git a/Hardware/HardwareTests/ErrorCodesTests.swift b/Hardware/HardwareTests/ErrorCodesTests.swift index 4a443582eef..b97d14ea6ba 100644 --- a/Hardware/HardwareTests/ErrorCodesTests.swift +++ b/Hardware/HardwareTests/ErrorCodesTests.swift @@ -162,6 +162,66 @@ final class CardReaderServiceErrorTests: XCTestCase { XCTAssertEqual(.processorAPIError, domainError(stripeCode: 9020)) } + func test_stripe_passcode_not_enabled_maps_to_expected_error() { + XCTAssertEqual(.passcodeNotEnabled, domainError(stripeCode: 2920)) + } + + func test_stripe_TOS_requires_iCloud_signin_maps_to_expected_error() { + XCTAssertEqual(.appleBuiltInReaderTOSAcceptanceRequiresiCloudSignIn, domainError(stripeCode: 2960)) + } + + func test_stripe_nfc_disabled_maps_to_expected_error() { + XCTAssertEqual(.nfcDisabled, domainError(stripeCode: 3100)) + } + + func test_stripe_built_in_reader_failed_to_prepare_maps_to_expected_error() { + XCTAssertEqual(.appleBuiltInReaderFailedToPrepare, domainError(stripeCode: 3910)) + } + + func test_stripe_TOS_acceptance_cancelled_maps_to_expected_error() { + XCTAssertEqual(.appleBuiltInReaderTOSAcceptanceCanceled, domainError(stripeCode: 2970)) + } + + func test_stripe_TOS_not_yet_accepted_maps_to_expected_error() { + XCTAssertEqual(.appleBuiltInReaderTOSNotYetAccepted, domainError(stripeCode: 3930)) + } + + func test_stripe_TOS_acceptance_failed_maps_to_expected_error() { + XCTAssertEqual(.appleBuiltInReaderTOSAcceptanceFailed, domainError(stripeCode: 3940)) + } + + func test_stripe_merchant_blocked_maps_to_expected_error() { + XCTAssertEqual(.appleBuiltInReaderMerchantBlocked, domainError(stripeCode: 3950)) + } + + func test_stripe_invalid_merchant_maps_to_expected_error() { + XCTAssertEqual(.appleBuiltInReaderInvalidMerchant, domainError(stripeCode: 3960)) + } + + func test_stripe_device_banned_maps_to_expected_error() { + XCTAssertEqual(.appleBuiltInReaderDeviceBanned, domainError(stripeCode: 3920)) + } + + func test_stripe_unsupported_mobile_device_maps_to_expected_error() { + XCTAssertEqual(.unsupportedMobileDeviceConfiguration, domainError(stripeCode: 2910)) + } + + func test_stripe_not_accessible_in_background_maps_to_expected_error() { + XCTAssertEqual(.readerNotAccessibleInBackground, domainError(stripeCode: 3900)) + } + + func test_stripe_command_not_allowed_during_call_maps_to_expected_error() { + XCTAssertEqual(.commandNotAllowedDuringCall, domainError(stripeCode: 2930)) + } + + func test_stripe_invalid_amount_maps_to_expected_error() { + XCTAssertEqual(.invalidAmount, domainError(stripeCode: 2940)) + } + + func test_stripe_invalid_currency_maps_to_expected_error() { + XCTAssertEqual(.invalidCurrency, domainError(stripeCode: 2950)) + } + func test_stripe_catch_all_error() { // Any error code not mapped to an specific error will be // mapped to `internalServiceError` diff --git a/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalBuiltInConfigurationProgress.swift b/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalBuiltInConfigurationProgress.swift new file mode 100644 index 00000000000..43d39864a96 --- /dev/null +++ b/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalBuiltInConfigurationProgress.swift @@ -0,0 +1,81 @@ +import UIKit + +/// Modal presented when a firmware update is being installed +/// +final class CardPresentModalBuiltInConfigurationProgress: CardPresentPaymentsModalViewModel, CardPresentModalProgressDisplaying { + /// Called when cancel button is tapped + private let cancelAction: (() -> Void)? + + let textMode: PaymentsModalTextMode = .fullInfo + let actionsMode: PaymentsModalActionsMode + + var topSubtitle: String? = nil + + var progress: Float + + let primaryButtonTitle: String? = nil + + let secondaryButtonTitle: String? = Localization.cancel + + let auxiliaryButtonTitle: String? = nil + + var titleComplete: String + + var titleInProgress: String + + var messageComplete: String? + + var messageInProgress: String? + + var accessibilityLabel: String? { + Localization.title + } + + init(progress: Float, cancel: (() -> Void)?) { + self.progress = progress + self.cancelAction = cancel + + titleComplete = Localization.titleComplete + titleInProgress = Localization.title + messageComplete = Localization.messageComplete + messageInProgress = Localization.message + actionsMode = cancel != nil ? .secondaryOnlyAction : .none + } + + func didTapPrimaryButton(in viewController: UIViewController?) {} + + func didTapSecondaryButton(in viewController: UIViewController?) { + cancelAction?() + } + + func didTapAuxiliaryButton(in viewController: UIViewController?) {} +} + +private extension CardPresentModalBuiltInConfigurationProgress { + enum Localization { + static let title = NSLocalizedString( + "Configuring iPhone", + comment: "Dialog title that displays when iPhone configuration is being updated for use as a card reader" + ) + + static let titleComplete = NSLocalizedString( + "Configuration updated", + comment: "Dialog title that displays when a configuration update just finished installing" + ) + + static let message = NSLocalizedString( + "Your iPhone needs to be configured to collect payments.", + comment: "Label that displays when a configuration update is happening" + ) + + static let messageComplete = NSLocalizedString( + "Your phone will be ready to collect payments in a moment...", + comment: "Dialog message that displays when a configuration update just finished installing" + ) + + static let cancel = NSLocalizedString( + "Cancel", + comment: "Label for a cancel button" + ) + } +} diff --git a/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalProgressDisplaying.swift b/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalProgressDisplaying.swift new file mode 100644 index 00000000000..67b85f7e1b2 --- /dev/null +++ b/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalProgressDisplaying.swift @@ -0,0 +1,39 @@ +import UIKit + +protocol CardPresentModalProgressDisplaying: CardPresentPaymentsModalViewModel { + var progress: Float { get } + var isComplete: Bool { get } + var titleComplete: String { get } + var titleInProgress: String { get } + var messageComplete: String? { get } + var messageInProgress: String? { get } +} + +extension CardPresentModalProgressDisplaying { + var image: UIImage { + .softwareUpdateProgress(progress: CGFloat(progress)) + } + + var isComplete: Bool { + progress == 1 + } + + var topTitle: String { + isComplete ? titleComplete : titleInProgress + } + + var bottomTitle: String? { + String(format: CardPresentModalProgressDisplayingLocalization.percentComplete, 100 * progress) + } + + var bottomSubtitle: String? { + isComplete ? messageComplete : messageInProgress + } +} + +fileprivate enum CardPresentModalProgressDisplayingLocalization { + static let percentComplete = NSLocalizedString( + "%.0f%% complete", + comment: "Label that describes the completed progress of an update being installed (e.g. 15% complete). Keep the %.0f%% exactly as is" + ) +} diff --git a/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalUpdateProgress.swift b/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalUpdateProgress.swift index cfadfa1720c..9a9b30cd58e 100644 --- a/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalUpdateProgress.swift +++ b/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalUpdateProgress.swift @@ -2,18 +2,16 @@ import UIKit /// Modal presented when a firmware update is being installed /// -final class CardPresentModalUpdateProgress: CardPresentPaymentsModalViewModel { +final class CardPresentModalUpdateProgress: CardPresentPaymentsModalViewModel, CardPresentModalProgressDisplaying { /// Called when cancel button is tapped private let cancelAction: (() -> Void)? let textMode: PaymentsModalTextMode = .fullInfo let actionsMode: PaymentsModalActionsMode - var topTitle: String - var topSubtitle: String? = nil - let image: UIImage + var progress: Float let primaryButtonTitle: String? = nil @@ -21,25 +19,28 @@ final class CardPresentModalUpdateProgress: CardPresentPaymentsModalViewModel { let auxiliaryButtonTitle: String? = nil - let bottomTitle: String? + var titleComplete: String + + var titleInProgress: String - var bottomSubtitle: String? = nil + var messageComplete: String? + + var messageInProgress: String? var accessibilityLabel: String? { Localization.title } init(requiredUpdate: Bool, progress: Float, cancel: (() -> Void)?) { + self.progress = progress self.cancelAction = cancel + actionsMode = cancel != nil ? .secondaryOnlyAction : .none + titleComplete = Localization.titleComplete + titleInProgress = Localization.title - let isComplete = progress == 1 - topTitle = isComplete ? Localization.titleComplete : Localization.title - image = .softwareUpdateProgress(progress: CGFloat(progress)) - bottomTitle = String(format: Localization.percentComplete, 100 * progress) if !isComplete { - bottomSubtitle = requiredUpdate ? Localization.messageRequired : Localization.messageOptional + messageInProgress = requiredUpdate ? Localization.messageRequired : Localization.messageOptional } - actionsMode = cancel != nil ? .secondaryOnlyAction : .none } func didTapPrimaryButton(in viewController: UIViewController?) {} @@ -63,12 +64,6 @@ private extension CardPresentModalUpdateProgress { comment: "Dialog title that displays when a software update just finished installing" ) - - static let percentComplete = NSLocalizedString( - "%.0f%% complete", - comment: "Label that describes the completed progress of a software update being installed (e.g. 15% complete). Keep the %.0f%% exactly as is" - ) - static let messageRequired = NSLocalizedString( "Your card reader software needs to be updated to collect payments. Cancelling will block your reader connection.", comment: "Label that displays when a mandatory software update is happening" diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/BuiltInReaderConnectionAlertsProvider.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/BuiltInReaderConnectionAlertsProvider.swift index cb9cbae952f..36674afb178 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/BuiltInReaderConnectionAlertsProvider.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/BuiltInReaderConnectionAlertsProvider.swift @@ -39,8 +39,10 @@ struct BuiltInReaderConnectionAlertsProvider: CardReaderConnectionAlertsProvidin return CardPresentModalUpdateFailedNonRetryable(close: close) } } - func updateProgress(requiredUpdate: Bool, progress: Float, + + func updateProgress(requiredUpdate: Bool, + progress: Float, cancel: (() -> Void)?) -> CardPresentPaymentsModalViewModel { - CardPresentModalUpdateProgress(requiredUpdate: requiredUpdate, progress: progress, cancel: cancel) + CardPresentModalBuiltInConfigurationProgress(progress: progress, cancel: cancel) } } diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index bdbad3d32cf..e7d483a720a 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -500,6 +500,8 @@ 03E471C4293A1F8D001A58AD /* BuiltInReaderConnectionAlertsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E471C3293A1F8D001A58AD /* BuiltInReaderConnectionAlertsProvider.swift */; }; 03E471C6293A2E95001A58AD /* CardPresentModalBuiltInReaderCheckingDeviceSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E471C5293A2E95001A58AD /* CardPresentModalBuiltInReaderCheckingDeviceSupport.swift */; }; 03E471C8293A3076001A58AD /* CardPresentModalBuiltInConnectingToReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E471C7293A3075001A58AD /* CardPresentModalBuiltInConnectingToReader.swift */; }; + 03E471CA293E0A30001A58AD /* CardPresentModalBuiltInConfigurationProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E471C9293E0A2F001A58AD /* CardPresentModalBuiltInConfigurationProgress.swift */; }; + 03E471CC293E0FB8001A58AD /* CardPresentModalProgressDisplaying.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E471CB293E0FB8001A58AD /* CardPresentModalProgressDisplaying.swift */; }; 03EF24FA28BF5D21006A033E /* InPersonPaymentsCashOnDeliveryToggleRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EF24F928BF5D21006A033E /* InPersonPaymentsCashOnDeliveryToggleRowViewModel.swift */; }; 03EF24FC28BF996F006A033E /* InPersonPaymentsCashOnDeliveryPaymentGatewayHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EF24FB28BF996F006A033E /* InPersonPaymentsCashOnDeliveryPaymentGatewayHelpers.swift */; }; 03EF24FE28C0B356006A033E /* CardPresentPaymentsPlugin+CashOnDelivery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EF24FD28C0B356006A033E /* CardPresentPaymentsPlugin+CashOnDelivery.swift */; }; @@ -2511,6 +2513,8 @@ 03E471C3293A1F8D001A58AD /* BuiltInReaderConnectionAlertsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuiltInReaderConnectionAlertsProvider.swift; sourceTree = ""; }; 03E471C5293A2E95001A58AD /* CardPresentModalBuiltInReaderCheckingDeviceSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardPresentModalBuiltInReaderCheckingDeviceSupport.swift; sourceTree = ""; }; 03E471C7293A3075001A58AD /* CardPresentModalBuiltInConnectingToReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardPresentModalBuiltInConnectingToReader.swift; sourceTree = ""; }; + 03E471C9293E0A2F001A58AD /* CardPresentModalBuiltInConfigurationProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardPresentModalBuiltInConfigurationProgress.swift; sourceTree = ""; }; + 03E471CB293E0FB8001A58AD /* CardPresentModalProgressDisplaying.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalProgressDisplaying.swift; sourceTree = ""; }; 03EF24F928BF5D21006A033E /* InPersonPaymentsCashOnDeliveryToggleRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsCashOnDeliveryToggleRowViewModel.swift; sourceTree = ""; }; 03EF24FB28BF996F006A033E /* InPersonPaymentsCashOnDeliveryPaymentGatewayHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsCashOnDeliveryPaymentGatewayHelpers.swift; sourceTree = ""; }; 03EF24FD28C0B356006A033E /* CardPresentPaymentsPlugin+CashOnDelivery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardPresentPaymentsPlugin+CashOnDelivery.swift"; sourceTree = ""; }; @@ -8555,6 +8559,8 @@ children = ( D8815AE626383FD600EDAD62 /* CardPresentPaymentsModalViewModel.swift */, 03E471C5293A2E95001A58AD /* CardPresentModalBuiltInReaderCheckingDeviceSupport.swift */, + 03E471C9293E0A2F001A58AD /* CardPresentModalBuiltInConfigurationProgress.swift */, + 03E471CB293E0FB8001A58AD /* CardPresentModalProgressDisplaying.swift */, 03E471C7293A3075001A58AD /* CardPresentModalBuiltInConnectingToReader.swift */, 311237ED2714DA240033C44E /* CardPresentModalDisplayMessage.swift */, D8815B0026385E3F00EDAD62 /* CardPresentModalTapCard.swift */, @@ -9992,6 +9998,7 @@ E138D4FC269EEAFE006EA5C6 /* InPersonPaymentsViewModel.swift in Sources */, 039D948F276113490044EF38 /* UIView+SuperviewConstraints.swift in Sources */, D8736B5322EF4F5900A14A29 /* NotificationsBadgeController.swift in Sources */, + 03E471CC293E0FB8001A58AD /* CardPresentModalProgressDisplaying.swift in Sources */, B541B220218A007C008FE7C1 /* NSMutableParagraphStyle+Helpers.swift in Sources */, 45AE582C230D9D35001901E3 /* OrderNoteHeaderTableViewCell.swift in Sources */, 6832C7CA26DA5C4500BA4088 /* LabeledTextViewTableViewCell.swift in Sources */, @@ -10012,6 +10019,7 @@ E1325EFB28FD544E00EC9B2A /* InAppPurchasesDebugView.swift in Sources */, 74460D4022289B7600D7316A /* Coordinator.swift in Sources */, B57C743D20F5493300EEFC87 /* AccountHeaderView.swift in Sources */, + 03E471CA293E0A30001A58AD /* CardPresentModalBuiltInConfigurationProgress.swift in Sources */, 31AD0B1126E9575F000B6391 /* CardPresentModalConnectingFailed.swift in Sources */, 576EA39425264C9B00AFC0B3 /* RefundConfirmationViewModel.swift in Sources */, DEC51B00276AEE91009F3DF4 /* SystemStatusReportViewModel.swift in Sources */,