This repository has been archived by the owner on Jan 4, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 355
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #695 from artsy/cardflight-revert
Fixes CardFlight integration
- Loading branch information
Showing
15 changed files
with
321 additions
and
355 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
version: 2 | ||
jobs: | ||
build: | ||
parallelism: 1 | ||
environment: | ||
CIRCLE_ARTIFACTS: /tmp/circleci-artifacts | ||
CIRCLE_TEST_REPORTS: /tmp/circleci-test-results | ||
FASTLANE_SKIP_UPDATE_CHECK: true | ||
UPLOAD_IOS_SNAPSHOT_BUCKET_NAME: eidolon-ci | ||
macos: | ||
xcode: 9.2.0 | ||
|
||
steps: | ||
- checkout | ||
- run: mkdir -p $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS derived_data | ||
- run: | ||
command: xcodebuild -version | ||
|
||
# Try to restore installed Gems | ||
- restore_cache: | ||
keys: | ||
- v1-gems-{{ checksum "Gemfile.lock" }} | ||
- v1-gems- | ||
- run: bundle check || bundle install --path=vendor/bundle --jobs 4 --retry 3 | ||
- save_cache: | ||
key: v1-gems-{{ checksum "Gemfile.lock" }} | ||
paths: | ||
- vendor/bundle | ||
|
||
# Try to restore CocoaPods dependencies | ||
- restore_cache: | ||
keys: | ||
- v2-pods-{{ checksum "Podfile.lock" }} | ||
- v2-pods- | ||
- run: bundle exec fastlane oss_keys | ||
- run: bundle exec pod check || curl -sS https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf | ||
- run: bundle exec pod check || bundle exec pod install --verbose | ||
- save_cache: | ||
key: v2-pods-{{ checksum "Podfile.lock" }} | ||
paths: | ||
- Pods | ||
|
||
# Restore the derived data cache (to speed up CI compile times), run the tests, and store the cache | ||
- restore_cache: | ||
keys: | ||
- v1-derived-{{ .Branch }} | ||
- v1-derived- | ||
- run: | ||
name: Run tests | ||
command: set -o pipefail && xcodebuild -destination "name=iPad Air 2,OS=11.2" -scheme "Kiosk" -workspace "Kiosk.xcworkspace" -derivedDataPath derived_data build | xcpretty --color | ||
- save_cache: | ||
key: v1-derived-{{ .Branch }} | ||
paths: | ||
- derived_data | ||
|
||
# Teardown | ||
# Save the Xcode activity log | ||
- run: find $HOME/Library/Developer/Xcode/DerivedData -name '*.xcactivitylog' -exec cp {} $CIRCLE_ARTIFACTS/xcactivitylog \; || true | ||
# Save test results | ||
- store_test_results: | ||
path: $CIRCLE_TEST_REPORTS | ||
# Save artifacts | ||
- store_artifacts: | ||
path: $ CIRCLE_ARTIFACTS | ||
- store_artifacts: | ||
path: $CIRCLE_TEST_REPORTS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,181 +1,107 @@ | ||
import UIKit | ||
import RxSwift | ||
import CardFlight | ||
|
||
class CardHandler: NSObject, CFTTransactionDelegate { | ||
class CardHandler: NSObject, CFTReaderDelegate { | ||
|
||
private let _cardStatus = PublishSubject<String>() | ||
private let _userMessages = PublishSubject<String>() | ||
private var cardReader: CFTCardReaderInfo? | ||
|
||
var transaction: CFTTransaction? | ||
fileprivate let _cardStatus = PublishSubject<String>() | ||
|
||
var cardStatus: Observable<String> { | ||
return _cardStatus.asObservable() | ||
} | ||
|
||
var userMessages: Observable<String> { | ||
// User messages are things like "Swipe card", "processing", or "Swipe card again". Due to a problem with the | ||
// CardFlight SDK, the user is prompted to accept processing for card tokenization, which is provides a | ||
// unfriendly user experience (prompting to accept a transaction that we're not actually placing). So we | ||
// auto-accept these requests and filter out confirmation messages, which don't apply to tokenization flows, | ||
// until this issue is fixed: https://github.com/CardFlight/cardflight-v4-ios/issues/4 | ||
return _userMessages | ||
.asObservable() | ||
.filter { message -> Bool in | ||
!message.hasSuffix("?") | ||
} | ||
} | ||
|
||
var cardFlightCredentials: CFTCredentials { | ||
let credentials = CFTCredentials() | ||
credentials.setup(apiKey: self.APIKey, accountToken: self.APIToken, completion: nil) | ||
return credentials | ||
} | ||
|
||
var card: (cardInfo: CFTCardInfo, token: String)? | ||
|
||
|
||
var card: CFTCard? | ||
|
||
let APIKey: String | ||
let APIToken: String | ||
|
||
var reader: CFTReader! | ||
lazy var sessionManager = CFTSessionManager.sharedInstance()! | ||
|
||
init(apiKey: String, accountToken: String){ | ||
APIKey = apiKey | ||
APIToken = accountToken | ||
|
||
super.init() | ||
|
||
self.transaction = CFTTransaction(delegate: self) | ||
} | ||
|
||
deinit { | ||
self.end() | ||
sessionManager.setApiToken(APIKey, accountToken: APIToken, completed: nil) | ||
} | ||
|
||
func startSearching() { | ||
_cardStatus.onNext("Starting search...") | ||
let tokenizationParameters = CFTTokenizationParameters(customerId: nil, credentials: self.cardFlightCredentials) | ||
self.transaction?.beginTokenizing(tokenizationParameters: tokenizationParameters) | ||
} | ||
sessionManager.setLogging(true) | ||
|
||
func end() { | ||
transaction?.select(processOption: CFTProcessOption.abort) | ||
transaction = nil | ||
reader = CFTReader(reader: CFTReaderType.UNKNOWN) | ||
reader.delegate = self | ||
reader.swipeHasTimeout(false) | ||
_cardStatus.onNext("Started searching") | ||
} | ||
|
||
func transaction(_ transaction: CFTTransaction, didUpdate state: CFTTransactionState, error: Error?) { | ||
switch state { | ||
case .completed: | ||
_cardStatus.onNext("Transaction completed") | ||
case .processing: | ||
_cardStatus.onNext("Transaction processing") | ||
case .deferred: | ||
_cardStatus.onNext("Transaction deferred") | ||
case .pendingCardInput: | ||
_cardStatus.onNext("Pending card input") | ||
transaction.select(cardReaderInfo: cardReader, cardReaderModel: cardReader?.cardReaderModel ?? .unknown) | ||
case .pendingTransactionParameters: | ||
_cardStatus.onNext("Pending transaction parameters") | ||
case .unknown: | ||
_cardStatus.onNext("Unknown transactionstate") | ||
case .pendingProcessOption: | ||
break | ||
} | ||
func end() { | ||
reader.cancelTransaction() | ||
reader = nil | ||
} | ||
|
||
func transaction(_ transaction: CFTTransaction, didComplete historicalTransaction: CFTHistoricalTransaction) { | ||
if let cardInfo = historicalTransaction.cardInfo, let token = historicalTransaction.cardToken { | ||
self.card = (cardInfo: cardInfo, token: token) | ||
func readerCardResponse(_ card: CFTCard?, withError error: Error?) { | ||
if let card = card { | ||
self.card = card; | ||
_cardStatus.onNext("Got Card") | ||
_cardStatus.onCompleted() | ||
} else { | ||
_cardStatus.onNext("Card Flight Error – could not retrieve card data."); | ||
if let error = historicalTransaction.error { | ||
_cardStatus.onNext("response Error \(error)"); | ||
logger.log("CardReader got a response it cannot handle") | ||
} | ||
startSearching() | ||
|
||
card.tokenizeCard(success: { [weak self] in | ||
self?._cardStatus.onCompleted() | ||
logger.log("Card was tokenized") | ||
|
||
}, failure: { [weak self] (error) in | ||
self?._cardStatus.onNext("Card Flight Error: \(String(describing: error))"); | ||
logger.log("Card was not tokenizable") | ||
}) | ||
|
||
} else if let error = error { | ||
self._cardStatus.onNext("response Error \(error)"); | ||
logger.log("CardReader got a response it cannot handle") | ||
|
||
|
||
reader.beginSwipe(); | ||
} | ||
} | ||
|
||
func transaction(_ transaction: CFTTransaction, didReceive cardReaderEvent: CFTCardReaderEvent, cardReaderInfo: CFTCardReaderInfo?) { | ||
_cardStatus.onNext(cardReaderEvent.statusMessage) | ||
func transactionResult(_ charge: CFTCharge!, withError error: Error!) { | ||
logger.log("Unexcepted call to transactionResult callback: \(charge)\n\(error)") | ||
} | ||
|
||
func transaction(_ transaction: CFTTransaction, didUpdate cardReaderArray: [CFTCardReaderInfo]) { | ||
self.cardReader = cardReaderArray.first | ||
_cardStatus.onNext("Received new card reader availability, number of readers: \(cardReaderArray.count)") | ||
// handle other delegate call backs with the status messages | ||
|
||
func readerIsAttached() { | ||
_cardStatus.onNext("Reader is attatched"); | ||
} | ||
|
||
func transaction(_ transaction: CFTTransaction, didRequestProcessOption cardInfo: CFTCardInfo) { | ||
logger.log("Received request for processing option, will process transaction.") | ||
_cardStatus.onNext("Request for process option, automatically processing...") | ||
// We auto-accept the process option on the user's behalf because the prompt doesn't make sense in a | ||
// tokenization flow. See comments in `userMessages` property above. | ||
transaction.select(processOption: .process) | ||
func readerIsConnecting() { | ||
_cardStatus.onNext("Reader is connecting"); | ||
} | ||
|
||
func transaction(_ transaction: CFTTransaction, didRequestDisplay message: CFTMessage) { | ||
let message = message.primary ?? message.secondary ?? "" | ||
_userMessages.onNext(message) | ||
logger.log("Received request to display message: \(message)") | ||
_cardStatus.onNext("Received message for user: \(message)") | ||
func readerIsDisconnected() { | ||
_cardStatus.onNext("Reader is disconnected"); | ||
logger.log("Card Reader Disconnected") | ||
} | ||
} | ||
|
||
typealias UnhandledDelegateCallbacks = CardHandler | ||
/// We don't expect any of these functions to be called, but they are required for the delegate protocol. | ||
extension UnhandledDelegateCallbacks { | ||
func transaction(_ transaction: CFTTransaction, didDefer transactionData: Data) { | ||
logger.log("Transaction has been deferred.") | ||
_cardStatus.onNext("Transaction deferred") | ||
func readerSwipeDidCancel() { | ||
_cardStatus.onNext("Reader did cancel"); | ||
logger.log("Card Reader was Cancelled") | ||
} | ||
|
||
public func transaction(_ transaction: CFTTransaction, didRequest cvm: CFTCVM) { | ||
if cvm == CFTCVM.signature { | ||
logger.log("Transaction requested signature from user, which should not occur for tokenization.") | ||
_cardStatus.onNext("Ignoring user signature request from CardFlight") | ||
} | ||
func readerGenericResponse(_ cardData: String!) { | ||
_cardStatus.onNext("Reader received non-card data: \(cardData ?? "") "); | ||
reader.beginSwipe() | ||
} | ||
} | ||
|
||
extension CFTCardReaderEvent { | ||
var statusMessage: String { | ||
switch self { | ||
case .unknown: | ||
return "Unknown card event" | ||
case .disconnected: | ||
return "Reader is disconnected" | ||
case .connected: | ||
return "Reader is connected" | ||
case .connectionErrored: | ||
return "Connection error occurred" | ||
case .cardSwiped: | ||
return "Card swiped" | ||
case .cardSwipeErrored: | ||
return "Card swipe error" | ||
case .cardInserted: | ||
return "Card inserted" | ||
case .cardInsertErrored: | ||
return "Card insertion error" | ||
case .cardRemoved: | ||
return "Card removed" | ||
case .cardTapped: | ||
return "Card tapped" | ||
case .cardTapErrored: | ||
return "Card tap error" | ||
case .updateStarted: | ||
return "Update started" | ||
case .updateCompleted: | ||
return "Updated completed" | ||
case .audioRecordingPermissionNotGranted: | ||
return "iOS audio permissions no granted" | ||
case .fatalError: | ||
return "Fatal error" | ||
case .connecting: | ||
return "Connecting" | ||
case .batteryStatusUpdated: | ||
return "Battery status updated" | ||
func readerIsConnected(_ isConnected: Bool, withError error: Error!) { | ||
if isConnected { | ||
_cardStatus.onNext("Reader is connected") | ||
reader.beginSwipe() | ||
} else { | ||
if (error != nil) { | ||
_cardStatus.onNext("Reader is disconnected: \(error.localizedDescription)"); | ||
} else { | ||
_cardStatus.onNext("Reader is disconnected"); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.