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

Fixes CardFlight integration #695

Merged
merged 3 commits into from
Oct 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Kiosk.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1151,7 +1151,6 @@
"${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 @@ -1187,7 +1186,6 @@
"${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 @@ -1553,7 +1551,6 @@
"$(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: 4 additions & 6 deletions Kiosk/Admin/AdminCardTestingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,21 @@ class AdminCardTestingViewController: UIViewController {
return
}

let cardDetails = "Card: \(card.cardInfo.cardholderName ?? "") - \(card.cardInfo.lastFour ?? "") \n \(card.token)"
let cardDetails = "Card: \(card.name ?? "") - \(card.last4 ?? "") \n \(card.cardToken ?? "")"
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
8 changes: 8 additions & 0 deletions Kiosk/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SDWebImage
import RxSwift
import Keys
import Stripe
import MediaPlayer

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
Expand Down Expand Up @@ -64,6 +65,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

logger.log("App Started")
ARAnalytics.event("Session Started")

// CardFlight v3 has a problem displaying the volume HUD (from a background thread, I think it confuses iOS).
// However, we can't upgrade to v4 yet. See for more info: https://artsyproduct.atlassian.net/browse/PURCHASE-615
let volumeView = MPVolumeView(frame: .zero)
volumeView.clipsToBounds = true
window?.addSubview(volumeView)

return true
}

Expand Down
198 changes: 62 additions & 136 deletions Kiosk/App/CardHandler.swift
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");
}
}
}
}
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))
#if DEBUG || (arch(i386) || arch(x86_64)) && os(iOS)
developmentEnvironment = true
#endif
return developmentEnvironment
Expand Down
30 changes: 11 additions & 19 deletions Kiosk/Bid Fulfillment/SwipeCreditCardViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ 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 @@ -43,27 +44,17 @@ 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 @@ -75,13 +66,13 @@ class SwipeCreditCardViewController: UIViewController, RegistrationSubController
self.cardStatusLabel.text = "Card Status: completed"

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

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

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

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

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

Expand Down
Loading