Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add credit card implementation #503

Merged
merged 25 commits into from Dec 4, 2018
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e3cd226
Email undeliverable/unverified (#478)
ifbarrera Nov 12, 2018
e9a1005
Use correct function to log events in crash logs (#481)
justinswart Nov 14, 2018
44ff603
Merge remote-tracking branch 'oss/feature-payment-methods' into add-c…
cdolm92 Nov 14, 2018
cc49f7c
changed ui colors for textfield text and font size of text label
cdolm92 Nov 14, 2018
01083af
vm and work on enabling save
cdolm92 Nov 15, 2018
1859e8c
wip- get stripe token
cdolm92 Nov 20, 2018
6f9dd6f
wip- payment source mutation
cdolm92 Nov 26, 2018
b066666
wip- keyboard response
cdolm92 Nov 27, 2018
8e98ec6
wip- getting a stripe error here
cdolm92 Nov 27, 2018
3237915
wip- error fix w/ publishable key, error banner showing
cdolm92 Nov 28, 2018
06cafc2
wip- saving with error and keyboard functionality
cdolm92 Nov 29, 2018
654102a
wip- updating card immediately
cdolm92 Nov 29, 2018
98d07de
wip
cdolm92 Nov 29, 2018
a6144aa
Merge remote-tracking branch 'oss/feature-payment-methods' into add-c…
cdolm92 Nov 29, 2018
fb3b702
wip - ACs met
cdolm92 Nov 29, 2018
0a221f0
wip - refactor in view model, made IDs testable, begane VM tests
cdolm92 Nov 30, 2018
aff4384
wip -refactor on vm/vmtest
cdolm92 Nov 30, 2018
56a0df0
wip -refactor deleted comments on vmt
cdolm92 Nov 30, 2018
1759300
wip -snapshot tests
cdolm92 Nov 30, 2018
a1dbe87
swiftlint fixes
cdolm92 Nov 30, 2018
40ecf69
renaming/refactor, corrected paymentmethodstests
cdolm92 Dec 3, 2018
84a4253
pr feedback
cdolm92 Dec 4, 2018
8883d58
swiftlint fixes
cdolm92 Dec 4, 2018
66030fd
changed function name
cdolm92 Dec 4, 2018
9cd3977
indentation
cdolm92 Dec 4, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
150 changes: 144 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 paymentTextField: STPPaymentCardTextField!
Copy link
Contributor Author

Choose a reason for hiding this comment

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

paymentTextField sounds too general here and it's obvious that fields here pertain to credit card information so going to rename this to creditCardTextField instead.


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.paymentTextField.delegate = self

self.viewModel.inputs.viewDidLoad()
}

override func bindStyles() {
Expand All @@ -42,29 +68,141 @@ 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.paymentTextField
|> \.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?.paymentTextField.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?.paymentTextField.resignFirstResponder()
}

self.viewModel.outputs.paymentDetails
.observeForUI()
.observeValues { [weak self] cardholderName, cardNumber, expMonth, expYear, cvc in
self?.stripeToken(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 ?? "")
Copy link
Contributor

Choose a reason for hiding this comment

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

Here I would make cardholderName optional and simply pass textField.text. This way make the viewModel responsible for taking care of this logic.

}

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

func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) {
Copy link
Contributor

Choose a reason for hiding this comment

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

For consistency, could you add internal keyword here?


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

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

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

func stripeToken(cardholderName: String,
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you make this func private? Also, I would rename it. It's not clear of what's the function about until we see its implementation. Here I would rename it to createStripeToken(...

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)
}
}