Skip to content

Commit

Permalink
Update HARDWARE.md
Browse files Browse the repository at this point in the history
  • Loading branch information
ctarda committed Mar 24, 2021
1 parent 4a402d2 commit 7f35e2c
Showing 1 changed file with 50 additions and 2 deletions.
52 changes: 50 additions & 2 deletions docs/HARDWARE.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,71 @@
# Hardware

This project provides abstractions that allow us to integrate with external hardware in a way that those integrations fit within the high level architecture of the WooCommerce iOS app.

In the context of the three-tier architecture of the WooCommerce iOS app, Hardware fulfills the role of the data transfer layer in between the application business logic ([Yosemite](YOSEMITE.md)) and external hardware (like card readers or printers). From a functional point of view, Hardware is similar in behaviour to [Networking](NETWORKING.md), the only difference is that Networking transfer information back and forth between the app and remote services, while Hardware tranfers info back and forth between the app and external Hardware.

## High level class diagram

## Public interfaces

* CardReaderService
* CardReaderService: Abstracts the integration with a Card Reader, it is the public API that provides access to a Card Reader and its associated operations.
* CardReaderConfigProvider: Abstraction provided by Hardware so that clients of the library can model a way to provide a connection token.

## Model objects

* CardReader: Models a Card Reader. This is the public struct that clients of Hardware are expected to consume. CardReader is meant to be inmutable.
* CardReaderEvent: The possible events emitted by a connected reader.
* CardReaderServiceDiscoveryStatus: Models the discovery status of a Card Reader Service.
* CardReaderServiceStatus: Models the status of a Card Reader Service.
* CardReaderType: Indicates if a reader is meant to be used handheld or as a countertop device.
* PaymentIntentParameters: Encapsulates the parameters needed to create a PaymentIntent, for example amount, currency, and readable descriptions for receipts
* PaymentStatus. The possible payment statuses
* CardReaderServiceError. Models errors thrown by the CardReaderService. See Error Handling for more info.

## Integration with Stripe Terminal

The initial release of Hardware provides an integration with the [Stripe Terminal SDK](https://github.com/stripe/stripe-terminal-ios)
The initial release of Hardware provides an integration with the [Stripe Terminal SDK](https://github.com/stripe/stripe-terminal-ios). That integration is encapsulated in `StripeCardReaderService`, and implementation of `CardReaderService` that is internal (in terms of Swift's access modifiers) to Hardware.

There are some interesting quirks in our implementation of the integration with the Stripe SDK that are worth mentioning.

The Stripe Terminal SDK exposes access to external card readers through a singleton, called [SCPTerminal](https://stripe.dev/stripe-terminal-ios/docs/Classes/SCPTerminal.html). The fact that is a singleton makes sense (there is one gateweay with external readers after all), but it makes our attempts to encapsulate the integration behind a clear boundary a bit more challenging. The reasons for those challenges will hopefully be clarified soon.

### Initialization

If there is one quirk, if there is one spot where we had to jump through a few hoops in order to make the integration with the Stripe Terminal SDK fit with the rest of the app, this is it.

Initializing the Stripe Terminal SDK requires providing it with access to an endpoint in our backend, [so that it can fetch a connection token](https://stripe.com/docs/terminal/sdk/ios#connection-token-client-side).

The way that is meant to happen is by setting a property of type `ConnectionTokenProvider` in SCPTerminal. ConnectionTokenProvider is a protocol declared in the Stripe Terminal SDK.

To prevent that protocol from leaking up outside of Hardware, Hardware exposes a pubic protocol, `CardReaderConfigProvider`, that we expect clients of Hardware to implement in a way that they can provide the connection token that is needed.

This implementation of CardReaderConfigProvider will have to be passed as a parameter to the `start()` method declared in CardReaderService. Internally, our implementation of CardReaderService specific to the integration with the Stripe Terminal SDK (`StripeCardReaderService`) will adapt the parameter to the required protocol and pass it to SCPTerminal

### Discovering readers

One thing to have in mind when initiating the reader discovery process is that, if there is another reader discovery process that has been already started, the Stripe Terminal SDK will throw an assertion.

That is why we provide a `cancelDiscovery()` method.

At this point, reader discovery commences as soon as initialization happens. In other words, calling `start()` triggers a reader discovery. We might need to revisit this decission later on.

The Stripe Terminal SDK notifies implementations of the [SCPDiscoveryDelegate](https://stripe.dev/stripe-terminal-ios/docs/Protocols/SCPDiscoveryDelegate.html) protocol of changes in the status of the process: every time a new reader is discovered, or when the process is considered complete.

To avoid this detail from leaking up as well, we implement SCPDiscoveryDelegate in StripeCardReaderService, and publish the new readers through `connectedReaders` and the status of the discovery process via `discoveryStatus`, two Combine publishers.

Once the reader discovery process starts yielding results, those results are propagated to the UI. Once again, we want to avoid leaking implementation details related to the Stripe Terminal SDK, so we expose domain model objects that are declared on Hardware. In this case, that is `CardReader`

Discovered readers are modelled, within the boundary of the integration with the Stripe Terminal SDK, as `StripeTerminal.CardReader`. These objects are not available for initialization outside of the Stripe Terminal SDK, and are [not meant to be cached](https://stripe.dev/stripe-terminal-ios/docs/Protocols/SCPDiscoveryDelegate.html#/c:objc(pl)SCPDiscoveryDelegate(im)terminal:didUpdateDiscoveredReaders:) between discovery sessions.

What our integration does is cache temporarlity those discovered readers, cleared that cache between discovery sessions, and map them to the instances of `CardReader` that we propagate up.

### Pairing (connecting) with a reader

Once the reader discovery process starts yielding results, those results are propagated to the UI. That happens in the form of instances of `CardReader`, a public model object declared in Hardware, that provides the information needed to render a card reader on the UI (e.g. name, identifier, battery level...)

When the user selects a reader to connect to, we pass that public model object back to the `CardReaderService`, via the `connect()` method. At that point, for the integration with the Stripe Terminal SDK, we will look into the internal cache of discovered readers, find the one that matches the serial number of the parameter provided, and attempt a connection with the StripeTerminal.CardReader found.

### Processing a payment

Collecting a payment is a three step process that needs to be performed in this specific sequence:
Expand Down

0 comments on commit 7f35e2c

Please sign in to comment.