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

@orta: Fixes for Kiosk CC testing on Staging, Unresolved bids, Bidding errors #510

Merged
merged 11 commits into from
Sep 24, 2015
Merged
1 change: 1 addition & 0 deletions CHANGELOG.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ upcoming:
- Displays artwork thumbnail until full image is downlaoded for detail view [ash]
- Tapping anywhere in the table cells shows more details [ash]
- Allows users to dismiss high bid screens by tapping checkmark [ash]
- Adds staging-only credit card testing [ash]
releases:
- version: 3.5.2
date: June 19 2015
Expand Down
4 changes: 4 additions & 0 deletions Kiosk.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@
5EBD314F1AE0563300648484 /* GenericFormValidationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EBD314E1AE0563300648484 /* GenericFormValidationViewModelTests.swift */; };
5EDBB3251BAB59A000D6A6C8 /* RACAlertAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EDBB3241BAB59A000D6A6C8 /* RACAlertAction.m */; settings = {ASSET_TAGS = (); }; };
5EDD04D21AA69D2700B30AE9 /* KeypadViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EDD04D11AA69D2700B30AE9 /* KeypadViewModelTests.swift */; };
5EE5B0CE1BB3479300069312 /* PlaceBidNetworkModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EE5B0CD1BB3479300069312 /* PlaceBidNetworkModelTests.swift */; settings = {ASSET_TAGS = (); }; };
5EF71E781AE986000069A266 /* StripeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF71E771AE986000069A266 /* StripeManager.swift */; };
5EF71E7B1AE98F080069A266 /* STPCard+Validation.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EF71E7A1AE98F080069A266 /* STPCard+Validation.m */; };
5EF71E7D1AE996F50069A266 /* SwiftExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF71E7C1AE996F50069A266 /* SwiftExtensions.swift */; };
Expand Down Expand Up @@ -422,6 +423,7 @@
5EDBB3231BAB59A000D6A6C8 /* RACAlertAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACAlertAction.h; sourceTree = "<group>"; };
5EDBB3241BAB59A000D6A6C8 /* RACAlertAction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACAlertAction.m; sourceTree = "<group>"; };
5EDD04D11AA69D2700B30AE9 /* KeypadViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeypadViewModelTests.swift; sourceTree = "<group>"; };
5EE5B0CD1BB3479300069312 /* PlaceBidNetworkModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceBidNetworkModelTests.swift; sourceTree = "<group>"; };
5EF71E771AE986000069A266 /* StripeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StripeManager.swift; sourceTree = "<group>"; };
5EF71E791AE98F080069A266 /* STPCard+Validation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "STPCard+Validation.h"; sourceTree = "<group>"; };
5EF71E7A1AE98F080069A266 /* STPCard+Validation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "STPCard+Validation.m"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -743,6 +745,7 @@
5E82BD341AF2C6E700CB02DD /* YourBiddingDetailsViewControllerTests.swift */,
5E8610CF1A5C43EF00456E41 /* PlaceBidViewControllerTests.swift */,
5EDD04D11AA69D2700B30AE9 /* KeypadViewModelTests.swift */,
5EE5B0CD1BB3479300069312 /* PlaceBidNetworkModelTests.swift */,
);
path = "Bid Fulfillment";
sourceTree = "<group>";
Expand Down Expand Up @@ -1257,6 +1260,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5EE5B0CE1BB3479300069312 /* PlaceBidNetworkModelTests.swift in Sources */,
5E8611081A5C43EF00456E41 /* ConfirmYourBidPINViewControllerTests.swift in Sources */,
5E8611301A5C43EF00456E41 /* RegisterFlowViewTests.swift in Sources */,
5E8611101A5C43EF00456E41 /* ListingsViewControllerTests.swift in Sources */,
Expand Down
8 changes: 6 additions & 2 deletions Kiosk/Admin/AdminCardTestingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ class AdminCardTestingViewController: UIViewController {

self.logTextView.text = ""

cardHandler = CardHandler(apiKey: self.keys.cardflightAPIClientKey(), accountToken: self.keys.cardflightMerchantAccountToken())

if AppSetup.sharedState.useStaging {
cardHandler = CardHandler(apiKey: self.keys.cardflightStagingAPIClientKey(), accountToken: self.keys.cardflightStagingMerchantAccountToken())
} else {
cardHandler = CardHandler(apiKey: self.keys.cardflightProductionAPIClientKey(), accountToken: self.keys.cardflightProductionMerchantAccountToken())
}

cardHandler.cardSwipedSignal.subscribeNext({ (message) -> Void in
self.log("\(message)")
return
Expand Down
7 changes: 6 additions & 1 deletion Kiosk/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
window?.makeKeyAndVisible()

let keys = EidolonKeys()
Stripe.setDefaultPublishableKey(keys.stripePublishableKey())

if AppSetup.sharedState.useStaging {
Stripe.setDefaultPublishableKey(keys.stripeStagingPublishableKey())
} else {
Stripe.setDefaultPublishableKey(keys.stripeProductionPublishableKey())
}

let mixpanelToken = AppSetup.sharedState.useStaging ? keys.mixpanelStagingAPIClientKey() : keys.mixpanelProductionAPIClientKey()

Expand Down
2 changes: 1 addition & 1 deletion Kiosk/App/Models/Image.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Image: JSONAble {

let id = json["id"].stringValue
let imageFormatString = json["image_url"].stringValue
let imageVersions = json["image_versions"].object as! [String]
let imageVersions = (json["image_versions"].object as? [String]) ?? []
let imageSize = CGSize(width: json["original_width"].int ?? 1, height: json["original_height"].int ?? 1)
let aspectRatio = { () -> CGFloat? in
if let aspectRatio = json["aspect_ratio"].float {
Expand Down
2 changes: 1 addition & 1 deletion Kiosk/App/Networking/ArtsyAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ struct Provider {

// MARK: - Provider support

private func stubbedResponse(filename: String) -> NSData! {
func stubbedResponse(filename: String) -> NSData! {
@objc class TestClass: NSObject { }

let bundle = NSBundle(forClass: TestClass.self)
Expand Down
11 changes: 8 additions & 3 deletions Kiosk/App/Views/SimulatorOnlyView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ import UIKit
class DeveloperOnlyView: UIView {

override func awakeFromNib() {
let shouldShow = AppSetup.sharedState.showDebugButtons && AppSetup.sharedState.useStaging
let notTests = NSClassFromString("XCTest") == nil
self.hidden = !shouldShow && notTests
// Show only if we're supposed to show AND we're on staging.
self.hidden = !(AppSetup.sharedState.showDebugButtons && AppSetup.sharedState.useStaging)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic here was messy, so I made it prettier.

if let _ = NSClassFromString("XCTest") {
// We are running in a test.
self.hidden = true
self.alpha = 0
}
}
}
2 changes: 1 addition & 1 deletion Kiosk/Bid Fulfillment/BidCheckingNetworkModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Moya
class BidCheckingNetworkModel: NSObject {

private var pollInterval = NSTimeInterval(1)
private var maxPollRequests = 6
private var maxPollRequests = 20
private var pollRequests = 0

// inputs
Expand Down
19 changes: 13 additions & 6 deletions Kiosk/Bid Fulfillment/BidderNetworkModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ class BidderNetworkModel: NSObject {
}

private func createOrUpdateUser() -> RACSignal {
let boolSignal = self.checkUserEmailExists(fulfillmentController.bidDetails.newUser.email!)
// Signal to test for user existence (does a user exist with this email?)
let boolSignal = self.checkUserEmailExists(fulfillmentController.bidDetails.newUser.email ?? "")
// If the user exists, update their info to the API, otherwise create a new user.
let signal = RACSignal.`if`(boolSignal, then: self.updateUser(), `else`: self.createNewUser())

// After update/create signal finishes, add a CC to their account (if we've collected one)
return signal.then { self.addCardToUser() }
}

Expand Down Expand Up @@ -66,19 +70,22 @@ class BidderNetworkModel: NSObject {

return updateProviderIfNecessary().then {
self.fulfillmentController.loggedInProvider!.request(endpoint).filterSuccessfulStatusCodes().mapJSON()
}.doError { (error) in
}.logNext().doError { (error) in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

logger.log("Updating user failed.")
logger.log("Error: \(error.localizedDescription). \n \(error.artsyServerError())")
}
}

private func addCardToUser() -> RACSignal {
if (fulfillmentController.bidDetails.newUser.creditCardToken == nil) { return RACSignal.empty() }
let endpoint: ArtsyAPI = ArtsyAPI.RegisterCard(stripeToken: fulfillmentController.bidDetails.newUser.creditCardToken!)
// If the user was asked to swipe a card, we'd have stored the token.
// If the token is not there, then the user must already have one on file. So we can skip this step.
guard let token = fulfillmentController.bidDetails.newUser.creditCardToken else {
return RACSignal.empty()
}

// on Staging the card tokenization fails
let endpoint: ArtsyAPI = ArtsyAPI.RegisterCard(stripeToken: token)

return fulfillmentController.loggedInProvider!.request(endpoint).doError { (error) in
return fulfillmentController.loggedInProvider!.request(endpoint).filterSuccessfulStatusCodes().mapJSON().doError { (error) in
logger.log("Adding Card to User failed.")
logger.log("Error: \(error.localizedDescription). \n \(error.artsyServerError())")
}
Expand Down
28 changes: 13 additions & 15 deletions Kiosk/Bid Fulfillment/ConfirmYourBidPINViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,31 +34,29 @@ class ConfirmYourBidPINViewController: UIViewController {
/// verify if we can connect with number & pin

confirmButton.rac_command = RACCommand(enabled: pinExistsSignal) { [weak self] _ in
if (self == nil) { return RACSignal.empty() }
guard let strongSelf = self else { return RACSignal.empty() }

let phone = self!.fulfillmentNav().bidDetails.newUser.phoneNumber
let phone = strongSelf.fulfillmentNav().bidDetails.newUser.phoneNumber ?? ""
let endpoint: ArtsyAPI = ArtsyAPI.Me

let testProvider = self!.providerForPIN(String(self!.pin), number:phone!)

return testProvider.request(endpoint).filterSuccessfulStatusCodes().doNext { _ in

self?.fulfillmentNav().loggedInProvider = testProvider
return
let loggedInProvider = strongSelf.providerForPIN(strongSelf.pin, number: phone)

return loggedInProvider.request(endpoint).filterSuccessfulStatusCodes().doNext { _ in
// If the request to ArtsyAPI.Me succeeds, we have logged in and can use this provider.
self?.fulfillmentNav().loggedInProvider = loggedInProvider
}.then {
// We want to put the data we've collected up to the server.
self?.fulfillmentNav().updateUserCredentials() ?? RACSignal.empty()

}.then {
// This looks for credit cards on the users account, and sends them on the signal
self?.checkForCreditCard() ?? RACSignal.empty()

}.doNext { (cards) in
if (self == nil) { return }
if (cards as! [Card]).count > 0 {
self?.performSegue(.PINConfirmedhasCard)

} else {
// If the cards list doesn't exist, or its empty, then perform the segue to collect one.
// Otherwise, proceed directly to the loading view controller to place the bid.
if (cards as? [Card]).isNilOrEmpty {
self?.performSegue(.ArtsyUserviaPINHasNotRegisteredCard)
} else {
self?.performSegue(.PINConfirmedhasCard)
}

}.doError({ [weak self] (error) -> Void in
Expand Down
16 changes: 10 additions & 6 deletions Kiosk/Bid Fulfillment/LoadingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ class LoadingViewController: UIViewController {

// The view model will perform actions like registering a user if necessary,
// placing a bid if requested, and polling for results.
viewModel.performActions().subscribeError({ [weak self] (error) -> Void in
viewModel.performActions().finally { [weak self] in
// Regardless of error or completion. hide the spinner.
self?.spinner.hidden = true
}.subscribeError({ [weak self] (error) -> Void in
self?.bidderError(error)
}, completed: { [weak self] () -> Void in
self?.finishUp()
Expand All @@ -58,8 +61,6 @@ class LoadingViewController: UIViewController {


func finishUp() {
self.spinner.hidden = true

let reserveNotMet = viewModel.reserveNotMet
let isHighestBidder = viewModel.isHighestBidder
let bidIsResolved = viewModel.bidIsResolved
Expand Down Expand Up @@ -117,7 +118,7 @@ class LoadingViewController: UIViewController {
}

func handleUnknownBidder() {
titleLabel.text = "Bid Confirmed"
titleLabel.text = "Bid Submitted"
bidConfirmationImageView.image = UIImage(named: "BidHighestBidder")
}

Expand Down Expand Up @@ -157,7 +158,11 @@ class LoadingViewController: UIViewController {
func bidderError(error: NSError?) {
if placingBid {
// If you are bidding, we show a bidding error regardless of whether or not you're also registering.
bidPlacementFailed(error)
if error?.domain == OutbidDomain {
handleLowestBidder()
} else {
bidPlacementFailed(error)
}
} else {
// If you're not placing a bid, you're here because you're just registering.
presentError("Registration Failed", message: "There was a problem registering for the auction. Please speak to an Artsy representative.")
Expand All @@ -175,7 +180,6 @@ class LoadingViewController: UIViewController {
}

func presentError(title: String, message: String) {
spinner.hidden = true
titleLabel.textColor = .artsyRed()
titleLabel.text = title
statusMessage.text = message
Expand Down
2 changes: 1 addition & 1 deletion Kiosk/Bid Fulfillment/LoadingViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class LoadingViewModel: NSObject {

if let strongSelf = self {
ARAnalytics.event("Started Placing Bid")
return strongSelf.placeBidNetworkModel.bidSignal(strongSelf.bidderNetworkModel.fulfillmentController.bidDetails)
return strongSelf.placeBidNetworkModel.bidSignal()
} else {
return RACSignal.empty()
}
Expand Down
22 changes: 22 additions & 0 deletions Kiosk/Bid Fulfillment/ManualCreditCardInputViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,25 @@ class ManualCreditCardInputViewController: UIViewController, RegistrationSubCont
return storyboard.viewControllerWithID(.ManualCardDetailsInput) as! ManualCreditCardInputViewController
}
}

private extension ManualCreditCardInputViewController {
func applyCardWithSuccess(success: Bool) {
cardNumberTextField.text = success ? "4242424242424242" : "4000000000000002"
cardNumberTextField.sendActionsForControlEvents(.AllEditingEvents)
cardConfirmButton.sendActionsForControlEvents(.TouchUpInside)

expirationMonthTextField.text = "04"
expirationMonthTextField.sendActionsForControlEvents(.AllEditingEvents)
expirationYearTextField.text = "2018"
expirationYearTextField.sendActionsForControlEvents(.AllEditingEvents)
dateConfirmButton.sendActionsForControlEvents(.TouchUpInside)
}

@IBAction func dev_creditCardOKTapped(sender: AnyObject) {
applyCardWithSuccess(true)
}

@IBAction func dev_creditCardFailTapped(sender: AnyObject) {
applyCardWithSuccess(false)
}
}
2 changes: 1 addition & 1 deletion Kiosk/Bid Fulfillment/ManualCreditCardInputViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,6 @@ class ManualCreditCardInputViewModel: NSObject {
}
}

// Only for testing purposes
// Only set for testing purposes, otherwise ignore.
lazy var stripeManager: StripeManager = StripeManager()
}
27 changes: 25 additions & 2 deletions Kiosk/Bid Fulfillment/PlaceBidNetworkModel.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import UIKit
import ReactiveCocoa
import Moya
import SwiftyJSON

let OutbidDomain = "Outbid"

class PlaceBidNetworkModel: NSObject {

var fulfillmentController: FulfillmentController!
var bidderPosition: BidderPosition?

var provider: ReactiveCocoaMoyaProvider<ArtsyAPI>! {
return self.fulfillmentController.loggedInProvider
}

init(fulfillmentController: FulfillmentController) {
self.fulfillmentController = fulfillmentController

super.init()
}

func bidSignal(bidDetails: BidDetails) -> RACSignal {
func bidSignal() -> RACSignal {
let bidDetails = fulfillmentController.bidDetails

let saleArtwork = bidDetails.saleArtwork
let cents = String(bidDetails.bidAmountCents! as Int)
Expand All @@ -24,12 +32,27 @@ class PlaceBidNetworkModel: NSObject {
private func bidOnSaleArtwork(saleArtwork: SaleArtwork, bidAmountCents: String) -> RACSignal {
let bidEndpoint: ArtsyAPI = ArtsyAPI.PlaceABid(auctionID: saleArtwork.auctionID!, artworkID: saleArtwork.artwork.id, maxBidCents: bidAmountCents)

let request = fulfillmentController.loggedInProvider!.request(bidEndpoint).filterSuccessfulStatusCodes().mapJSON().mapToObject(BidderPosition.self)
let request = provider.request(bidEndpoint).filterSuccessfulStatusCodes().mapJSON().mapToObject(BidderPosition.self)

return request.doNext { [weak self] (position) -> Void in
self?.bidderPosition = position as? BidderPosition
return

}.`catch` { error -> RACSignal! in
// We've received an error. We're going to check to see if it's type is "param_error", which indicates we were outbid.
let data = error.userInfo["data"]

return RACSignal.`return`(data).mapJSON().tryMap{ (object, errorPointer) -> AnyObject! in
if let type = JSON(object)["type"].string where type == "param_error" {
errorPointer.memory = NSError(domain: OutbidDomain, code: 0, userInfo: [NSUnderlyingErrorKey: error])
} else {
errorPointer.memory = error
}

// Return nil, causing this signal to error again. This time, it may have a new error, though.
return nil
}

}.doError { (error) in
logger.log("Bidding on Sale Artwork failed.")
logger.log("Error: \(error.localizedDescription). \n \(error.artsyServerError())")
Expand Down
Loading