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 #695 from artsy/cardflight-revert
Browse files Browse the repository at this point in the history
Fixes CardFlight integration
  • Loading branch information
ashfurrow committed Oct 30, 2018
2 parents e90a1ff + 52ca7d7 commit b97de34
Show file tree
Hide file tree
Showing 15 changed files with 321 additions and 355 deletions.
66 changes: 66 additions & 0 deletions .circleci/config.yml
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
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
Loading

0 comments on commit b97de34

Please sign in to comment.