Skip to content
This repository has been archived by the owner on Jan 4, 2023. It is now read-only.

Commit

Permalink
Merge pull request #687 from artsy/cardflight-update
Browse files Browse the repository at this point in the history
Updates to v4 of CardFlight SDK
  • Loading branch information
ashfurrow authored Mar 6, 2018
2 parents e98923f + c8fdf99 commit 24c721a
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 220 deletions.
3 changes: 3 additions & 0 deletions Kiosk.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,7 @@
"${BUILT_PRODUCTS_DIR}/Artsy+UIFonts/Artsy_UIFonts.framework",
"${BUILT_PRODUCTS_DIR}/Artsy+UILabels/Artsy_UILabels.framework",
"${BUILT_PRODUCTS_DIR}/Artsy-UIButtons/Artsy_UIButtons.framework",
"${PODS_ROOT}/CardFlight-v4/CardFlight.framework",
"${BUILT_PRODUCTS_DIR}/DZNWebViewController/DZNWebViewController.framework",
"${BUILT_PRODUCTS_DIR}/ECPhoneNumberFormatter/ECPhoneNumberFormatter.framework",
"${BUILT_PRODUCTS_DIR}/FLKAutoLayout/FLKAutoLayout.framework",
Expand Down Expand Up @@ -1186,6 +1187,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Artsy_UIFonts.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Artsy_UILabels.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Artsy_UIButtons.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CardFlight.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DZNWebViewController.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ECPhoneNumberFormatter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLKAutoLayout.framework",
Expand Down Expand Up @@ -1551,6 +1553,7 @@
"$(SRCROOT)/Pods/CardFlight/**",
);
INFOPLIST_FILE = "Kiosk/Supporting Files/Info.plist";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" -DDEBUG";
PRODUCT_BUNDLE_IDENTIFIER = net.artsy.kiosk.beta;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Kiosk/Supporting Files/BridgingHeader.h";
Expand Down
10 changes: 6 additions & 4 deletions Kiosk/Admin/AdminCardTestingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,23 @@ class AdminCardTestingViewController: UIViewController {
return
}

let cardDetails = "Card: \(card.name ?? "") - \(card.last4 ?? "") \n \(card.cardToken ?? "")"
let cardDetails = "Card: \(card.cardInfo.cardholderName ?? "") - \(card.cardInfo.lastFour ?? "") \n \(card.token)"
self.log(cardDetails)
}
}
.disposed(by: rx.disposeBag)


cardHandler.startSearching()
}

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
cardHandler.end()
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
cardHandler.startSearching()
}

func log(_ string: String) {
self.logTextView.text = "\(self.logTextView.text ?? "")\n\(string)"

Expand Down
198 changes: 136 additions & 62 deletions Kiosk/App/CardHandler.swift
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"
}
}
}
2 changes: 1 addition & 1 deletion Kiosk/App/GlobalFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func responseIsOK(_ response: Response) -> Bool {

func detectDevelopmentEnvironment() -> Bool {
var developmentEnvironment = false
#if DEBUG || (arch(i386) || arch(x86_64)) && os(iOS)
#if DEBUG || (arch(i386) || arch(x86_64))
developmentEnvironment = true
#endif
return developmentEnvironment
Expand Down
30 changes: 19 additions & 11 deletions Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ class SwipeCreditCardViewController: UIViewController, RegistrationSubController
@IBOutlet var cardStatusLabel: ARSerifLabel!
let finished = PublishSubject<Void>()

@IBOutlet weak var titleLabel: ARSerifLabel!
@IBOutlet weak var spinner: Spinner!
@IBOutlet weak var processingLabel: UILabel!
@IBOutlet weak var illustrationImageView: UIImageView!

@IBOutlet weak var titleLabel: ARSerifLabel!

class func instantiateFromStoryboard(_ storyboard: UIStoryboard) -> SwipeCreditCardViewController {
return storyboard.viewController(withID: .RegisterCreditCard) as! SwipeCreditCardViewController
Expand Down Expand Up @@ -44,17 +43,27 @@ class SwipeCreditCardViewController: UIViewController, RegistrationSubController
super.viewDidLoad()
self.setInProgress(false)

let pleaseWaitMessage = "Please wait..."

cardHandler.userMessages
.startWith(pleaseWaitMessage)
.map { message in
if message.isEmpty {
return pleaseWaitMessage
} else {
return message
}
}
.bind(to: titleLabel.rx.text)
.disposed(by: rx.disposeBag)

cardHandler.cardStatus
.takeUntil(self.viewWillDisappear)
.subscribe(onNext: { message in
self.cardStatusLabel.text = "Card Status: \(message)"
if message == "Got Card" {
self.setInProgress(true)
}

if message.hasPrefix("Card Flight Error") {
self.processingLabel.text = "ERROR PROCESSING CARD - SEE ADMIN"
}
},
onError: { error in
self.cardStatusLabel.text = "Card Status: Errored"
Expand All @@ -66,13 +75,13 @@ class SwipeCreditCardViewController: UIViewController, RegistrationSubController
self.cardStatusLabel.text = "Card Status: completed"

if let card = self.cardHandler.card {
self.cardName.value = card.name
self.cardLastDigits.value = card.last4
self.cardName.value = card.cardInfo.cardholderName ?? ""
self.cardLastDigits.value = card.cardInfo.lastFour ?? ""

self.cardToken.value = card.cardToken
self.cardToken.value = card.token

if let newUser = self.navigationController?.fulfillmentNav().bidDetails.newUser {
newUser.name.value = (newUser.name.value.isNilOrEmpty) ? card.name : newUser.name.value
newUser.name.value = (newUser.name.value.isNilOrEmpty) ? card.cardInfo.cardholderName : newUser.name.value
}
}

Expand Down Expand Up @@ -116,7 +125,6 @@ class SwipeCreditCardViewController: UIViewController, RegistrationSubController

func setInProgress(_ show: Bool) {
illustrationImageView.alpha = show ? 0.1 : 1
processingLabel.isHidden = !show
spinner.isHidden = !show
}

Expand Down
Loading

0 comments on commit 24c721a

Please sign in to comment.