Skip to content

Commit

Permalink
Add credit card implementation (#503)
Browse files Browse the repository at this point in the history
* Email undeliverable/unverified (#478)

* Adding emailIsVerified

* Renaming GraphUserEmail

* ViewModel tests

* ChangeEmailViewController tests

* Strings, swiftlint

* Strings & new screenshots

* Cleanup

* PR comments

* PR updates

* Test naming

* Use correct function to log events in crash logs (#481)

* Use correct function to log events in crash logs

* Fix indentation

* changed ui colors for textfield text and font size of text label

* vm and work on enabling save

* wip- get stripe token

* wip- payment source mutation

* wip- keyboard response

* wip- getting a stripe error here

* wip- error fix w/ publishable key, error banner showing

* wip- saving with error and keyboard functionality

* wip- updating card immediately

* wip

* wip - ACs met

* wip - refactor in view model, made IDs testable, begane VM tests

* wip -refactor on vm/vmtest

* wip -refactor deleted comments on vmt

* wip -snapshot tests

* swiftlint fixes

* renaming/refactor, corrected paymentmethodstests

* pr feedback

* swiftlint fixes

* changed function name

* indentation
  • Loading branch information
cdolm92 committed Dec 4, 2018
1 parent 20c5d50 commit bcaf066
Show file tree
Hide file tree
Showing 41 changed files with 703 additions and 30 deletions.
2 changes: 2 additions & 0 deletions Kickstarter-iOS/Info.plist
Expand Up @@ -55,6 +55,8 @@
<array>
<dict>
<key>KitInfo</key>
<dict/>
<key>KitName</key>
<string>Crashlytics</string>
</dict>
</array>
Expand Down
147 changes: 141 additions & 6 deletions Kickstarter-iOS/Views/Controllers/AddNewCardViewController.swift
Expand Up @@ -5,12 +5,21 @@ import ReactiveSwift
import Stripe
import UIKit

internal protocol AddNewCardViewControllerDelegate: class {
func presentAddCardSuccessfulBanner(_ message: String)
}

internal final class AddNewCardViewController: UIViewController, STPPaymentCardTextFieldDelegate {
internal weak var delegate: AddNewCardViewControllerDelegate?

private weak var saveButtonView: LoadingBarButtonItemView!
@IBOutlet private weak var cardholderNameLabel: UILabel!
@IBOutlet private weak var cardholderNameTextField: UITextField!
@IBOutlet private weak var paymentField: STPPaymentCardTextField!
@IBOutlet private weak var creditCardTextField: STPPaymentCardTextField!

private var saveButtonView: LoadingBarButtonItemView!
private var messageBannerView: MessageBannerViewController!

fileprivate let viewModel: AddNewCardViewModelType = AddNewCardViewModel()

internal static func instantiate() -> AddNewCardViewController {
return Storyboard.Settings.instantiate(AddNewCardViewController.self)
Expand All @@ -19,6 +28,20 @@ internal final class AddNewCardViewController: UIViewController, STPPaymentCardT
override func viewDidLoad() {
super.viewDidLoad()

guard let messageViewController = self.children.first as? MessageBannerViewController else {
fatalError("Missing message View Controller")

}
self.messageBannerView = messageViewController

self.cardholderNameTextField.addTarget(self,
action: #selector(cardholderNameTextFieldReturn),
for: .editingDidEndOnExit)

self.cardholderNameTextField.addTarget(self,
action: #selector(cardholderNameTextFieldChanged(_:)),
for: [.editingDidEndOnExit, .editingChanged])

let cancelButton = UIBarButtonItem(title: Strings.Cancel(),
style: .plain,
target: self,
Expand All @@ -29,10 +52,13 @@ internal final class AddNewCardViewController: UIViewController, STPPaymentCardT
self.saveButtonView = LoadingBarButtonItemView.instantiate()
self.saveButtonView.setTitle(title: Strings.Save())
self.saveButtonView.addTarget(self, action: #selector(saveButtonTapped))

let navigationBarButton = UIBarButtonItem(customView: self.saveButtonView)
self.navigationItem.setRightBarButton(navigationBarButton, animated: false)

self.paymentField.delegate = self
self.creditCardTextField.delegate = self

self.viewModel.inputs.viewDidLoad()
}

override func bindStyles() {
Expand All @@ -42,29 +68,138 @@ internal final class AddNewCardViewController: UIViewController, STPPaymentCardT
|> settingsViewControllerStyle

_ = self.cardholderNameLabel
|> settingsSectionLabelStyle
|> \.textColor .~ .ksr_text_dark_grey_900
|> \.font .~ .ksr_body()
|> \.text %~ { _ in Strings.Cardholder_name() }

_ = self.cardholderNameTextField
|> formFieldStyle
|> \.autocapitalizationType .~ .words
|> \.returnKeyType .~ .next
|> \.textAlignment .~ .right
|> \.textColor .~ .ksr_text_dark_grey_500
|> \.attributedPlaceholder .~ NSAttributedString(
string: Strings.Name(),
attributes: [NSAttributedString.Key.foregroundColor: UIColor.ksr_text_dark_grey_400])

_ = self.paymentField
_ = self.creditCardTextField
|> \.borderColor .~ nil
|> \.font .~ .ksr_body()
|> \.cursorColor .~ .ksr_green_700
|> \.textColor .~ .ksr_text_dark_grey_500
|> \.placeholderColor .~ .ksr_text_dark_grey_400
}

override func bindViewModel() {
super.bindViewModel()

self.cardholderNameTextField.rac.becomeFirstResponder =
self.viewModel.outputs.cardholderNameBecomeFirstResponder

self.viewModel.outputs.paymentDetailsBecomeFirstResponder
.observeForUI()
.observeValues { [weak self] in
self?.creditCardTextField.becomeFirstResponder()
}

self.viewModel.outputs.saveButtonIsEnabled
.observeForUI()
.observeValues { [weak self] (isEnabled) in
self?.saveButtonView.setIsEnabled(isEnabled: isEnabled)
}

self.viewModel.outputs.setStripePublishableKey
.observeForUI()
.observeValues {
STPPaymentConfiguration.shared().publishableKey = $0
}

self.viewModel.outputs.dismissKeyboard
.observeForControllerAction()
.observeValues { [weak self] in
self?.creditCardTextField.resignFirstResponder()
}

self.viewModel.outputs.paymentDetails
.observeForUI()
.observeValues { [weak self] cardholderName, cardNumber, expMonth, expYear, cvc in
self?.createStripeToken(cardholderName: cardholderName,
cardNumber: cardNumber,
expirationMonth: expMonth,
expirationYear: expYear,
cvc: cvc)
}

self.viewModel.outputs.activityIndicatorShouldShow
.observeForUI()
.observeValues { shouldShow in
if shouldShow {
self.saveButtonView.startAnimating()
} else {
self.saveButtonView.stopAnimating()
}
}

self.viewModel.outputs.addNewCardSuccess
.observeForControllerAction()
.observeValues { [weak self] message in
self?.delegate?.presentAddCardSuccessfulBanner(message)
self?.navigationController?.dismiss(animated: true, completion: nil)
}

self.viewModel.outputs.addNewCardFailure
.observeForControllerAction()
.observeValues { [weak self] errorMessage in
self?.messageBannerView.showBanner(with: .error, message: errorMessage)
}
}

@objc fileprivate func cancelButtonTapped() {
self.dismiss(animated: true, completion: nil)
}

@objc fileprivate func saveButtonTapped() {
self.dismiss(animated: true, completion: nil)
self.viewModel.inputs.saveButtonTapped()
}

@objc func cardholderNameTextFieldChanged(_ textField: UITextField) {
self.viewModel.inputs.cardholderNameChanged(textField.text)
}

@objc func cardholderNameTextFieldReturn(_ textField: UITextField
) {
self.viewModel.inputs.cardholderNameTextFieldReturn()
}

internal func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) {

guard let cardnumber = textField.cardNumber, let cvc = textField.cvc else {
return
}

self.viewModel.inputs.creditCardChanged(cardNumber: cardnumber,
expMonth: Int(textField.expirationMonth),
expYear: Int(textField.expirationYear),
cvc: cvc)

self.viewModel.inputs.paymentInfo(valid: textField.isValid)
}

private func createStripeToken(cardholderName: String, cardNumber: String, expirationMonth: Int,
expirationYear: Int, cvc: String) {
let cardParams = STPCardParams()
cardParams.name = cardholderName
cardParams.number = cardNumber
cardParams.expMonth = UInt(expirationMonth)
cardParams.expYear = UInt(expirationYear)
cardParams.cvc = cvc

STPAPIClient.shared().createToken(withCard: cardParams) { token, error in
if let token = token {
self.viewModel.inputs.stripeCreated(token.tokenId, stripeID: token.stripeID)
} else {
self.viewModel.inputs.stripeError(error)
}
}
}
}
Expand Up @@ -11,13 +11,21 @@ internal final class PaymentMethodsViewController: UIViewController {
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var tableView: UITableView!

private var messageBannerView: MessageBannerViewController!

public static func instantiate() -> PaymentMethodsViewController {
return Storyboard.Settings.instantiate(PaymentMethodsViewController.self)
}

override func viewDidLoad() {
super.viewDidLoad()

guard let messageViewController = self.children.first as? MessageBannerViewController else {
fatalError("Missing message View Controller")

}
self.messageBannerView = messageViewController

self.tableView.dataSource = self.dataSource
self.tableView.delegate = self
self.tableView.register(nib: .CreditCardCell)
Expand All @@ -36,6 +44,12 @@ internal final class PaymentMethodsViewController: UIViewController {
}
}

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

self.viewModel.inputs.viewDidAppear()
}

override func bindStyles() {
super.bindStyles()

Expand Down Expand Up @@ -74,6 +88,12 @@ internal final class PaymentMethodsViewController: UIViewController {
self?.goToAddCardScreen()
}

self.viewModel.outputs.presentBanner
.observeForUI()
.observeValues { [weak self] message in
self?.messageBannerView.showBanner(with: .success, message: message)
}

self.viewModel.outputs.tableViewIsEditing
.observeForUI()
.observeValues { [weak self] isEditing in
Expand All @@ -96,6 +116,7 @@ internal final class PaymentMethodsViewController: UIViewController {

private func goToAddCardScreen() {
let vc = AddNewCardViewController.instantiate()
vc.delegate = self
let nav = UINavigationController(rootViewController: vc)
nav.modalPresentationStyle = .formSheet

Expand Down Expand Up @@ -123,8 +144,13 @@ extension PaymentMethodsViewController: UITableViewDelegate {
}

extension PaymentMethodsViewController: PaymentMethodsFooterViewDelegate {

internal func paymentMethodsFooterViewDidTapAddNewCardButton(_ footerView: PaymentMethodsFooterView) {
self.viewModel.inputs.paymentMethodsFooterViewDidTapAddNewCardButton()
}
}

extension PaymentMethodsViewController: AddNewCardViewControllerDelegate {
internal func presentAddCardSuccessfulBanner(_ message: String) {
self.viewModel.inputs.cardAddedSuccessfully(message)
}
}

0 comments on commit bcaf066

Please sign in to comment.