Skip to content

Commit

Permalink
Show initial blank vertical view controller in vertical mode for Flow…
Browse files Browse the repository at this point in the history
…Controller (stripe#3563)

## Summary
- Make `FlowControllerViewControllerDelegate` which abstracts out FC's
view controller. Both horizontal & vertical VCs conform to it.
- Adds `PaymentSheetVerticalViewController`, barebones implementation.

PaymentSheet to follow

## Testing
Manually tested for now.
  • Loading branch information
yuki-stripe authored May 6, 2024
1 parent a45b584 commit ec6f349
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@
B306EA3F66D07CCABF17CB9C /* LinkInlineSignupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7910B57E6FD99F2AFCA4DAC2 /* LinkInlineSignupViewModel.swift */; };
B4679C9095BCD53CCC2C7D25 /* StripeCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E41AA4E90E5BB28D588FDE51 /* StripeCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B55EFA2557B5BE39CC12E357 /* STPPaymentMethod+PaymentSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 813E88EE408666654EF835E2 /* STPPaymentMethod+PaymentSheet.swift */; };
B6859A882BE54CD30018E06C /* PaymentSheetVerticalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6859A872BE54CD30018E06C /* PaymentSheetVerticalViewController.swift */; };
B68CB9632B0D2169006ACDB1 /* STPAPIClient+PaymentSheetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68CB9622B0D2169006ACDB1 /* STPAPIClient+PaymentSheetTest.swift */; };
B6B3481CBA798CF22EE8411A /* TextFieldElement+IBAN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 570931B897DCCAC0F55FB6E3 /* TextFieldElement+IBAN.swift */; };
B8A217F26AAEC592B9B0D2E1 /* CardScanButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24AB1D50F770A8A035EF31DF /* CardScanButton.swift */; };
Expand Down Expand Up @@ -524,6 +525,7 @@
B54274B9DEB3F1B0906127D1 /* et-EE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "et-EE"; path = "et-EE.lproj/Localizable.strings"; sourceTree = "<group>"; };
B61FFE76D0960C7F1E34B405 /* PaymentSheetAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetAppearance.swift; sourceTree = "<group>"; };
B667E074D30964FABC64B552 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
B6859A872BE54CD30018E06C /* PaymentSheetVerticalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetVerticalViewController.swift; sourceTree = "<group>"; };
B68CB9622B0D2169006ACDB1 /* STPAPIClient+PaymentSheetTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+PaymentSheetTest.swift"; sourceTree = "<group>"; };
B70D161E50723A8953665C4B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
B829BC7EEE9576F724F7A9B3 /* StripePaymentSheetTestHostApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StripePaymentSheetTestHostApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -864,6 +866,7 @@
6BA8D3352B0C1F9B008C51FF /* CVCReconfirmationViewController.swift */,
51FF291A25EA43D4D100983B /* LoadingViewController.swift */,
107DAAB4492531735377AA7C /* PaymentSheetFlowControllerViewController.swift */,
B6859A872BE54CD30018E06C /* PaymentSheetVerticalViewController.swift */,
0CACEA8571301D469FF07741 /* PaymentSheetViewController.swift */,
101DFBD8D19B7B182CDD8882 /* PollingViewController.swift */,
26C092533C67D8C7FDE12742 /* PollingViewModel.swift */,
Expand Down Expand Up @@ -1624,6 +1627,7 @@
40806EF506CB719299FC90CC /* STPLocalizedString.swift in Sources */,
71132CE036C3EE0655ECD2DB /* STPStringUtils.swift in Sources */,
1AF3BBA86D643AAF26CD0E2B /* StripePaymentSheet+Exports.swift in Sources */,
B6859A882BE54CD30018E06C /* PaymentSheetVerticalViewController.swift in Sources */,
73F3E8DCF2314972A162B2A3 /* StripePaymentSheetBundleLocator.swift in Sources */,
73EE441CF71707651109CE19 /* ConsumerSession+LookupResponse.swift in Sources */,
311AC53D6C76953E9B70148A /* ConsumerSession+PublishableKey.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,11 @@ extension PaymentSheet {
}

// MARK: - Private properties

private var intent: Intent {
var intent: Intent {
return viewController.intent
}
lazy var paymentHandler: STPPaymentHandler = { STPPaymentHandler(apiClient: configuration.apiClient, formSpecPaymentHandler: PaymentSheetFormSpecPaymentHandler()) }()
var viewController: PaymentSheetFlowControllerViewController
var viewController: FlowControllerViewController
private var presentPaymentOptionsCompletion: (() -> Void)?

/// The desired, valid (ie passed client-side checks) payment option from the underlying payment options VC.
Expand Down Expand Up @@ -453,39 +452,41 @@ extension PaymentSheet {
configuration: Configuration,
loadResult: PaymentSheetLoader.LoadResult,
previousPaymentOption: PaymentOption? = nil
) -> PaymentSheetFlowControllerViewController {
let vc = PaymentSheetFlowControllerViewController(
configuration: configuration,
loadResult: loadResult,
previousPaymentOption: previousPaymentOption
)
configuration.style.configure(vc)
return vc
) -> FlowControllerViewController {
switch configuration.appearance.layout {
case .horizontal:
return PaymentSheetFlowControllerViewController(
configuration: configuration,
loadResult: loadResult,
previousPaymentOption: previousPaymentOption
)
case .vertical:
return PaymentSheetVerticalViewController(configuration: configuration, loadResult: loadResult)
}
}
}
}

// MARK: - FlowControllerViewControllerDelegate

protocol FlowControllerViewControllerDelegate: AnyObject {
func flowControllerViewControllerShouldClose(
_ PaymentSheetFlowControllerViewController: FlowControllerViewController, didCancel: Bool)
}

// MARK: - PaymentSheetFlowControllerViewControllerDelegate
extension PaymentSheet.FlowController: PaymentSheetFlowControllerViewControllerDelegate {
func paymentSheetFlowControllerViewControllerShouldClose(
_ PaymentSheetFlowControllerViewController: PaymentSheetFlowControllerViewController,
extension PaymentSheet.FlowController: FlowControllerViewControllerDelegate {
func flowControllerViewControllerShouldClose(
_ flowControllerViewController: FlowControllerViewController,
didCancel: Bool
) {
if !didCancel {
self.didPresentAndContinue = true
}
PaymentSheetFlowControllerViewController.dismiss(animated: true) {
flowControllerViewController.dismiss(animated: true) {
self.presentPaymentOptionsCompletion?()
self.isPresented = false
}
}

func paymentSheetFlowControllerViewControllerDidUpdateSelection(
_ PaymentSheetFlowControllerViewController: PaymentSheetFlowControllerViewController
) {
// no-op
}
}

// MARK: - STPAnalyticsProtocol
Expand Down Expand Up @@ -525,3 +526,15 @@ class AuthenticationContext: NSObject, PaymentSheetAuthenticationContext {
return presentingViewController
}
}

// MARK: - FlowControllerViewController

/// All the things FlowController needs from its UIViewController.
internal protocol FlowControllerViewController: BottomSheetContentViewController {
var error: Error? { get }
var intent: Intent { get }
var selectedPaymentOption: PaymentOption? { get }
// TODO: This should be nullable instead of vending .unknown
var selectedPaymentMethodType: PaymentSheet.PaymentMethodType { get }
var delegate: FlowControllerViewControllerDelegate? { get set }
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,8 @@ import Foundation
@_spi(STP) import StripeUICore
import UIKit

protocol PaymentSheetFlowControllerViewControllerDelegate: AnyObject {
func paymentSheetFlowControllerViewControllerShouldClose(
_ PaymentSheetFlowControllerViewController: PaymentSheetFlowControllerViewController, didCancel: Bool)
func paymentSheetFlowControllerViewControllerDidUpdateSelection(
_ PaymentSheetFlowControllerViewController: PaymentSheetFlowControllerViewController)
}

/// For internal SDK use only
@objc(STP_Internal_PaymentSheetFlowControllerViewController)
class PaymentSheetFlowControllerViewController: UIViewController {
class PaymentSheetFlowControllerViewController: UIViewController, FlowControllerViewController {
// MARK: - Internal Properties
let intent: Intent
let configuration: PaymentSheet.Configuration
Expand Down Expand Up @@ -65,7 +57,7 @@ class PaymentSheetFlowControllerViewController: UIViewController {
return addPaymentMethodViewController.selectedPaymentMethodType
}
}
weak var delegate: PaymentSheetFlowControllerViewControllerDelegate?
weak var delegate: FlowControllerViewControllerDelegate?
lazy var navigationBar: SheetNavigationBar = {
let navBar = SheetNavigationBar(isTestMode: configuration.apiClient.isTestmode,
appearance: configuration.appearance)
Expand Down Expand Up @@ -242,6 +234,7 @@ class PaymentSheetFlowControllerViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = configuration.appearance.colors.background
configuration.style.configure(self)

// One stack view contains all our subviews
let stackView = UIStackView(arrangedSubviews: [
Expand Down Expand Up @@ -474,12 +467,12 @@ class PaymentSheetFlowControllerViewController: UIViewController {
STPAnalyticsClient.sharedClient.logPaymentSheetConfirmButtonTapped(paymentMethodTypeIdentifier: selectedPaymentMethodType.identifier)
switch mode {
case .selectingSaved:
self.delegate?.paymentSheetFlowControllerViewControllerShouldClose(self, didCancel: false)
self.delegate?.flowControllerViewControllerShouldClose(self, didCancel: false)
case .addingNew:
if let buyButtonOverrideBehavior = addPaymentMethodViewController.overrideBuyButtonBehavior {
addPaymentMethodViewController.didTapCallToActionButton(behavior: buyButtonOverrideBehavior, from: self)
} else {
self.delegate?.paymentSheetFlowControllerViewControllerShouldClose(self, didCancel: false)
self.delegate?.flowControllerViewControllerShouldClose(self, didCancel: false)
}
}

Expand All @@ -489,7 +482,7 @@ class PaymentSheetFlowControllerViewController: UIViewController {
// When we close the window, unset the hacky Link button. This will reset the PaymentOption to nil, if needed.
isHackyLinkButtonSelected = false
// If the customer was adding a new payment method and it's incomplete/invalid, return to the saved PM screen
delegate?.paymentSheetFlowControllerViewControllerShouldClose(self, didCancel: didCancel)
delegate?.flowControllerViewControllerShouldClose(self, didCancel: didCancel)
if savedPaymentOptionsViewController.isRemovingPaymentMethods {
savedPaymentOptionsViewController.isRemovingPaymentMethods = false
configureEditSavedPaymentMethodsButton()
Expand Down Expand Up @@ -567,10 +560,9 @@ extension PaymentSheetFlowControllerViewController: SavedPaymentOptionsViewContr
error = nil // Clear any errors
updateUI()
case .applePay, .link, .saved:
delegate?.paymentSheetFlowControllerViewControllerDidUpdateSelection(self)
updateUI()
if isDismissable, !selectedPaymentMethodType.requiresMandateDisplayForSavedSelection {
delegate?.paymentSheetFlowControllerViewControllerShouldClose(self, didCancel: false)
delegate?.flowControllerViewControllerShouldClose(self, didCancel: false)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// PaymentSheetVerticalViewController.swift
// StripePaymentSheet
//
// Created by Yuki Tokuhiro on 5/3/24.
//

@_spi(STP) import StripeCore
@_spi(STP) import StripePayments
@_spi(STP) import StripePaymentsUI
@_spi(STP) import StripeUICore
import UIKit

class PaymentSheetVerticalViewController: UIViewController, FlowControllerViewController {
var selectedPaymentOption: PaymentSheet.PaymentOption?
/// The type of the payment method that's currently selected in the UI, or unknown if no payment method is selected.
var selectedPaymentMethodType: PaymentSheet.PaymentMethodType = .stripe(.unknown)
weak var delegate: FlowControllerViewControllerDelegate?
let loadResult: PaymentSheetLoader.LoadResult
let configuration: PaymentSheet.Configuration
var intent: Intent {
return loadResult.intent
}
var error: Error?

// MARK: - UI properties

lazy var navigationBar: SheetNavigationBar = {
let navBar = SheetNavigationBar(isTestMode: configuration.apiClient.isTestmode,
appearance: configuration.appearance)
// TODO: set navBar.delegate = self
return navBar
}()

// MARK: - Initializers

init(configuration: PaymentSheet.Configuration, loadResult: PaymentSheetLoader.LoadResult) {
// TODO: Deal with previousPaymentOption
self.loadResult = loadResult
self.configuration = configuration
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - UIViewController Methods
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = configuration.appearance.colors.background
configuration.style.configure(self)

let dummyView = UILabel()
dummyView.text = "Welcome to vertical mode"

// One stack view contains all our subviews
let stackView = UIStackView(arrangedSubviews: [
dummyView,
])
view.addAndPinSubview(stackView, insets: .init(top: 0, leading: 0, bottom: 0, trailing: PaymentSheetUI.defaultSheetMargins.bottom))
}
}

// MARK: - BottomSheetContentViewController
extension PaymentSheetVerticalViewController: BottomSheetContentViewController {
var allowsDragToDismiss: Bool {
// TODO
return true
}

func didTapOrSwipeToDismiss() {
// TODO
delegate?.flowControllerViewControllerShouldClose(self, didCancel: true)
}

var requiresFullScreen: Bool {
// TODO
return false
}

func didFinishAnimatingHeight() {
// no-op
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -727,24 +727,24 @@ class PaymentSheetAPITest: XCTestCase {
switch result {
case .success(let sut):
// ...the vc's intent should match the initial intent config...
XCTAssertFalse(sut.viewController.intent.isSettingUp)
XCTAssertTrue(sut.viewController.intent.isPaymentIntent)
XCTAssertFalse(sut.intent.isSettingUp)
XCTAssertTrue(sut.intent.isPaymentIntent)
// ...and updating the intent config should succeed...
intentConfig.mode = .setup(currency: nil, setupFutureUsage: .offSession)
sut.update(intentConfiguration: intentConfig) { error in
XCTAssertNil(error)
XCTAssertNil(sut.paymentOption)
XCTAssertTrue(sut.viewController.intent.isSettingUp)
XCTAssertFalse(sut.viewController.intent.isPaymentIntent)
XCTAssertTrue(sut.intent.isSettingUp)
XCTAssertFalse(sut.intent.isPaymentIntent)
firstUpdateExpectation.fulfill()

// ...updating the intent config multiple times should succeed...
intentConfig.mode = .payment(amount: 100, currency: "USD", setupFutureUsage: nil)
sut.update(intentConfiguration: intentConfig) { error in
XCTAssertNil(error)
XCTAssertNil(sut.paymentOption)
XCTAssertFalse(sut.viewController.intent.isSettingUp)
XCTAssertTrue(sut.viewController.intent.isPaymentIntent)
XCTAssertFalse(sut.intent.isSettingUp)
XCTAssertTrue(sut.intent.isPaymentIntent)

// Sanity check that the analytics...
let analytics = STPAnalyticsClient.sharedClient._testLogHistory
Expand Down

0 comments on commit ec6f349

Please sign in to comment.