-
Notifications
You must be signed in to change notification settings - Fork 976
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Embedded update pt 2 - update view (#4150)
## Summary Previous PR: #4141 Makes EmbeddedPaymentElement view a view that contains embedded view so we can swap to the updated embedded view with an animation. https://github.com/user-attachments/assets/533e0fb1-c848-4fea-9793-30347bbd90f1 Still to come: - Restore previous customer input + E2E tests (e.g. load PI -> fill out card form -> update to SI -> expect form to be preserved but w/o checkbox) - Make confirm handle in-flight and failed update calls. - (Bonus) Cancel network calls etc. from previous update to reduce battery/network usage. Can apply this to FC.update as well. ## Motivation https://jira.corp.stripe.com/browse/MOBILESDK-2583 ## Testing See snapshot test. ## Changelog Not user facing --------- Co-authored-by: Nick Porter <88012362+porter-stripe@users.noreply.github.com>
- Loading branch information
1 parent
2d1e81f
commit 0995ef4
Showing
6 changed files
with
151 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
...StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementContainerView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// | ||
// EmbeddedPaymentElementContainerView.swift | ||
// StripePaymentSheet | ||
// | ||
// Created by Yuki Tokuhiro on 10/16/24. | ||
// | ||
import UIKit | ||
|
||
/// The view that's vended to the merchant, containing the embedded view. We use this to be able to swap out the embedded view with an animation when `update` is called. | ||
class EmbeddedPaymentElementContainerView: UIView { | ||
var updateSuperviewHeight: () -> Void = {} | ||
private var view: EmbeddedPaymentMethodsView | ||
private var bottomAnchorConstraint: NSLayoutConstraint! | ||
|
||
init(embeddedPaymentMethodsView: EmbeddedPaymentMethodsView) { | ||
self.view = embeddedPaymentMethodsView | ||
super.init(frame: .zero) | ||
addInitialView(view) | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError() | ||
} | ||
|
||
private func addInitialView(_ view: UIView) { | ||
view.translatesAutoresizingMaskIntoConstraints = false | ||
addSubview(view) | ||
bottomAnchorConstraint = view.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) | ||
NSLayoutConstraint.activate([ | ||
view.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), | ||
view.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), | ||
view.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), | ||
bottomAnchorConstraint, | ||
]) | ||
} | ||
|
||
func updateEmbeddedPaymentMethodsView(_ embeddedPaymentMethodsView: EmbeddedPaymentMethodsView) { | ||
guard frame.size != .zero else { | ||
// A zero frame means we haven't been laid out yet. Simply replace the old view to avoid laying out before the view is ready and breaking constraints. | ||
self.view.removeFromSuperview() | ||
self.view = embeddedPaymentMethodsView | ||
addInitialView(embeddedPaymentMethodsView) | ||
return | ||
} | ||
let oldView = view | ||
let oldViewHeight = frame.height | ||
// Add the new view w/ 0 alpha | ||
embeddedPaymentMethodsView.translatesAutoresizingMaskIntoConstraints = false | ||
addSubview(embeddedPaymentMethodsView) | ||
view = embeddedPaymentMethodsView | ||
embeddedPaymentMethodsView.alpha = 0 | ||
NSLayoutConstraint.activate([ | ||
embeddedPaymentMethodsView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), | ||
embeddedPaymentMethodsView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), | ||
embeddedPaymentMethodsView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), | ||
// Omit the bottom anchor so that the height is still fixed to the old view height | ||
]) | ||
// important that the view is already laid out before the animation block so that it doesn't animate from zero size. | ||
layoutIfNeeded() | ||
|
||
UIView.animate(withDuration: 0.2) { | ||
// Re-pin bottom anchor to the new view, thus updating our height | ||
self.bottomAnchorConstraint.isActive = false | ||
self.bottomAnchorConstraint = embeddedPaymentMethodsView.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor) | ||
self.bottomAnchorConstraint.isActive = true | ||
self.layoutIfNeeded() | ||
// Fade old view out and new view in | ||
oldView.alpha = 0 | ||
embeddedPaymentMethodsView.alpha = 1 | ||
if oldViewHeight != self.systemLayoutSizeFitting(.zero).height { | ||
// Invoke EmbeddedPaymentElement delegate method so that height does not jump | ||
self.updateSuperviewHeight() | ||
} | ||
} completion: { _ in | ||
oldView.removeFromSuperview() | ||
} | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
...ymentSheet/StripePaymentSheetTests/PaymentSheet/EmbeddedPaymentElementSnapshotTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// | ||
// EmbeddedPaymentElementSnapshotTests.swift | ||
// StripePaymentSheet | ||
// | ||
// Created by Yuki Tokuhiro on 10/16/24. | ||
// | ||
|
||
import StripeCoreTestUtils | ||
@_spi(STP) @testable import StripePayments | ||
@_spi(EmbeddedPaymentElementPrivateBeta) @testable import StripePaymentSheet | ||
@testable import StripePaymentsTestUtils | ||
@_spi(STP) @testable import StripeUICore | ||
import XCTest | ||
|
||
class EmbeddedPaymentElementSnapshotTests: STPSnapshotTestCase, EmbeddedPaymentElementDelegate { | ||
var delegateDidUpdateHeightCalled: Bool = false | ||
var delegateDidUpdatePaymentOptionCalled: Bool = false | ||
func embeddedPaymentElementDidUpdateHeight(embeddedPaymentElement: StripePaymentSheet.EmbeddedPaymentElement) { | ||
self.delegateDidUpdateHeightCalled = true | ||
} | ||
|
||
func embeddedPaymentElementDidUpdatePaymentOption(embeddedPaymentElement: StripePaymentSheet.EmbeddedPaymentElement) { | ||
self.delegateDidUpdatePaymentOptionCalled = true | ||
} | ||
|
||
lazy var configuration: EmbeddedPaymentElement.Configuration = { | ||
var config = EmbeddedPaymentElement.Configuration._testValue_MostPermissive(isApplePayEnabled: false) | ||
config.apiClient = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) | ||
return config | ||
}() | ||
let paymentIntentConfig = EmbeddedPaymentElement.IntentConfiguration(mode: .payment(amount: 1000, currency: "USD"), paymentMethodTypes: ["card"]) { _, _, _ in | ||
// These tests don't confirm, so this is unused | ||
} | ||
let setupIntentConfig = EmbeddedPaymentElement.IntentConfiguration(mode: .setup(setupFutureUsage: .offSession), paymentMethodTypes: ["card", "us_bank_account"]) { _, _, _ in | ||
// These tests don't confirm, so this is unused | ||
} | ||
|
||
func testUpdateFromCardToCardAndUSBankAccount() async throws { | ||
// Given a EmbeddedPaymentElement instance... | ||
let sut = try await EmbeddedPaymentElement.create(intentConfiguration: paymentIntentConfig, configuration: configuration) | ||
sut.delegate = self | ||
sut.view.autosizeHeight(width: 300) | ||
|
||
let loadResult = await sut.update(intentConfiguration: setupIntentConfig) | ||
XCTAssertEqual(loadResult, .succeeded) | ||
sut.view.autosizeHeight(width: 300) | ||
|
||
STPSnapshotVerifyView(sut.view) // Should show US Bank and card | ||
XCTAssertTrue(delegateDidUpdateHeightCalled) | ||
XCTAssertFalse(delegateDidUpdatePaymentOptionCalled) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+19.9 KB
...shotTests/testUpdateFromCardToCardAndUSBankAccountWithCompletionHandler_@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.