@@ -58,7 +58,8 @@ extension StripeCardReaderService: CardReaderService {
5858
5959 // MARK: - CardReaderService conformance. Commands
6060
61- public func start( _ configProvider: CardReaderConfigProvider ) throws {
61+ public func start( _ configProvider: CardReaderConfigProvider ,
62+ discoveryMethod: CardReaderDiscoveryMethod ) throws {
6263 setConfigProvider ( configProvider)
6364
6465 Terminal . setLogListener { message in
@@ -85,13 +86,15 @@ extension StripeCardReaderService: CardReaderService {
8586 }
8687
8788 let config = DiscoveryConfiguration (
88- discoveryMethod: . bluetoothScan ,
89+ discoveryMethod: discoveryMethod . toStripe ( ) ,
8990 simulated: shouldUseSimulatedCardReader
9091 )
9192
9293 // If we're using the simulated reader, we don't want to check for Bluetooth permissions
9394 // as the simulator won't have Bluetooth available.
94- guard shouldUseSimulatedCardReader || CBCentralManager . authorization != . denied else {
95+ // If we're using the built-in reader, bluetooth is not required.
96+ guard shouldSkipBluetoothCheck ( discoveryConfiguration: config) ||
97+ CBCentralManager . authorization != . denied else {
9598 throw CardReaderServiceError . bluetoothDenied
9699 }
97100
@@ -123,6 +126,10 @@ extension StripeCardReaderService: CardReaderService {
123126 } )
124127 }
125128
129+ private func shouldSkipBluetoothCheck( discoveryConfiguration: DiscoveryConfiguration ) -> Bool {
130+ shouldUseSimulatedCardReader || discoveryConfiguration. discoveryMethod == . localMobile
131+ }
132+
126133 public func cancelDiscovery( ) -> Future < Void , Error > {
127134 Future { [ weak self] promise in
128135 /**
@@ -311,9 +318,20 @@ extension StripeCardReaderService: CardReaderService {
311318 } . eraseToAnyPublisher ( )
312319 }
313320
314- return getBluetoothConfiguration ( stripeReader) . flatMap { configuration in
315- self . connect ( stripeReader, configuration: configuration)
316- } . eraseToAnyPublisher ( )
321+ switch stripeReader. deviceType {
322+ case . appleBuiltIn:
323+ return getLocalMobileConfiguration ( stripeReader) . flatMap { configuration in
324+ self . connect ( stripeReader, configuration: configuration)
325+ }
326+ . share ( )
327+ . eraseToAnyPublisher ( )
328+ default :
329+ return getBluetoothConfiguration ( stripeReader) . flatMap { configuration in
330+ self . connect ( stripeReader, configuration: configuration)
331+ }
332+ . share ( )
333+ . eraseToAnyPublisher ( )
334+ }
317335 }
318336
319337 private func getBluetoothConfiguration( _ reader: StripeTerminal . Reader ) -> Future < BluetoothConnectionConfiguration , Error > {
@@ -337,6 +355,27 @@ extension StripeCardReaderService: CardReaderService {
337355 }
338356 }
339357
358+ private func getLocalMobileConfiguration( _ reader: StripeTerminal . Reader ) -> Future < LocalMobileConnectionConfiguration , Error > {
359+ return Future ( ) { [ weak self] promise in
360+ guard let self = self else {
361+ promise ( . failure( CardReaderServiceError . connection ( ) ) )
362+ return
363+ }
364+
365+ // TODO - If we've recently connected to this reader, use the cached locationId from the
366+ // Terminal SDK instead of making this fetch. See #5116 and #5087
367+ self . readerLocationProvider? . fetchDefaultLocationID { result in
368+ switch result {
369+ case . success( let locationId) :
370+ return promise ( . success( LocalMobileConnectionConfiguration ( locationId: locationId) ) )
371+ case . failure( let error) :
372+ let underlyingError = UnderlyingError ( with: error)
373+ return promise ( . failure( CardReaderServiceError . connection ( underlyingError: underlyingError) ) )
374+ }
375+ }
376+ }
377+ }
378+
340379 public func connect( _ reader: StripeTerminal . Reader , configuration: BluetoothConnectionConfiguration ) -> Future < CardReader , Error > {
341380 // Keep a copy of the battery level in case the connection fails due to low battery
342381 // If that happens, the reader object won't be accessible anymore, and we want to show
@@ -376,6 +415,40 @@ extension StripeCardReaderService: CardReaderService {
376415 }
377416 }
378417
418+ public func connect( _ reader: StripeTerminal . Reader , configuration: LocalMobileConnectionConfiguration ) -> Future < CardReader , Error > {
419+ return Future { [ weak self] promise in
420+ guard let self = self else {
421+ promise ( . failure( CardReaderServiceError . connection ( ) ) )
422+ return
423+ }
424+
425+ Terminal . shared. connectLocalMobileReader ( reader, delegate: self , connectionConfig: configuration) { [ weak self] ( reader, error) in
426+ guard let self = self else {
427+ promise ( . failure( CardReaderServiceError . connection ( ) ) )
428+ return
429+ }
430+ // Clear cached readers, as per Stripe's documentation.
431+ self . discoveredStripeReadersCache. clear ( )
432+
433+ if let error = error {
434+ let underlyingError = UnderlyingError ( with: error)
435+ // Starting with StripeTerminal 2.0, required software updates happen transparently on connection
436+ // Any error related to that will be reported here, but we don't want to treat it as a connection error
437+ let serviceError : CardReaderServiceError = underlyingError. isSoftwareUpdateError ?
438+ . softwareUpdate( underlyingError: underlyingError, batteryLevel: nil ) :
439+ . connection( underlyingError: underlyingError)
440+ promise ( . failure( serviceError) )
441+ }
442+
443+ if let reader = reader {
444+ self . connectedReadersSubject. send ( [ CardReader ( reader: reader) ] )
445+ self . switchStatusToIdle ( )
446+ promise ( . success( CardReader ( reader: reader) ) )
447+ }
448+ }
449+ }
450+ }
451+
379452 public func installUpdate( ) -> Void {
380453 Terminal . shared. installAvailableUpdate ( )
381454 }
@@ -443,6 +516,7 @@ private extension StripeCardReaderService {
443516
444517 if underlyingError == . commandCancelled {
445518 DDLogWarn ( " 💳 Warning: collect payment error cancelled. We actively ignore this error \( error) " )
519+ promise ( . failure( CardReaderServiceError . paymentCancellation ( underlyingError: underlyingError) ) )
446520 }
447521
448522 }
@@ -696,6 +770,46 @@ extension StripeCardReaderService: BluetoothReaderDelegate {
696770 }
697771}
698772
773+ extension StripeCardReaderService : LocalMobileReaderDelegate {
774+ public func localMobileReader( _ reader: Reader , didRequestReaderInput inputOptions: ReaderInputOptions = [ ] ) {
775+ sendReaderEvent ( CardReaderEvent . make ( stripeReaderInputOptions: inputOptions) )
776+ }
777+
778+ public func localMobileReader( _ reader: Reader , didRequestReaderDisplayMessage displayMessage: ReaderDisplayMessage ) {
779+ sendReaderEvent ( CardReaderEvent . make ( displayMessage: displayMessage) )
780+ }
781+
782+
783+ // TODO: use a specific `deviceSetup` in these three functions instead of reusing the softwareUpdateSubject
784+ // https://github.com/woocommerce/woocommerce-ios/issues/8088
785+ public func localMobileReader( _ reader: Reader , didStartInstallingUpdate update: ReaderSoftwareUpdate , cancelable: Cancelable ? ) {
786+ softwareUpdateSubject. send ( . started( cancelable: cancelable. map ( StripeCancelable . init ( cancelable: ) ) ) )
787+ }
788+
789+ public func localMobileReader( _ reader: Reader , didReportReaderSoftwareUpdateProgress progress: Float ) {
790+ softwareUpdateSubject. send ( . installing( progress: progress) )
791+ }
792+
793+ public func localMobileReader( _ reader: Reader , didFinishInstallingUpdate update: ReaderSoftwareUpdate ? , error: Error ? ) {
794+ if let error = error {
795+ softwareUpdateSubject. send ( . failed(
796+ error: CardReaderServiceError . softwareUpdate ( underlyingError: UnderlyingError ( with: error) ,
797+ batteryLevel: reader. batteryLevel? . doubleValue) )
798+ )
799+ if let requiredDate = update? . requiredAt,
800+ requiredDate > Date ( ) {
801+ softwareUpdateSubject. send ( . available)
802+ } else {
803+ softwareUpdateSubject. send ( . none)
804+ }
805+ } else {
806+ softwareUpdateSubject. send ( . completed)
807+ connectedReadersSubject. send ( [ CardReader ( reader: reader) ] )
808+ softwareUpdateSubject. send ( . none)
809+ }
810+ }
811+ }
812+
699813// MARK: - Terminal delegate
700814extension StripeCardReaderService : TerminalDelegate {
701815 public func terminal( _ terminal: Terminal , didReportUnexpectedReaderDisconnect reader: Reader ) {
0 commit comments