Skip to content

Commit

Permalink
[NT-262] Remember this card added to mutation (#856)
Browse files Browse the repository at this point in the history
* Ensure initial card selected notifies delegate

* Add reusable param to CreatePaymentSourceInput

* Don't be silly

* Switch off by default

* Combine delegate methods, update tests, move constructor to extension

* Put dismissal back

* Use .values()
  • Loading branch information
justinswart authored Sep 30, 2019
1 parent 57ddae9 commit 39dc6dd
Show file tree
Hide file tree
Showing 36 changed files with 186 additions and 77 deletions.
37 changes: 18 additions & 19 deletions Kickstarter-iOS/Views/Controllers/AddNewCardViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@ import UIKit
internal protocol AddNewCardViewControllerDelegate: AnyObject {
func addNewCardViewController(
_ viewController: AddNewCardViewController,
didSucceedWithMessage message: String
didAdd newCard: GraphUserCreditCard.CreditCard,
withMessage message: String
)
func addNewCardViewControllerDismissed(_ viewController: AddNewCardViewController)
func addNewCardViewController(
_ viewController: AddNewCardViewController,
_ newCard: GraphUserCreditCard.CreditCard
)
}

internal final class AddNewCardViewController: UIViewController,
Expand Down Expand Up @@ -202,11 +199,11 @@ internal final class AddNewCardViewController: UIViewController,
STPPaymentConfiguration.shared().publishableKey = $0
}

self.viewModel.outputs.newCardAdded
self.viewModel.outputs.newCardAddedWithMessage
.observeForUI()
.observeValues { [weak self] newCard in
guard let _self = self else { return }
_self.delegate?.addNewCardViewController(_self, newCard)
.observeValues { [weak self] newCard, message in
guard let self = self else { return }
self.delegate?.addNewCardViewController(self, didAdd: newCard, withMessage: message)
}

self.viewModel.outputs.dismissKeyboard
Expand All @@ -231,12 +228,6 @@ internal final class AddNewCardViewController: UIViewController,
}
}

self.viewModel.outputs.addNewCardSuccess
.observeForControllerAction()
.observeValues { [weak self] message in
self?.dismissAndPresentMessageBanner(with: message)
}

self.viewModel.outputs.addNewCardFailure
.observeForControllerAction()
.observeValues { [weak self] errorMessage in
Expand Down Expand Up @@ -316,6 +307,12 @@ internal final class AddNewCardViewController: UIViewController,
self.rememberThisCardToggleViewControllerContainer.heightAnchor
.constraint(greaterThanOrEqualToConstant: Styles.minTouchSize.height)
.isActive = true

self.rememberThisCardToggleViewController.toggle.addTarget(
self,
action: #selector(AddNewCardViewController.rememberThisCardToggled(_:)),
for: .valueChanged
)
}

private func createStripeToken(with paymentDetails: PaymentDetails) {
Expand All @@ -340,15 +337,17 @@ internal final class AddNewCardViewController: UIViewController,
return self.supportedCardBrands.contains(brand)
}

private func dismissAndPresentMessageBanner(with message: String) {
self.delegate?.addNewCardViewController(self, didSucceedWithMessage: message)
}

private func dismissKeyboard() {
[self.cardholderNameTextField, self.creditCardTextField, self.zipcodeView.textField]
.forEach { $0?.resignFirstResponder() }
}

// MARK: - Actions

@objc private func rememberThisCardToggled(_ sender: UISwitch) {
self.viewModel.inputs.rememberThisCardToggleChanged(to: sender.isOn)
}

// MARK: - Subviews

