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 356
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 #687 from artsy/cardflight-update
Updates to v4 of CardFlight SDK
- Loading branch information
Showing
11 changed files
with
322 additions
and
220 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
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,107 +1,181 @@ | ||
import UIKit | ||
import RxSwift | ||
import CardFlight | ||
|
||
class CardHandler: NSObject, CFTReaderDelegate { | ||
class CardHandler: NSObject, CFTTransactionDelegate { | ||
|
||
fileprivate let _cardStatus = PublishSubject<String>() | ||
private let _cardStatus = PublishSubject<String>() | ||
private let _userMessages = PublishSubject<String>() | ||
private var cardReader: CFTCardReaderInfo? | ||
|
||
var transaction: CFTTransaction? | ||
|
||
var cardStatus: Observable<String> { | ||
return _cardStatus.asObservable() | ||
} | ||
|
||
var card: CFTCard? | ||
|
||
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)? | ||
|
||
|
||
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() | ||
|
||
sessionManager.setApiToken(APIKey, accountToken: APIToken, completed: nil) | ||
self.transaction = CFTTransaction(delegate: self) | ||
} | ||
|
||
func startSearching() { | ||
sessionManager.setLogging(true) | ||
deinit { | ||
self.end() | ||
} | ||
|
||
reader = CFTReader(reader: CFTReaderType.UNKNOWN) | ||
reader.delegate = self | ||
reader.swipeHasTimeout(false) | ||
_cardStatus.onNext("Started searching") | ||
func startSearching() { | ||
_cardStatus.onNext("Starting search...") | ||
let tokenizationParameters = CFTTokenizationParameters(customerId: nil, credentials: self.cardFlightCredentials) | ||
self.transaction?.beginTokenizing(tokenizationParameters: tokenizationParameters) | ||
} | ||
|
||
func end() { | ||
reader.cancelTransaction() | ||
reader = nil | ||
transaction?.select(processOption: CFTProcessOption.abort) | ||
transaction = nil | ||
} | ||
|
||
func readerCardResponse(_ card: CFTCard?, withError error: Error?) { | ||
if let card = card { | ||
self.card = card; | ||
_cardStatus.onNext("Got Card") | ||
|
||
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, 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 transactionResult(_ charge: CFTCharge!, withError error: Error!) { | ||
logger.log("Unexcepted call to transactionResult callback: \(charge)\n\(error)") | ||
func transaction(_ transaction: CFTTransaction, didComplete historicalTransaction: CFTHistoricalTransaction) { | ||
if let cardInfo = historicalTransaction.cardInfo, let token = historicalTransaction.cardToken { | ||
self.card = (cardInfo: cardInfo, token: token) | ||
_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() | ||
} | ||
} | ||
|
||
// handle other delegate call backs with the status messages | ||
func transaction(_ transaction: CFTTransaction, didReceive cardReaderEvent: CFTCardReaderEvent, cardReaderInfo: CFTCardReaderInfo?) { | ||
_cardStatus.onNext(cardReaderEvent.statusMessage) | ||
} | ||
|
||
func readerIsAttached() { | ||
_cardStatus.onNext("Reader is attatched"); | ||
func transaction(_ transaction: CFTTransaction, didUpdate cardReaderArray: [CFTCardReaderInfo]) { | ||
self.cardReader = cardReaderArray.first | ||
_cardStatus.onNext("Received new card reader availability, number of readers: \(cardReaderArray.count)") | ||
} | ||
|
||
func readerIsConnecting() { | ||
_cardStatus.onNext("Reader is connecting"); | ||
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 readerIsDisconnected() { | ||
_cardStatus.onNext("Reader is disconnected"); | ||
logger.log("Card Reader Disconnected") | ||
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 readerSwipeDidCancel() { | ||
_cardStatus.onNext("Reader did cancel"); | ||
logger.log("Card Reader was Cancelled") | ||
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 readerGenericResponse(_ cardData: String!) { | ||
_cardStatus.onNext("Reader received non-card data: \(cardData ?? "") "); | ||
reader.beginSwipe() | ||
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 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"); | ||
} | ||
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" | ||
} | ||
} | ||
} |
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
Oops, something went wrong.