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 embedded to playground #4025

Merged
merged 6 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,38 @@ import Foundation
@_spi(EmbeddedPaymentMethodsViewBeta) import StripePaymentSheet
import UIKit

protocol EmbeddedPlaygroundViewControllerDelegate: AnyObject {
func didComplete(with result: PaymentSheetResult)
}

class EmbeddedPlaygroundViewController: UIViewController {

private let settings: PaymentSheetTestPlaygroundSettings
private let appearance: PaymentSheet.Appearance

weak var delegate: EmbeddedPlaygroundViewControllerDelegate?

private lazy var checkoutButton: UIButton = {
let checkoutButton = UIButton(type: .system)
checkoutButton.backgroundColor = appearance.primaryButton.backgroundColor ?? appearance.colors.primary
checkoutButton.layer.cornerRadius = 5.0
checkoutButton.clipsToBounds = true
checkoutButton.setTitle("Checkout", for: .normal)
checkoutButton.setTitleColor(.white, for: .normal)
checkoutButton.translatesAutoresizingMaskIntoConstraints = false
return checkoutButton
}()

init(settings: PaymentSheetTestPlaygroundSettings, appearance: PaymentSheet.Appearance) {
self.settings = settings
self.appearance = appearance
super.init(nibName: nil, bundle: nil)
}

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

override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor(dynamicProvider: { traitCollection in
Expand All @@ -20,9 +51,29 @@ class EmbeddedPlaygroundViewController: UIViewController {
return .systemBackground
})

var appearance = PaymentSheet.Appearance.default
appearance.paymentOptionView.style = .flatRadio
// TODO: pass in an embedded configuration built from `PaymentSheetTestPlaygroundSettings`
let paymentMethodsView = EmbeddedPaymentMethodsView(savedPaymentMethod: settings.customerMode == .returning ? .mockPaymentMethod : nil,
appearance: appearance,
shouldShowApplePay: settings.applePayEnabled == .on,
shouldShowLink: settings.linkMode == .link_pm)
paymentMethodsView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(paymentMethodsView)
self.view.addSubview(checkoutButton)

NSLayoutConstraint.activate([
paymentMethodsView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
paymentMethodsView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
paymentMethodsView.widthAnchor.constraint(equalTo: view.widthAnchor),
checkoutButton.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9),
checkoutButton.heightAnchor.constraint(equalToConstant: 50),
checkoutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
checkoutButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
])
}
}

extension STPPaymentMethod {
static var mockPaymentMethod: STPPaymentMethod? {
let amex =
[
"card": [
Expand All @@ -35,16 +86,6 @@ class EmbeddedPlaygroundViewController: UIViewController {
"type": "card",
"id": "preloaded_amex",
] as [String: Any]
let paymentMethod = STPPaymentMethod.decodedObject(fromAPIResponse: amex)

let paymentMethodsView = EmbeddedPaymentMethodsView(savedPaymentMethod: paymentMethod, appearance: appearance, shouldShowApplePay: true, shouldShowLink: true)
paymentMethodsView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(paymentMethodsView)

NSLayoutConstraint.activate([
paymentMethodsView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
paymentMethodsView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
paymentMethodsView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1),
])
return STPPaymentMethod.decodedObject(fromAPIResponse: amex)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ struct PaymentSheetTestPlayground: View {
// Note: Use group to work around XCode 14: "Extra Argument in Call" issue
// (each view can hold 10 direct subviews)
Group {
SettingView(setting: $playgroundController.settings.uiStyle)
SettingView(setting: $playgroundController.settings.layout)
SettingView(setting: uiStyleBinding)
SettingView(setting: $playgroundController.settings.layout).disabled(playgroundController.settings.uiStyle == .embedded)
SettingView(setting: $playgroundController.settings.shippingInfo)
SettingView(setting: $playgroundController.settings.applePayEnabled)
SettingView(setting: $playgroundController.settings.applePayButtonType)
Expand Down Expand Up @@ -90,7 +90,7 @@ struct PaymentSheetTestPlayground: View {
})
}
SettingView(setting: $playgroundController.settings.mode)
SettingPickerView(setting: $playgroundController.settings.integrationType)
SettingPickerView(setting: integrationTypeBinding).disabled(playgroundController.settings.uiStyle == .embedded)
SettingView(setting: $playgroundController.settings.customerKeyType)
SettingView(setting: customerModeBinding)
SettingPickerView(setting: $playgroundController.settings.amount)
Expand Down Expand Up @@ -234,12 +234,38 @@ struct PaymentSheetTestPlayground: View {
}
}
}

var uiStyleBinding: Binding<PaymentSheetTestPlaygroundSettings.UIStyle> {
Binding<PaymentSheetTestPlaygroundSettings.UIStyle> {
return playgroundController.settings.uiStyle
} set: { newUIStyle in
// If we switch to embedded set confirmation type to deferred CSC if in intent first confirmation type
if newUIStyle == .embedded && playgroundController.settings.integrationType == .normal {
playgroundController.settings.integrationType = .deferred_csc
}

playgroundController.settings.uiStyle = newUIStyle
}
}

var integrationTypeBinding: Binding<PaymentSheetTestPlaygroundSettings.IntegrationType> {
Binding<PaymentSheetTestPlaygroundSettings.IntegrationType> {
return playgroundController.settings.integrationType
} set: { newIntegrationType in
// If switching to CSC and embedded is selected, reset to PaymentSheet
if newIntegrationType == .normal && playgroundController.settings.uiStyle == .embedded {
playgroundController.settings.uiStyle = .paymentSheet
}
playgroundController.settings.integrationType = newIntegrationType
}
}
}

