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
Changes from 20 commits
e3cd226
e9a1005
44ff603
cc49f7c
01083af
1859e8c
6f9dd6f
b066666
8e98ec6
3237915
06cafc2
654102a
98d07de
a6144aa
fb3b702
0a221f0
aff4384
56a0df0
1759300
a1dbe87
40ecf69
84a4253
8883d58
66030fd
9cd3977
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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! | ||
|
||
private var saveButtonView: LoadingBarButtonItemView! | ||
private var messageBannerView: MessageBannerViewController! | ||
|
||
fileprivate let viewModel: AddNewCardViewModelType = AddNewCardViewModel() | ||
|
||
internal static func instantiate() -> AddNewCardViewController { | ||
return Storyboard.Settings.instantiate(AddNewCardViewController.self) | ||
|
@@ -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, | ||
|
@@ -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() { | ||
|
@@ -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 ?? "") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here I would make |
||
} | ||
|
||
@objc func cardholderNameTextFieldReturn(_ textField: UITextField | ||
) { | ||
self.viewModel.inputs.cardholderNameTextFieldReturn() | ||
} | ||
|
||
func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For consistency, could you add |
||
|
||
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you make this func |
||
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) | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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 tocreditCardTextField
instead.