private lazy var rememberThisCardToggleViewControllerContainer: UIView = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,14 @@ extension PaymentMethodsViewController: PaymentMethodsFooterViewDelegate {
extension PaymentMethodsViewController: AddNewCardViewControllerDelegate {
func addNewCardViewController(
_: AddNewCardViewController,
didSucceedWithMessage message: String
didAdd _: GraphUserCreditCard.CreditCard,
withMessage message: String
) {
self.dismiss(animated: true) {
self.viewModel.inputs.addNewCardSucceeded(with: message)
}
}

func addNewCardViewController(_: AddNewCardViewController, _: GraphUserCreditCard.CreditCard) {}

func addNewCardViewControllerDismissed(_: AddNewCardViewController) {
self.dismiss(animated: true) {
self.viewModel.inputs.addNewCardDismissed()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,17 @@ extension PledgePaymentMethodsViewController: PledgeAddNewCardViewDelegate {
}

extension PledgePaymentMethodsViewController: AddNewCardViewControllerDelegate {
func addNewCardViewController(_: AddNewCardViewController, _ newCard: GraphUserCreditCard.CreditCard) {
self.viewModel.inputs.addNewCardViewControllerDidAdd(newCard: newCard)
func addNewCardViewController(
_: AddNewCardViewController,
didAdd newCard: GraphUserCreditCard.CreditCard,
withMessage _: String
) {
self.dismiss(animated: true) {
self.viewModel.inputs.addNewCardViewControllerDidAdd(newCard: newCard)
}
}

func addNewCardViewControllerDismissed(_: AddNewCardViewController) {
self.dismiss(animated: true)
}

func addNewCardViewController(_: AddNewCardViewController, didSucceedWithMessage _: String) {
self.dismiss(animated: true)
}
}
8 changes: 8 additions & 0 deletions Kickstarter.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,8 @@
8A8099FF22E21F9700373E66 /* PledgeDescriptionViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8099FE22E21F9700373E66 /* PledgeDescriptionViewControllerTests.swift */; };
8AAA9BD822F49EC200F12976 /* UIColor+Mixing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AAA9BD722F49EC200F12976 /* UIColor+Mixing.swift */; };
8AF91CDD22EF5AF8005F9C90 /* Feature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF91CDC22EF5AF7005F9C90 /* Feature.swift */; };
8AFB8C97233E9977006779B5 /* CreatePaymentSourceInput+Constructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFB8C96233E9977006779B5 /* CreatePaymentSourceInput+Constructor.swift */; };
8AFB8C99233E9A1F006779B5 /* CreatePaymentSourceInput+ConstructorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFB8C98233E9A1F006779B5 /* CreatePaymentSourceInput+ConstructorTests.swift */; };
9D0FB7751D7600B5005774F2 /* CheckoutRacingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D0FB7741D7600B5005774F2 /* CheckoutRacingViewModel.swift */; };
9D10B91B1D35407C008B8045 /* String+Truncate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D10B91A1D35407C008B8045 /* String+Truncate.swift */; };
9D14FF8D1D133351005F4ABB /* ProjectActivityBackingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D9F57CB1D131AF200CE81DE /* ProjectActivityBackingCell.swift */; };
Expand Down Expand Up @@ -1675,6 +1677,8 @@
8A8099FE22E21F9700373E66 /* PledgeDescriptionViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PledgeDescriptionViewControllerTests.swift; sourceTree = "<group>"; };
8AAA9BD722F49EC200F12976 /* UIColor+Mixing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Mixing.swift"; sourceTree = "<group>"; };
8AF91CDC22EF5AF7005F9C90 /* Feature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Feature.swift; sourceTree = "<group>"; };
8AFB8C96233E9977006779B5 /* CreatePaymentSourceInput+Constructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CreatePaymentSourceInput+Constructor.swift"; sourceTree = "<group>"; };
8AFB8C98233E9A1F006779B5 /* CreatePaymentSourceInput+ConstructorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CreatePaymentSourceInput+ConstructorTests.swift"; sourceTree = "<group>"; };
9D0FB7741D7600B5005774F2 /* CheckoutRacingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckoutRacingViewModel.swift; sourceTree = "<group>"; };
9D10B91A1D35407C008B8045 /* String+Truncate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Truncate.swift"; sourceTree = "<group>"; };
9D14FFC51D135C12005F4ABB /* ProjectActivityUpdateCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProjectActivityUpdateCellViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3078,6 +3082,8 @@
A7D121081D15B08200F364FD /* CountBadgeView.swift */,
77BC00F1232BE49E00808E75 /* CreateApplePayBackingInput+Constructor.swift */,
77BC00F3232BE52C00808E75 /* CreateApplePayBackingInputConstructorTests.swift */,
8AFB8C96233E9977006779B5 /* CreatePaymentSourceInput+Constructor.swift */,
8AFB8C98233E9A1F006779B5 /* CreatePaymentSourceInput+ConstructorTests.swift */,
77AA2B39233C09F5008BBCB8 /* CreateBackingInput+Constructor.swift */,
77AA2B3C233D10D3008BBCB8 /* CreateBackingConstructorTests.swift */,
D6534D3922E7878D00E9D279 /* CreditCard+Utils.swift */,
Expand Down Expand Up @@ -4631,6 +4637,7 @@
A7F441E11D005A9400FE6FC5 /* ResetPasswordViewModel.swift in Sources */,
A734A2671D21A1790080BBD5 /* WKNavigationActionData.swift in Sources */,
A7C93FB61D44142900C2DF9B /* ProjectDescriptionViewModel.swift in Sources */,
8AFB8C97233E9977006779B5 /* CreatePaymentSourceInput+Constructor.swift in Sources */,
9D8772131D19E84E003A4E96 /* ProjectActivityLaunchCellViewModel.swift in Sources */,
774F8D5D22B1B14100A1ACD5 /* FeatureFlagToolsViewModel.swift in Sources */,
A7F441CF1D005A9400FE6FC5 /* MessageThreadCellViewModel.swift in Sources */,
Expand Down Expand Up @@ -4820,6 +4827,7 @@
A7ED1FD91E831C5C00BFFA01 /* ProjectActivitiesViewModelTests.swift in Sources */,
A7ED1FDB1E831C5C00BFFA01 /* DashboardViewModelTests.swift in Sources */,
3706408822A8A6F200889CBD /* PledgeShippingLocationViewModelTests.swift in Sources */,
8AFB8C99233E9A1F006779B5 /* CreatePaymentSourceInput+ConstructorTests.swift in Sources */,
A7ED1F2B1E830FDC00BFFA01 /* IsValidEmailTests.swift in Sources */,
A7ED1FEB1E831C5C00BFFA01 /* DashboardReferrersCellViewModelTests.swift in Sources */,
37C7B81723187BAC00C78278 /* ShippingRuleCellViewModelTests.swift in Sources */,
Expand Down
11 changes: 7 additions & 4 deletions KsApi/mutations/inputs/CreatePaymentSourceInput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@ import Foundation

