From c48f05d41759f38d986b56ae91b9f51fed36cf08 Mon Sep 17 00:00:00 2001 From: Yuki Date: Thu, 17 Oct 2024 09:53:09 -0700 Subject: [PATCH 1/5] Embedded update pt 3 - restore previous customer input --- .../Source/Helpers/DownloadManager.swift | 2 +- .../EmbeddedPaymentElement+Internal.swift | 89 ++++++++++++++++++- .../Embedded/EmbeddedPaymentElement.swift | 47 ++++++++-- .../Embedded/EmbeddedPaymentMethodsView.swift | 70 +++++---------- .../Vertical Main Screen/RowButton.swift | 2 +- 5 files changed, 151 insertions(+), 59 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Helpers/DownloadManager.swift b/StripePaymentSheet/StripePaymentSheet/Source/Helpers/DownloadManager.swift index d843c777102..92a19c5914a 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Helpers/DownloadManager.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Helpers/DownloadManager.swift @@ -10,7 +10,7 @@ import UIKit /// For internal SDK use only. @objc(STP_Internal_DownloadManager) -// TODO: Refactor this API shape! https://github.com/stripe/stripe-ios/pull/3487#discussion_r1561337866 +// TODO: https://jira.corp.stripe.com/browse/MOBILESDK-2604 Refactor this! @_spi(STP) public class DownloadManager: NSObject { public typealias UpdateImageHandler = (UIImage) -> Void diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift index 4e80858929c..2759a337289 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift @@ -11,8 +11,15 @@ extension EmbeddedPaymentElement { configuration: Configuration, loadResult: PaymentSheetLoader.LoadResult, analyticsHelper: PaymentSheetAnalyticsHelper, + previousPaymentOption: PaymentOption? = nil, delegate: EmbeddedPaymentMethodsViewDelegate? = nil ) -> EmbeddedPaymentMethodsView { + // Restore the customer's previous payment method. + // Caveats: + // - Only payment method details (including checkbox state) and billing details are restored + // - Only restored if the previous input resulted in a completed form i.e. partial or invalid input is still discarded + // TODO: Restore the form, if any + let shouldShowApplePay = PaymentSheet.isApplePayEnabled(elementsSession: loadResult.elementsSession, configuration: configuration) let shouldShowLink = PaymentSheet.isLinkEnabled(elementsSession: loadResult.elementsSession, configuration: configuration) let savedPaymentMethodAccessoryType = RowButton.RightAccessoryButton.getAccessoryButtonType( @@ -23,7 +30,23 @@ extension EmbeddedPaymentElement { allowsPaymentMethodRemoval: loadResult.elementsSession.allowsRemovalOfPaymentMethodsForPaymentSheet() ) let initialSelection: EmbeddedPaymentMethodsView.Selection? = { - // Default to the customer's default or the first saved payment method, if any + // Select the previous payment option + switch previousPaymentOption { + case .applePay: + return .applePay + case .link: + return .link + case .external(paymentMethod: let paymentMethod, billingDetails: _): + return .new(paymentMethodType: .external(paymentMethod)) + case .saved(paymentMethod: let paymentMethod, confirmParams: _): + return .saved(paymentMethod: paymentMethod) + case .new(confirmParams: let confirmParams): + return .new(paymentMethodType: confirmParams.paymentMethodType) + case nil: + break + } + + // If there's no previous customer input, default to the customer's default or the first saved payment method, if any let customerDefault = CustomerPaymentOption.defaultPaymentMethod(for: configuration.customer?.id) switch customerDefault { case .applePay: @@ -66,3 +89,67 @@ extension EmbeddedPaymentElement: EmbeddedPaymentMethodsViewDelegate { delegate?.embeddedPaymentElementDidUpdatePaymentOption(embeddedPaymentElement: self) } } +@_spi(STP) import StripePayments +@_spi(STP) import StripePaymentsUI +@_spi(STP) import StripeUICore +import UIKit + +extension EmbeddedPaymentElement.PaymentOptionDisplayData { + init(paymentOption: PaymentOption, mandateText: NSAttributedString?) { + self.mandateText = mandateText + self.image = paymentOption.makeIcon(updateImageHandler: nil) + switch paymentOption { + case .applePay: + label = String.Localized.apple_pay + paymentMethodType = "apple_pay" + billingDetails = nil + case .saved(let paymentMethod, _): + label = paymentMethod.paymentSheetLabel + paymentMethodType = paymentMethod.type.identifier + billingDetails = paymentMethod.billingDetails?.toPaymentSheetBillingDetails() + case .new(let confirmParams): + label = confirmParams.paymentSheetLabel + paymentMethodType = confirmParams.paymentMethodType.identifier + billingDetails = confirmParams.paymentMethodParams.billingDetails?.toPaymentSheetBillingDetails() + case .link(let option): + label = option.paymentSheetLabel + paymentMethodType = STPPaymentMethodType.link.identifier + billingDetails = option.billingDetails?.toPaymentSheetBillingDetails() + case .external(let paymentMethod, let stpBillingDetails): + label = paymentMethod.label + paymentMethodType = paymentMethod.type + billingDetails = stpBillingDetails.toPaymentSheetBillingDetails() + } + } + +// init(selection: EmbeddedPaymentMethodsView.Selection, mandateText: NSAttributedString?) { +// self.mandateText = mandateText +// +// switch selection { +// case .new(paymentMethodType: let paymentMethodType): +// // TODO: Make image dynamic https://jira.corp.stripe.com/browse/MOBILESDK-2322 +// image = paymentMethodType.makeImage( +// forDarkBackground: UITraitCollection.current.isDarkMode, +// updateHandler: nil +// ) +// label = paymentMethodType.displayName +// self.paymentMethodType = paymentMethodType.identifier +// billingDetails = nil // TODO(porter) Handle billing details when we present forms (maybe set this to defaultBillingDetails) if billingDetailsConfiguration.attachDefaultsToPaymentMethod is true +// case .saved(paymentMethod: let paymentMethod): +// image = paymentMethod.makeIcon() +// label = paymentMethod.paymentSheetLabel +// paymentMethodType = paymentMethod.type.identifier +// billingDetails = paymentMethod.billingDetails?.toPaymentSheetBillingDetails() +// case .applePay: +// image = Image.apple_pay_mark.makeImage().withRenderingMode(.alwaysOriginal) +// label = .Localized.apple_pay +// paymentMethodType = "apple_pay" +// billingDetails = nil // TODO(porter) Handle billing details when we present forms +// case .link: +// image = Image.link_logo.makeImage() +// label = STPPaymentMethodType.link.displayName +// paymentMethodType = STPPaymentMethodType.link.identifier +// billingDetails = nil // TODO(porter) Handle billing details when we present forms +// } +// } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement.swift index f5df948e4fe..e98df39c857 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement.swift @@ -49,7 +49,10 @@ public final class EmbeddedPaymentElement { /// Contains information about the customer's selected payment option. /// Use this to display the payment option in your own UI public var paymentOption: PaymentOptionDisplayData? { - return embeddedPaymentMethodsView.displayData + guard let _paymentOption else { + return nil + } + return .init(paymentOption: _paymentOption, mandateText: embeddedPaymentMethodsView.mandateText) } /// An asynchronous failable initializer @@ -103,7 +106,8 @@ public final class EmbeddedPaymentElement { currentUpdateTask?.cancel() _ = await currentUpdateTask?.value // Start the new update task - let currentUpdateTask = Task { [weak self, configuration, paymentOption, analyticsHelper] in + let currentUpdateTask = Task { @MainActor [weak self, configuration, analyticsHelper] in + // ⚠️ Don't modify `self` until the end to avoid being canceled halfway through and leaving self in a partially updated state. // 1. Reload v1/elements/session. let loadResult: PaymentSheetLoader.LoadResult do { @@ -126,26 +130,28 @@ public final class EmbeddedPaymentElement { configuration: configuration, loadResult: loadResult, analyticsHelper: analyticsHelper, + previousPaymentOption: self?._paymentOption, delegate: self - // TODO: https://jira.corp.stripe.com/browse/MOBILESDK-2583 Restore previous payment option ) - // 2. Pre-load image into cache - // Hack: Accessing paymentOption has the side-effect of ensuring its `image` property is loaded (from the internet instead of disk) before we call the completion handler. + // 3. Pre-load image into cache // Call this on a detached Task b/c this synchronously (!) loads the image from network and we don't want to block the main actor let fetchPaymentOption = Task.detached(priority: .userInitiated) { - return await embeddedPaymentMethodsView.displayData + // This has the nasty side effect of synchronously downloading the image (see https://jira.corp.stripe.com/browse/MOBILESDK-2604) + // This caches it so that DownloadManager doesn't block the main thread when the merchant tries to access the image + return await embeddedPaymentMethodsView.selection?.paymentMethodType?.makeImage(updateHandler: nil) } _ = await fetchPaymentOption.value guard let self, !Task.isCancelled else { return .canceled } - // At this point, we're the latest update - update self properties and inform our delegate. + // At this point, we're still the latest update and update is successful - update self properties and inform our delegate. + let oldPaymentOption = self.paymentOption self.loadResult = loadResult self.embeddedPaymentMethodsView = embeddedPaymentMethodsView self.containerView.updateEmbeddedPaymentMethodsView(embeddedPaymentMethodsView) - if paymentOption != embeddedPaymentMethodsView.displayData { + if oldPaymentOption != self.paymentOption { self.delegate?.embeddedPaymentElementDidUpdatePaymentOption(embeddedPaymentElement: self) } return .succeeded @@ -169,6 +175,31 @@ public final class EmbeddedPaymentElement { internal private(set) var loadResult: PaymentSheetLoader.LoadResult internal private(set) var currentUpdateTask: Task? private let analyticsHelper: PaymentSheetAnalyticsHelper + internal var _paymentOption: PaymentOption? { + // TODO: Handle forms. See `PaymentSheetVerticalViewController.selectedPaymentOption`. + // TODO: Handle CVC recollection + switch embeddedPaymentMethodsView.selection { + case .applePay: + return .applePay + case .link: + return .link(option: .wallet) + case let .new(paymentMethodType: paymentMethodType): + let params = IntentConfirmParams(type: paymentMethodType) + params.setDefaultBillingDetailsIfNecessary(for: configuration) + switch paymentMethodType { + case .stripe: + return .new(confirmParams: params) + case .external(let type): + return .external(paymentMethod: type, billingDetails: params.paymentMethodParams.nonnil_billingDetails) + case .instantDebits, .linkCardBrand: + return .new(confirmParams: params) + } + case .saved(paymentMethod: let paymentMethod): + return .saved(paymentMethod: paymentMethod, confirmParams: nil) + case .none: + return nil + } + } private init( configuration: Configuration, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift index 18df4d059e9..1bd292e1c52 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift @@ -21,11 +21,6 @@ class EmbeddedPaymentMethodsView: UIView { typealias Selection = VerticalPaymentMethodListSelection // TODO(porter) Maybe define our own later - var displayData: EmbeddedPaymentElement.PaymentOptionDisplayData? { - guard let selection else { return nil } - return .init(selection: selection, mandateText: mandateView.attributedText) - } - private let appearance: PaymentSheet.Appearance private(set) var selection: Selection? { didSet { @@ -37,6 +32,11 @@ class EmbeddedPaymentMethodsView: UIView { } private let mandateProvider: MandateTextProvider private let shouldShowMandate: Bool + /// A bit hacky; this is the mandate text for the given payment method, *regardless* of whether it is shown in the view. + /// It'd be better if the source of truth of mandate text was not the view and instead an independent `func mandateText(...) -> NSAttributedString` function, but this is hard b/c US Bank Account doesn't show mandate in certain states. + var mandateText: NSAttributedString? { + mandateView.attributedText + } lazy var stackView: UIStackView = { let stackView = UIStackView() @@ -136,16 +136,24 @@ class EmbeddedPaymentMethodsView: UIView { stackView.addArrangedSubview(linkRowButton) } + // Add all non-card PMs (card is added above) for paymentMethodType in paymentMethodTypes where paymentMethodType != .stripe(.card) { - stackView.addArrangedSubview(RowButton.makeForPaymentMethodType(paymentMethodType: paymentMethodType, - subtitle: VerticalPaymentMethodListViewController.subtitleText(for: paymentMethodType), - savedPaymentMethodType: savedPaymentMethod?.type, - appearance: rowButtonAppearance, - shouldAnimateOnPress: true, - isEmbedded: true, - didTap: { [weak self] rowButton in - self?.didTap(selectedRowButton: rowButton, selection: .new(paymentMethodType: paymentMethodType)) - })) + let selection: Selection = .new(paymentMethodType: paymentMethodType) + let rowButton = RowButton.makeForPaymentMethodType( + paymentMethodType: paymentMethodType, + subtitle: VerticalPaymentMethodListViewController.subtitleText(for: paymentMethodType), + savedPaymentMethodType: savedPaymentMethod?.type, + appearance: rowButtonAppearance, + shouldAnimateOnPress: true, + isEmbedded: true, + didTap: { [weak self] rowButton in + self?.didTap(selectedRowButton: rowButton, selection: selection) + } + ) + if initialSelection == selection { + rowButton.isSelected = true + } + stackView.addArrangedSubview(rowButton) } if appearance.embeddedPaymentElement.style != .floatingButton { @@ -249,37 +257,3 @@ extension PaymentSheet.Appearance.EmbeddedPaymentElement.Style { } } } -@_spi(STP) import StripePayments -@_spi(STP) import StripePaymentsUI - -extension EmbeddedPaymentElement.PaymentOptionDisplayData { - init(selection: EmbeddedPaymentMethodsView.Selection, mandateText: NSAttributedString?) { - self.mandateText = mandateText - - switch selection { - case .new(paymentMethodType: let paymentMethodType): - image = paymentMethodType.makeImage( - forDarkBackground: UITraitCollection.current.isDarkMode, - updateHandler: nil - ) - label = paymentMethodType.displayName - self.paymentMethodType = paymentMethodType.identifier - billingDetails = nil // TODO(porter) Handle billing details when we present forms (maybe set this to defaultBillingDetails) if billingDetailsConfiguration.attachDefaultsToPaymentMethod is true - case .saved(paymentMethod: let paymentMethod): - image = paymentMethod.makeIcon() - label = paymentMethod.paymentSheetLabel - paymentMethodType = paymentMethod.type.identifier - billingDetails = paymentMethod.billingDetails?.toPaymentSheetBillingDetails() - case .applePay: - image = Image.apple_pay_mark.makeImage().withRenderingMode(.alwaysOriginal) - label = .Localized.apple_pay - paymentMethodType = "apple_pay" - billingDetails = nil // TODO(porter) Handle billing details when we present forms - case .link: - image = Image.link_logo.makeImage() - label = STPPaymentMethodType.link.displayName - paymentMethodType = STPPaymentMethodType.link.identifier - billingDetails = nil // TODO(porter) Handle billing details when we present forms - } - } -} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Vertical Main Screen/RowButton.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Vertical Main Screen/RowButton.swift index 2c6a50d8de1..60bfb03779a 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Vertical Main Screen/RowButton.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Vertical Main Screen/RowButton.swift @@ -47,7 +47,7 @@ class RowButton: UIView { init(appearance: PaymentSheet.Appearance, imageView: UIImageView, text: String, subtext: String? = nil, rightAccessoryView: UIView? = nil, shouldAnimateOnPress: Bool = false, isEmbedded: Bool = false, didTap: @escaping DidTapClosure) { self.appearance = appearance - self.shouldAnimateOnPress = shouldAnimateOnPress + self.shouldAnimateOnPress = true self.didTap = didTap self.shadowRoundedRect = ShadowedRoundedRectangle(appearance: appearance) self.imageView = imageView From da408fab4d8ab3c77021b6aa9635e0f6f03a3649 Mon Sep 17 00:00:00 2001 From: Yuki Date: Fri, 18 Oct 2024 18:50:55 -0700 Subject: [PATCH 2/5] Test --- .../project.pbxproj | 4 + .../PaymentSheetEmbeddedUITests.swift | 23 ++++++ .../EmbeddedPaymentElement+Internal.swift | 42 ++-------- .../Embedded/EmbeddedPaymentElement.swift | 10 +++ .../Embedded/EmbeddedPaymentMethodsView.swift | 2 +- .../Embedded/MandateTextProvider.swift | 75 +++++++++--------- .../Views/SimpleMandateTextView.swift | 2 +- .../EmbeddedPaymentElementTest.swift | 76 ++++++++++++++----- 8 files changed, 138 insertions(+), 96 deletions(-) create mode 100644 Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetEmbeddedUITests.swift diff --git a/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/project.pbxproj b/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/project.pbxproj index 4ed23d11305..2aeea7c994c 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/project.pbxproj +++ b/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ B36A24145C97D73C981DDBAC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4B26C754925F8D2A5183B2E /* Main.storyboard */; }; B615E86F2CA4B267007D684C /* ExampleEmbeddedElementCheckoutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B615E86E2CA4B266007D684C /* ExampleEmbeddedElementCheckoutViewController.swift */; }; B641A4192C2BA25D00AE654A /* PaymentSheetVerticalUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B641A4182C2BA25D00AE654A /* PaymentSheetVerticalUITest.swift */; }; + B69470622CC33EAA0088DEED /* PaymentSheetEmbeddedUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69470612CC33EA40088DEED /* PaymentSheetEmbeddedUITests.swift */; }; B6CA975C2C486DE700DAE441 /* PaymentSheetLPMUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6CA975B2C486DE700DAE441 /* PaymentSheetLPMUITest.swift */; }; B6D6AAA666859847BB59749C /* PaymentSheet+AddressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61C5739DBE6405A4D9FD5F2 /* PaymentSheet+AddressTests.swift */; }; C39CA2EBE08EE768559A8FD1 /* StripeCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1FD1F5193E4A361EA9E8FED3 /* StripeCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -247,6 +248,7 @@ B54E58F2CA450CF49ECD5637 /* CustomerSheetTestPlaygroundController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerSheetTestPlaygroundController.swift; sourceTree = ""; }; B615E86E2CA4B266007D684C /* ExampleEmbeddedElementCheckoutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleEmbeddedElementCheckoutViewController.swift; sourceTree = ""; }; B641A4182C2BA25D00AE654A /* PaymentSheetVerticalUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetVerticalUITest.swift; sourceTree = ""; }; + B69470612CC33EA40088DEED /* PaymentSheetEmbeddedUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetEmbeddedUITests.swift; sourceTree = ""; }; B69C155A2B9FDCBD009CE667 /* PaymentSheet Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = "PaymentSheet Example.entitlements"; path = "PaymentSheet Example/PaymentSheet Example.entitlements"; sourceTree = ""; }; B6CA975B2C486DE700DAE441 /* PaymentSheetLPMUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetLPMUITest.swift; sourceTree = ""; }; B7AFD32B5EAD3BEEEC3D4260 /* ExampleSwiftUIPaymentSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleSwiftUIPaymentSheet.swift; sourceTree = ""; }; @@ -422,6 +424,7 @@ 5CA299EAF5914484195167EB /* PaymentSheetUITest.swift */, B6CA975B2C486DE700DAE441 /* PaymentSheetLPMUITest.swift */, B641A4182C2BA25D00AE654A /* PaymentSheetVerticalUITest.swift */, + B69470612CC33EA40088DEED /* PaymentSheetEmbeddedUITests.swift */, 36BB679CF53EEF943F0BAAC9 /* XCUITest+Utilities.swift */, 6A5CCDA32C0E6F5D003306A4 /* LinkPaymentControllerUITest.swift */, ); @@ -664,6 +667,7 @@ B641A4192C2BA25D00AE654A /* PaymentSheetVerticalUITest.swift in Sources */, 5AF35F9FA7106ACD4E4F360F /* PaymentSheetBillingCollectionUITests.swift in Sources */, 2CD71F097C7CD0D9BFC7499D /* PaymentSheetTestPlaygroundSettings.swift in Sources */, + B69470622CC33EAA0088DEED /* PaymentSheetEmbeddedUITests.swift in Sources */, 540F279CE5B41C122AD1DEF6 /* PaymentSheetUITest.swift in Sources */, 6A5CCDA42C0E6F5D003306A4 /* LinkPaymentControllerUITest.swift in Sources */, EE0FA73AAB80863583C69D70 /* XCUITest+Utilities.swift in Sources */, diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetEmbeddedUITests.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetEmbeddedUITests.swift new file mode 100644 index 00000000000..faf6d68ee46 --- /dev/null +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetEmbeddedUITests.swift @@ -0,0 +1,23 @@ +// +// PayymentSheetEmbeddedUITests.swift +// PaymentSheet Example +// +// Created by Yuki Tokuhiro on 10/18/24. +// +import XCTest + +class PaymentSheetEmbeddedUITests: PaymentSheetUITestCase { + func testUpdate() { + var settings = PaymentSheetTestPlaygroundSettings.defaultValues() + settings.uiStyle = .embedded + settings.currency = .eur + loadPlayground(app, settings) + + app.buttons["Present embedded payment element"].waitForExistenceAndTap() + app.buttons["Card"].waitForExistenceAndTap() + + try! fillCardData(app) + app.buttons["Pay €50.99"].tap() + XCTAssertTrue(app.staticTexts["Success!"].waitForExistence(timeout: 10)) + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift index 2759a337289..8b84352c551 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift @@ -4,6 +4,9 @@ // // Created by Yuki Tokuhiro on 10/10/24. // +@_spi(STP) import StripePayments +@_spi(STP) import StripePaymentsUI +@_spi(STP) import StripeUICore extension EmbeddedPaymentElement { @MainActor @@ -89,15 +92,13 @@ extension EmbeddedPaymentElement: EmbeddedPaymentMethodsViewDelegate { delegate?.embeddedPaymentElementDidUpdatePaymentOption(embeddedPaymentElement: self) } } -@_spi(STP) import StripePayments -@_spi(STP) import StripePaymentsUI -@_spi(STP) import StripeUICore -import UIKit + +// MARK: - EmbeddedPaymentElement.PaymentOptionDisplayData extension EmbeddedPaymentElement.PaymentOptionDisplayData { init(paymentOption: PaymentOption, mandateText: NSAttributedString?) { self.mandateText = mandateText - self.image = paymentOption.makeIcon(updateImageHandler: nil) + self.image = paymentOption.makeIcon(updateImageHandler: nil) // ☠️ This can make a blocking network request TODO: https://jira.corp.stripe.com/browse/MOBILESDK-2604 Refactor this! switch paymentOption { case .applePay: label = String.Localized.apple_pay @@ -121,35 +122,4 @@ extension EmbeddedPaymentElement.PaymentOptionDisplayData { billingDetails = stpBillingDetails.toPaymentSheetBillingDetails() } } - -// init(selection: EmbeddedPaymentMethodsView.Selection, mandateText: NSAttributedString?) { -// self.mandateText = mandateText -// -// switch selection { -// case .new(paymentMethodType: let paymentMethodType): -// // TODO: Make image dynamic https://jira.corp.stripe.com/browse/MOBILESDK-2322 -// image = paymentMethodType.makeImage( -// forDarkBackground: UITraitCollection.current.isDarkMode, -// updateHandler: nil -// ) -// label = paymentMethodType.displayName -// self.paymentMethodType = paymentMethodType.identifier -// billingDetails = nil // TODO(porter) Handle billing details when we present forms (maybe set this to defaultBillingDetails) if billingDetailsConfiguration.attachDefaultsToPaymentMethod is true -// case .saved(paymentMethod: let paymentMethod): -// image = paymentMethod.makeIcon() -// label = paymentMethod.paymentSheetLabel -// paymentMethodType = paymentMethod.type.identifier -// billingDetails = paymentMethod.billingDetails?.toPaymentSheetBillingDetails() -// case .applePay: -// image = Image.apple_pay_mark.makeImage().withRenderingMode(.alwaysOriginal) -// label = .Localized.apple_pay -// paymentMethodType = "apple_pay" -// billingDetails = nil // TODO(porter) Handle billing details when we present forms -// case .link: -// image = Image.link_logo.makeImage() -// label = STPPaymentMethodType.link.displayName -// paymentMethodType = STPPaymentMethodType.link.identifier -// billingDetails = nil // TODO(porter) Handle billing details when we present forms -// } -// } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement.swift index e98df39c857..a1ba6715ed0 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement.swift @@ -44,6 +44,7 @@ public final class EmbeddedPaymentElement { public let paymentMethodType: String /// If you set `configuration.embeddedViewDisplaysMandateText = false`, this text must be displayed to the customer near your “Buy” button to comply with regulations. public let mandateText: NSAttributedString? + } /// Contains information about the customer's selected payment option. @@ -309,3 +310,12 @@ extension EmbeddedPaymentElement { public typealias BillingDetailsCollectionConfiguration = PaymentSheet.BillingDetailsCollectionConfiguration public typealias ExternalPaymentMethodConfiguration = PaymentSheet.ExternalPaymentMethodConfiguration } + +// MARK: - EmbeddedPaymentElement.PaymentOptionDisplayData + +extension EmbeddedPaymentElement.PaymentOptionDisplayData { + public static func == (lhs: Self, rhs: Self) -> Bool { + // Unfortunately, we need to manually define this because the implementation of Equatable on UIImage does not work + return lhs.image.pngData() == rhs.image.pngData() && rhs.label == lhs.label && lhs.billingDetails == rhs.billingDetails && lhs.paymentMethodType == rhs.paymentMethodType && lhs.mandateText == rhs.mandateText + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift index 1bd292e1c52..60ebb1495d8 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift @@ -38,7 +38,7 @@ class EmbeddedPaymentMethodsView: UIView { mandateView.attributedText } - lazy var stackView: UIStackView = { + private(set) lazy var stackView: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = appearance.embeddedPaymentElement.style == .floatingButton ? appearance.embeddedPaymentElement.row.floating.spacing : 0 diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/MandateTextProvider.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/MandateTextProvider.swift index 95cc5f9e1d3..77ce5ea9f7b 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/MandateTextProvider.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/MandateTextProvider.swift @@ -31,46 +31,45 @@ class VerticalListMandateProvider: MandateTextProvider { /// - Parameter paymentMethodType: The payment method type who's mandate should be constructed /// - Parameter savedPaymentMethod: The currently selected saved payment method if any /// - Parameter bottomNoticeAttributedString: Passing this in just makes this method return it - /// - Returns: An `NSAttributedString` representing the mandate to be displayed for `paymentMethodType`. + /// - Returns: An `NSAttributedString` representing the mandate to be displayed for `paymentMethodType` or `nil` if there is no mandate. func mandate(for paymentMethodType: PaymentSheet.PaymentMethodType?, savedPaymentMethod: STPPaymentMethod?, bottomNoticeAttributedString: NSAttributedString? = nil) -> NSAttributedString? { - let newMandateText: NSAttributedString? = { - guard let paymentMethodType else { return nil } - if savedPaymentMethod != nil { - // 1. For saved PMs, manually build mandates - switch paymentMethodType { - case .stripe(.USBankAccount): - return USBankAccountPaymentMethodElement.attributedMandateTextSavedPaymentMethod(alignment: .natural, theme: configuration.appearance.asElementsTheme) - case .stripe(.SEPADebit): - return .init(string: String(format: String.Localized.sepa_mandate_text, configuration.merchantDisplayName)) - default: - return nil - } - } else { - // 2. For new PMs, see if we have a bottomNoticeAttributedString, typically just US bank acct. and Link Instant Debits - if let bottomNoticeAttributedString { - return bottomNoticeAttributedString - } - // 3. If not, generate the form - let form = PaymentSheetFormFactory( - intent: intent, - elementsSession: elementsSession, - configuration: .paymentSheet(configuration), - paymentMethod: paymentMethodType, - previousCustomerInput: nil, - linkAccount: LinkAccountContext.shared.account, - analyticsHelper: analyticsHelper - ).make() - - guard !form.collectsUserInput else { - // If it collects user input, the mandate will be displayed in the form and not here - return nil - } - // Get the mandate from the form, if available - // 🙋‍♂️ Note: assumes mandates are SimpleMandateElement! - return form.getAllUnwrappedSubElements().compactMap({ $0 as? SimpleMandateElement }).first?.mandateTextView.attributedText + guard let paymentMethodType else { return nil } + if savedPaymentMethod != nil { + // 1. For saved PMs, manually build mandates + switch paymentMethodType { + case .stripe(.USBankAccount): + return USBankAccountPaymentMethodElement.attributedMandateTextSavedPaymentMethod(alignment: .natural, theme: configuration.appearance.asElementsTheme) + case .stripe(.SEPADebit): + return .init(string: String(format: String.Localized.sepa_mandate_text, configuration.merchantDisplayName)) + default: + return nil + } + } else { + // 2. For new PMs, see if we have a bottomNoticeAttributedString, typically just US bank acct. and Link Instant Debits + if let bottomNoticeAttributedString { + return bottomNoticeAttributedString } - }() + // 3. If not, generate the form + let form = PaymentSheetFormFactory( + intent: intent, + elementsSession: elementsSession, + configuration: .paymentSheet(configuration), + paymentMethod: paymentMethodType, + previousCustomerInput: nil, + linkAccount: LinkAccountContext.shared.account, + analyticsHelper: analyticsHelper + ).make() - return newMandateText + guard !form.collectsUserInput else { + // If it collects user input, the mandate will be displayed in the form and not here + return nil + } + // Get the mandate from the form, if available + // 🙋‍♂️ Note: assumes mandates are SimpleMandateElement! + if let mandateText = form.getAllUnwrappedSubElements().compactMap({ $0 as? SimpleMandateElement }).first?.mandateTextView.attributedText, !mandateText.string.isEmpty { + return mandateText + } + return nil + } } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Views/SimpleMandateTextView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Views/SimpleMandateTextView.swift index cecd5f71a5e..c78ff4642c8 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Views/SimpleMandateTextView.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Views/SimpleMandateTextView.swift @@ -17,7 +17,7 @@ class SimpleMandateTextView: UIView { let textView: UITextView = UITextView() var attributedText: NSAttributedString? { get { - textView.attributedText + textView.attributedText.string.isEmpty ? nil : textView.attributedText } set { textView.attributedText = newValue diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/EmbeddedPaymentElementTest.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/EmbeddedPaymentElementTest.swift index 2c268b479c0..2cdacfc5df8 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/EmbeddedPaymentElementTest.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/EmbeddedPaymentElementTest.swift @@ -13,47 +13,69 @@ import XCTest @_spi(EmbeddedPaymentElementPrivateBeta) @_spi(STP) @testable import StripePaymentSheet @MainActor -class EmbeddedPaymentElementTest: STPNetworkStubbingTestCase { +// https://jira.corp.stripe.com/browse/MOBILESDK-2607 Make these STPNetworkStubbingTestCase; blocked on getting them to record image requests +class EmbeddedPaymentElementTest: XCTestCase { 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 + let paymentIntentConfig = EmbeddedPaymentElement.IntentConfiguration(mode: .payment(amount: 1000, currency: "USD"), paymentMethodTypes: ["card", "cashapp"]) { _, _, _ in // These tests don't confirm, so this is unused } - let setupIntentConfig = EmbeddedPaymentElement.IntentConfiguration(mode: .setup(setupFutureUsage: .offSession), paymentMethodTypes: ["card"]) { _, _, _ in + let paymentIntentConfig2 = EmbeddedPaymentElement.IntentConfiguration(mode: .payment(amount: 999, currency: "USD"), paymentMethodTypes: ["card", "cashapp"]) { _, _, _ in // These tests don't confirm, so this is unused } + let setupIntentConfig = EmbeddedPaymentElement.IntentConfiguration(mode: .setup(setupFutureUsage: .offSession), paymentMethodTypes: ["card", "cashapp"]) { _, _, _ in + // These tests don't confirm, so this is unused + } + var delegateDidUpdatePaymentOptionCalled = false + var delegateDidUpdateHeightCalled = false // MARK: - `update` tests func testUpdate() async throws { STPAnalyticsClient.sharedClient._testLogHistory = [] - CustomerPaymentOption.setDefaultPaymentMethod(.applePay, forCustomer: nil) + CustomerPaymentOption.setDefaultPaymentMethod(nil, forCustomer: nil) // Given a EmbeddedPaymentElement instance... let sut = try await EmbeddedPaymentElement.create(intentConfiguration: paymentIntentConfig, configuration: configuration) sut.delegate = self + sut.view.autosizeHeight(width: 320) + // ...with cash app selected... + let cashAppPayRowButton = sut.embeddedPaymentMethodsView.getRowButton(accessibilityIdentifier: "Cash App Pay") + sut.embeddedPaymentMethodsView.didTap(selectedRowButton: cashAppPayRowButton, selection: .new(paymentMethodType: .stripe(.cashApp))) + delegateDidUpdatePaymentOptionCalled = false // This gets set to true when we select cash app ^ + XCTAssertNil(sut.paymentOption?.mandateText) // ...its intent should match the initial intent config... - XCTAssertFalse(sut.loadResult.intent.isSettingUp) - XCTAssertTrue(sut.loadResult.intent.isPaymentIntent) - - // ...and updating the intent config should succeed... - let update1Result = await sut.update(intentConfiguration: setupIntentConfig) + XCTAssertEqual(sut.loadResult.intent.amount, 1000) + // ...and updating the amount should succeed... + let update1Result = await sut.update(intentConfiguration: paymentIntentConfig2 + ) XCTAssertEqual(update1Result, .succeeded) - XCTAssertTrue(sut.loadResult.intent.isSettingUp) - XCTAssertFalse(sut.loadResult.intent.isPaymentIntent) - - // ...updating the intent config multiple times... + XCTAssertEqual(sut.loadResult.intent.amount, 999) + // ...without invoking the delegate (since neither height nor payment option updated) + XCTAssertFalse(delegateDidUpdateHeightCalled) + XCTAssertFalse(delegateDidUpdatePaymentOptionCalled) + // ...and preserve the cash app pay selection + XCTAssertEqual(sut.paymentOption?.label, "Cash App Pay") + + // Updating the intent config from payment to setup... // ...(using the completion block based API this time)... let secondUpdateExpectation = expectation(description: "Second update completes") - sut.update(intentConfiguration: paymentIntentConfig) { update2Result in + // ...(and resetting the delegate trackers)... + delegateDidUpdatePaymentOptionCalled = false + delegateDidUpdatePaymentOptionCalled = false + sut.update(intentConfiguration: setupIntentConfig) { update2Result in // ...should succeed. XCTAssertEqual(update2Result, .succeeded) - XCTAssertFalse(sut.loadResult.intent.isSettingUp) - XCTAssertTrue(sut.loadResult.intent.isPaymentIntent) - // TODO: Test paymentOption updates correctly. + XCTAssertFalse(sut.loadResult.intent.isPaymentIntent) + // ...and preserve the cash app pay selection + XCTAssertEqual(sut.paymentOption?.label, "Cash App Pay") + // ...and invoke both delegate methods since cash app now has a mandate + XCTAssertNotNil(sut.paymentOption?.mandateText) + XCTAssertTrue(self.delegateDidUpdateHeightCalled) + XCTAssertTrue(self.delegateDidUpdatePaymentOptionCalled) // Sanity check that the analytics... let analytics = STPAnalyticsClient.sharedClient._testLogHistory @@ -81,7 +103,7 @@ class EmbeddedPaymentElementTest: STPNetworkStubbingTestCase { intentConfig.mode = .setup(currency: "Invalid currency", setupFutureUsage: .offSession) let updateResult = await sut.update(intentConfiguration: intentConfig) switch updateResult { - case .failed(error: let error): + case .failed: break default: XCTFail() @@ -116,9 +138,13 @@ class EmbeddedPaymentElementTest: STPNetworkStubbingTestCase { extension EmbeddedPaymentElementTest: EmbeddedPaymentElementDelegate { // TODO: Test delegates are called - func embeddedPaymentElementDidUpdateHeight(embeddedPaymentElement: StripePaymentSheet.EmbeddedPaymentElement) {} + func embeddedPaymentElementDidUpdateHeight(embeddedPaymentElement: StripePaymentSheet.EmbeddedPaymentElement) { + delegateDidUpdateHeightCalled = true + } - func embeddedPaymentElementDidUpdatePaymentOption(embeddedPaymentElement: StripePaymentSheet.EmbeddedPaymentElement) {} + func embeddedPaymentElementDidUpdatePaymentOption(embeddedPaymentElement: StripePaymentSheet.EmbeddedPaymentElement) { + delegateDidUpdatePaymentOptionCalled = true + } } extension EmbeddedPaymentElement.UpdateResult: Equatable { @@ -131,3 +157,13 @@ extension EmbeddedPaymentElement.UpdateResult: Equatable { } } } + +extension EmbeddedPaymentMethodsView { + var rowButtons: [RowButton] { + return stackView.arrangedSubviews.compactMap { $0 as? RowButton } + } + + func getRowButton(accessibilityIdentifier: String) -> RowButton { + return rowButtons.first { $0.accessibilityIdentifier == accessibilityIdentifier }! + } +} From 326d76c69c0725ad97195bb7b9c89d7413c5625a Mon Sep 17 00:00:00 2001 From: Yuki Date: Mon, 21 Oct 2024 10:17:22 -0700 Subject: [PATCH 3/5] Fix compiler error?? --- .../PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift index 8b84352c551..4fa7200d131 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift @@ -4,6 +4,7 @@ // // Created by Yuki Tokuhiro on 10/10/24. // +@_spi(STP) import StripeCore @_spi(STP) import StripePayments @_spi(STP) import StripePaymentsUI @_spi(STP) import StripeUICore From 5f329e1cccf978c263fc74ebf45c5cb3ad66b108 Mon Sep 17 00:00:00 2001 From: Yuki Date: Mon, 21 Oct 2024 10:42:07 -0700 Subject: [PATCH 4/5] Remove UI test --- .../project.pbxproj | 4 ---- .../PaymentSheetEmbeddedUITests.swift | 23 ------------------- 2 files changed, 27 deletions(-) delete mode 100644 Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetEmbeddedUITests.swift diff --git a/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/project.pbxproj b/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/project.pbxproj index 2aeea7c994c..4ed23d11305 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/project.pbxproj +++ b/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/project.pbxproj @@ -53,7 +53,6 @@ B36A24145C97D73C981DDBAC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4B26C754925F8D2A5183B2E /* Main.storyboard */; }; B615E86F2CA4B267007D684C /* ExampleEmbeddedElementCheckoutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B615E86E2CA4B266007D684C /* ExampleEmbeddedElementCheckoutViewController.swift */; }; B641A4192C2BA25D00AE654A /* PaymentSheetVerticalUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B641A4182C2BA25D00AE654A /* PaymentSheetVerticalUITest.swift */; }; - B69470622CC33EAA0088DEED /* PaymentSheetEmbeddedUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69470612CC33EA40088DEED /* PaymentSheetEmbeddedUITests.swift */; }; B6CA975C2C486DE700DAE441 /* PaymentSheetLPMUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6CA975B2C486DE700DAE441 /* PaymentSheetLPMUITest.swift */; }; B6D6AAA666859847BB59749C /* PaymentSheet+AddressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61C5739DBE6405A4D9FD5F2 /* PaymentSheet+AddressTests.swift */; }; C39CA2EBE08EE768559A8FD1 /* StripeCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1FD1F5193E4A361EA9E8FED3 /* StripeCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -248,7 +247,6 @@ B54E58F2CA450CF49ECD5637 /* CustomerSheetTestPlaygroundController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerSheetTestPlaygroundController.swift; sourceTree = ""; }; B615E86E2CA4B266007D684C /* ExampleEmbeddedElementCheckoutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleEmbeddedElementCheckoutViewController.swift; sourceTree = ""; }; B641A4182C2BA25D00AE654A /* PaymentSheetVerticalUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetVerticalUITest.swift; sourceTree = ""; }; - B69470612CC33EA40088DEED /* PaymentSheetEmbeddedUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetEmbeddedUITests.swift; sourceTree = ""; }; B69C155A2B9FDCBD009CE667 /* PaymentSheet Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = "PaymentSheet Example.entitlements"; path = "PaymentSheet Example/PaymentSheet Example.entitlements"; sourceTree = ""; }; B6CA975B2C486DE700DAE441 /* PaymentSheetLPMUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetLPMUITest.swift; sourceTree = ""; }; B7AFD32B5EAD3BEEEC3D4260 /* ExampleSwiftUIPaymentSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleSwiftUIPaymentSheet.swift; sourceTree = ""; }; @@ -424,7 +422,6 @@ 5CA299EAF5914484195167EB /* PaymentSheetUITest.swift */, B6CA975B2C486DE700DAE441 /* PaymentSheetLPMUITest.swift */, B641A4182C2BA25D00AE654A /* PaymentSheetVerticalUITest.swift */, - B69470612CC33EA40088DEED /* PaymentSheetEmbeddedUITests.swift */, 36BB679CF53EEF943F0BAAC9 /* XCUITest+Utilities.swift */, 6A5CCDA32C0E6F5D003306A4 /* LinkPaymentControllerUITest.swift */, ); @@ -667,7 +664,6 @@ B641A4192C2BA25D00AE654A /* PaymentSheetVerticalUITest.swift in Sources */, 5AF35F9FA7106ACD4E4F360F /* PaymentSheetBillingCollectionUITests.swift in Sources */, 2CD71F097C7CD0D9BFC7499D /* PaymentSheetTestPlaygroundSettings.swift in Sources */, - B69470622CC33EAA0088DEED /* PaymentSheetEmbeddedUITests.swift in Sources */, 540F279CE5B41C122AD1DEF6 /* PaymentSheetUITest.swift in Sources */, 6A5CCDA42C0E6F5D003306A4 /* LinkPaymentControllerUITest.swift in Sources */, EE0FA73AAB80863583C69D70 /* XCUITest+Utilities.swift in Sources */, diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetEmbeddedUITests.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetEmbeddedUITests.swift deleted file mode 100644 index faf6d68ee46..00000000000 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetEmbeddedUITests.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// PayymentSheetEmbeddedUITests.swift -// PaymentSheet Example -// -// Created by Yuki Tokuhiro on 10/18/24. -// -import XCTest - -class PaymentSheetEmbeddedUITests: PaymentSheetUITestCase { - func testUpdate() { - var settings = PaymentSheetTestPlaygroundSettings.defaultValues() - settings.uiStyle = .embedded - settings.currency = .eur - loadPlayground(app, settings) - - app.buttons["Present embedded payment element"].waitForExistenceAndTap() - app.buttons["Card"].waitForExistenceAndTap() - - try! fillCardData(app) - app.buttons["Pay €50.99"].tap() - XCTAssertTrue(app.staticTexts["Success!"].waitForExistence(timeout: 10)) - } -} From 81dbf98460e5dbbcbefc344b8fbde459b5972d94 Mon Sep 17 00:00:00 2001 From: Yuki Date: Mon, 21 Oct 2024 11:03:14 -0700 Subject: [PATCH 5/5] More fixes --- .../PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift | 1 + .../PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift index 4fa7200d131..744c024a4a1 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift @@ -8,6 +8,7 @@ @_spi(STP) import StripePayments @_spi(STP) import StripePaymentsUI @_spi(STP) import StripeUICore +import UIKit extension EmbeddedPaymentElement { @MainActor diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift index 60ebb1495d8..b23d5c268f7 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentMethodsView.swift @@ -6,7 +6,6 @@ // import Foundation -@_spi(STP) import StripeCore @_spi(STP) import StripeUICore import UIKit