@available(iOS 14.0, *)
struct PaymentSheetButtons: View {
@EnvironmentObject var playgroundController: PlaygroundController
@State var psIsPresented: Bool = false
@State var embeddedIsPresented: Bool = false
@State var psFCOptionsIsPresented: Bool = false
@State var psFCIsConfirming: Bool = false

Expand Down Expand Up @@ -298,7 +324,7 @@ struct PaymentSheetButtons: View {
ExamplePaymentStatusView(result: result)
}
}
} else {
} else if playgroundController.settings.uiStyle == .flowController {
VStack {
HStack {
Text("PaymentSheet.FlowController")
Expand Down Expand Up @@ -355,6 +381,55 @@ struct PaymentSheetButtons: View {
ExamplePaymentStatusView(result: result)
}
}
} else if playgroundController.settings.uiStyle == .embedded {
VStack {
HStack {
Text("Embedded mobile payment element")
.font(.subheadline.smallCaps())
Spacer()
if playgroundController.isLoading {
ProgressView()
} else {
if playgroundController.settings != playgroundController.currentlyRenderedSettings {
StaleView()
}
Button {
reloadPlaygroundController()
} label: {
Image(systemName: "arrow.clockwise.circle")
}
.accessibility(identifier: "Reload")
.frame(alignment: .topLeading)
}
}.padding(.horizontal)

if let _ = playgroundController.embeddedPlaygroundController,
playgroundController.lastPaymentResult == nil || playgroundController.lastPaymentResult?.shouldAllowPresentingPaymentSheet() ?? false {
HStack {
Button {
embeddedIsPresented = true
playgroundController.presentEmbedded()
} label: {
Text("Present embedded payment element")
}
Spacer()
Button {
playgroundController.didTapShippingAddressButton()
} label: {
Text("\(playgroundController.addressDetails?.localizedDescription ?? "Address")")
.accessibility(identifier: "Address")
}
}
.padding()
} else {
Text("Embedded payment element is nil")
.foregroundColor(.gray)
.padding()
}
if let result = playgroundController.lastPaymentResult {
ExamplePaymentStatusView(result: result)
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable {

case paymentSheet
case flowController
case embedded
}

enum Mode: String, PickerEnum {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import UIKit
class PlaygroundController: ObservableObject {
@Published var paymentSheetFlowController: PaymentSheet.FlowController?
@Published var paymentSheet: PaymentSheet?
@Published var embeddedPlaygroundController: EmbeddedPlaygroundViewController?
@Published var settings: PaymentSheetTestPlaygroundSettings
@Published var currentlyRenderedSettings: PaymentSheetTestPlaygroundSettings
@Published var addressDetails: AddressViewController.AddressDetails?
Expand Down Expand Up @@ -460,6 +461,7 @@ extension PlaygroundController {
paymentSheetFlowController = nil
addressViewController = nil
paymentSheet = nil
embeddedPlaygroundController = nil
lastPaymentResult = nil
isLoading = true
let settingsToLoad = self.settings
Expand Down Expand Up @@ -551,7 +553,7 @@ extension PlaygroundController {
self.buildPaymentSheet()
self.isLoading = false
self.currentlyRenderedSettings = self.settings
} else {
} else if self.settings.uiStyle == .flowController {
let completion: (Result<PaymentSheet.FlowController, Error>) -> Void = { result in
self.currentlyRenderedSettings = self.settings
switch result {
Expand Down Expand Up @@ -594,6 +596,10 @@ extension PlaygroundController {
completion: completion
)
}
} else if self.settings.uiStyle == .embedded {
self.embeddedPaymentElement()
self.isLoading = false
self.currentlyRenderedSettings = self.settings
}
}
}
Expand Down Expand Up @@ -797,3 +803,29 @@ class AnalyticsLogObserver: ObservableObject {
/// All analytic events sent by the SDK since the playground was loaded.
@Published var analyticsLog: [[String: Any]] = []
}


// MARK: Embedded helpers
extension PlaygroundController: EmbeddedPlaygroundViewControllerDelegate {
func embeddedPaymentElement() {
embeddedPlaygroundController = EmbeddedPlaygroundViewController(settings: settings, appearance: appearance)
embeddedPlaygroundController?.delegate = self
}

func presentEmbedded() {
guard let embeddedPlaygroundController else { return }
let closeButton = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(dismissEmbedded))
embeddedPlaygroundController.navigationItem.leftBarButtonItem = closeButton

let navController = UINavigationController(rootViewController: embeddedPlaygroundController)
rootViewController.present(navController, animated: true)
}

@objc func dismissEmbedded() {
embeddedPlaygroundController?.dismiss(animated: true, completion: nil)
}

func didComplete(with result: StripePaymentSheet.PaymentSheetResult) {
lastPaymentResult = result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,29 +93,21 @@
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hIk-Ur-mhf">
<rect key="frame" x="0.0" y="108" width="222" height="0.0"/>
<rect key="frame" x="0.0" y="108" width="222" height="16.5"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="CustomerSheet (SwiftUI)"/>
<connections>
<segue destination="AjW-Xm-4P3" kind="show" destinationCreationSelector="showSwiftUICusotmerSheetSwiftUI:" id="ODf-7Z-F15"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" id="Ysb-4K-dUG">
<rect key="frame" x="0.0" y="120" width="222" height="0.0"/>
<rect key="frame" x="0.0" y="136.5" width="222" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="LinkPaymentController"/>
<connections>
<segue destination="jqF-43-4W3" kind="show" id="I4Q-i5-Ogf"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="PqJ-Uq-tvb">
<rect key="frame" x="0.0" y="132" width="222" height="34.5"/>
<state key="normal" title="Button"/>
<buttonConfiguration key="configuration" style="plain" title="Embedded playground"/>
<connections>
<segue destination="UO7-ym-FxO" kind="show" id="8Y8-nE-pda"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
Expand Down Expand Up @@ -452,22 +444,6 @@
</objects>
<point key="canvasLocation" x="1400" y="877"/>
</scene>
<!--Embedded Playground View Controller-->
<scene sceneID="8b9-XO-txM">
<objects>
<viewController id="UO7-ym-FxO" customClass="EmbeddedPlaygroundViewController" customModule="PaymentSheetExample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="T1q-Uc-aU5">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="HQL-0B-cUt"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
<navigationItem key="navigationItem" id="MhB-nP-jRJ"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="8fv-x9-cLD" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2245" y="859"/>
</scene>
<!--Checkout (Custom)-->
<scene sceneID="QR4-jL-qym">
<objects>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2289,6 +2289,9 @@ class PaymentSheetLinkUITests: PaymentSheetUITestCase {
case .flowController:
app.buttons["Continue"].tap()
app.buttons["Confirm"].waitForExistenceAndTap()
case .embedded:
// TODO(porter) Fill in embedded UI test steps
break
}
XCTAssertTrue(app.staticTexts["Success!"].waitForExistence(timeout: 10.0))
// Roundabout way to validate that signup completed successfully
Expand Down
Loading