public struct CreatePaymentSourceInput: GraphMutationInput {
let paymentType: PaymentType
let reusable: Bool
let stripeToken: String
let stripeCardId: String

public init(paymentType: PaymentType, stripeToken: String, stripeCardId: String) {
public init(paymentType: PaymentType, reusable: Bool, stripeToken: String, stripeCardId: String) {
self.paymentType = paymentType
self.reusable = reusable
self.stripeToken = stripeToken
self.stripeCardId = stripeCardId
}

public func toInputDictionary() -> [String: Any] {
return [
"paymentType": paymentType.rawValue,
"stripeToken": stripeToken,
"stripeCardId": stripeCardId
"paymentType": self.paymentType.rawValue,
"reusable": self.reusable,
"stripeToken": self.stripeToken,
"stripeCardId": self.stripeCardId
]
}
}
17 changes: 17 additions & 0 deletions Library/CreatePaymentSourceInput+Constructor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Foundation
import KsApi

extension CreatePaymentSourceInput {
internal static func input(
fromToken token: String,
stripeCardId: String,
reusable: Bool
) -> CreatePaymentSourceInput {
return CreatePaymentSourceInput(
paymentType: PaymentType.creditCard,
reusable: reusable,
stripeToken: token,
stripeCardId: stripeCardId
)
}
}
33 changes: 33 additions & 0 deletions Library/CreatePaymentSourceInput+ConstructorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation
@testable import KsApi
@testable import Library
import Prelude
import XCTest

final class CreatePaymentSourceInput_ConstructorTests: TestCase {
func testCreatePaymentSourceInput_Reusable() {
let input = CreatePaymentSourceInput.input(
fromToken: "token",
stripeCardId: "cardId",
reusable: true
)

XCTAssertEqual(input.paymentType, PaymentType.creditCard)
XCTAssertEqual(input.stripeToken, "token")
XCTAssertEqual(input.stripeCardId, "cardId")
XCTAssertEqual(input.reusable, true)
}

func testCreatePaymentSourceInput_SingleUse() {
let input = CreatePaymentSourceInput.input(
fromToken: "token",
stripeCardId: "cardId",
reusable: false
)

XCTAssertEqual(input.paymentType, PaymentType.creditCard)
XCTAssertEqual(input.stripeToken, "token")
XCTAssertEqual(input.stripeCardId, "cardId")
XCTAssertEqual(input.reusable, false)
}
}
79 changes: 45 additions & 34 deletions Library/ViewModels/AddNewCardViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public protocol AddNewCardViewModelInputs {
func configure(with intent: AddNewCardIntent)
func paymentCardTextFieldDidEndEditing()
func paymentInfo(isValid: Bool)
func rememberThisCardToggleChanged(to value: Bool)
func saveButtonTapped()
func stripeCreated(_ token: String?, stripeID: String?)
func stripeError(_ error: Error?)
Expand All @@ -31,11 +32,10 @@ public protocol AddNewCardViewModelInputs {
public protocol AddNewCardViewModelOutputs {
var activityIndicatorShouldShow: Signal<Bool, Never> { get }
var addNewCardFailure: Signal<String, Never> { get }
var addNewCardSuccess: Signal<String, Never> { get }
var creditCardValidationErrorContainerHidden: Signal<Bool, Never> { get }
var cardholderNameBecomeFirstResponder: Signal<Void, Never> { get }
var dismissKeyboard: Signal<Void, Never> { get }
var newCardAdded: Signal<GraphUserCreditCard.CreditCard, Never> { get }
var newCardAddedWithMessage: Signal<(GraphUserCreditCard.CreditCard, String), Never> { get }
var paymentDetails: Signal<PaymentDetails, Never> { get }
var paymentDetailsBecomeFirstResponder: Signal<Void, Never> { get }
var rememberThisCardToggleViewControllerContainerIsHidden: Signal<Bool, Never> { get }
Expand Down Expand Up @@ -109,11 +109,13 @@ public final class AddNewCardViewModel: AddNewCardViewModelType, AddNewCardViewM
)
}

let saveButtonTappedOrZipCodeEditingEnded = Signal.merge(
self.saveButtonTappedProperty.signal,
self.zipcodeTextFieldDidEndEditingProperty.signal
)

let submitPaymentDetails = self.saveButtonIsEnabled
.takeWhen(Signal.merge(
self.saveButtonTappedProperty.signal,
self.zipcodeTextFieldDidEndEditingProperty.signal
))
.takeWhen(saveButtonTappedOrZipCodeEditingEnded)
.filter(isTrue)

self.paymentDetails = paymentInput.takeWhen(submitPaymentDetails)
Expand All @@ -123,19 +125,34 @@ public final class AddNewCardViewModel: AddNewCardViewModelType, AddNewCardViewM
self.setStripePublishableKey = self.viewDidLoadProperty.signal
.map { _ in AppEnvironment.current.environmentType.stripePublishableKey }

let addNewCardEvent = self.stripeTokenProperty.signal.skipNil()
.map { CreatePaymentSourceInput(
paymentType: PaymentType.creditCard,
stripeToken: $0.0, stripeCardId: $0.1
) }
.switchMap {
AppEnvironment.current.apiService.addNewCreditCard(input: $0)
.ksr_delay(AppEnvironment.current.apiDelayInterval, on: AppEnvironment.current.scheduler)
.map { (envelope: CreatePaymentSourceEnvelope) in envelope.createPaymentSource }
.materialize()
}
self.rememberThisCardToggleViewControllerIsOn = Signal.combineLatest(
self.addNewCardIntentProperty.signal,
self.viewDidLoadProperty.signal
)
.map(first)
.map { $0 == .settings }

let rememberThisCard = Signal.merge(
self.rememberThisCardToggleViewControllerIsOn,
self.rememberThisCardToggleChangedToValue.signal
)

self.newCardAdded = addNewCardEvent.map { $0.value?.paymentSource }.skipNil()
let addNewCardEvent = Signal.combineLatest(
self.stripeTokenProperty.signal.skipNil(),
rememberThisCard
)
.map(unpack)
.map(CreatePaymentSourceInput.input(fromToken:stripeCardId:reusable:))
.switchMap {
AppEnvironment.current.apiService.addNewCreditCard(input: $0)
.ksr_delay(AppEnvironment.current.apiDelayInterval, on: AppEnvironment.current.scheduler)
.map { (envelope: CreatePaymentSourceEnvelope) in envelope.createPaymentSource }
.materialize()
}

self.newCardAddedWithMessage = addNewCardEvent.values()
.map { $0.paymentSource }
.map { card in (card, Strings.Got_it_your_changes_have_been_saved()) }

let stripeInvalidToken = self.stripeErrorProperty.signal.map {
$0?.localizedDescription
Expand All @@ -153,16 +170,9 @@ public final class AddNewCardViewModel: AddNewCardViewModelType, AddNewCardViewM

self.addNewCardFailure = errorMessage.map { $0 }

let cardAddedSuccessfully = addNewCardEvent
.filter { $0.value?.isSuccessful == true }
.mapConst(true)

self.addNewCardSuccess = cardAddedSuccessfully
.map { _ in Strings.Got_it_your_changes_have_been_saved() }

self.activityIndicatorShouldShow = Signal.merge(
submitPaymentDetails.mapConst(true),
self.addNewCardSuccess.mapConst(false),
self.newCardAddedWithMessage.mapConst(false),
self.addNewCardFailure.mapConst(false)
)

Expand All @@ -172,16 +182,13 @@ public final class AddNewCardViewModel: AddNewCardViewModelType, AddNewCardViewM
)
.map(first)

self.rememberThisCardToggleViewControllerIsOn = self.viewDidLoadProperty.signal
.mapConst(true)

// Koala
self.viewWillAppearProperty.signal
.observeValues {
AppEnvironment.current.koala.trackViewedAddNewCard()
}

self.addNewCardSuccess
self.newCardAddedWithMessage
.observeValues { _ in
AppEnvironment.current.koala.trackSavedPaymentMethod()
}
Expand Down Expand Up @@ -227,6 +234,11 @@ public final class AddNewCardViewModel: AddNewCardViewModelType, AddNewCardViewM
self.paymentInfoIsValidProperty.value = isValid
}

private let rememberThisCardToggleChangedToValue = MutableProperty(false)
public func rememberThisCardToggleChanged(to value: Bool) {
self.rememberThisCardToggleChangedToValue.value = value
}

private let saveButtonTappedProperty = MutableProperty(())
public func saveButtonTapped() {
self.saveButtonTappedProperty.value = ()
Expand Down Expand Up @@ -266,15 +278,14 @@ public final class AddNewCardViewModel: AddNewCardViewModelType, AddNewCardViewM

public let activityIndicatorShouldShow: Signal<Bool, Never>
public let addNewCardFailure: Signal<String, Never>
public let addNewCardSuccess: Signal<String, Never>
public let creditCardValidationErrorContainerHidden: Signal<Bool, Never>
public let cardholderNameBecomeFirstResponder: Signal<Void, Never>
public let dismissKeyboard: Signal<Void, Never>
public let newCardAdded: Signal<GraphUserCreditCard.CreditCard, Never>
public let newCardAddedWithMessage: Signal<(GraphUserCreditCard.CreditCard, String), Never>
public let paymentDetails: Signal<PaymentDetails, Never>
public let paymentDetailsBecomeFirstResponder: Signal<Void, Never>
public var rememberThisCardToggleViewControllerContainerIsHidden: Signal<Bool, Never>
public var rememberThisCardToggleViewControllerIsOn: Signal<Bool, Never>
public let rememberThisCardToggleViewControllerContainerIsHidden: Signal<Bool, Never>
public let rememberThisCardToggleViewControllerIsOn: Signal<Bool, Never>
public let saveButtonIsEnabled: Signal<Bool, Never>
public let setStripePublishableKey: Signal<String, Never>
public let zipcodeTextFieldBecomeFirstResponder: Signal<Void, Never>
Expand Down
Loading

0 comments on commit 39dc6dd

Please sign in to comment.