Skip to content

Commit

Permalink
Add initial embedded API and change spi private beta name
Browse files Browse the repository at this point in the history
  • Loading branch information
yuki-stripe committed Sep 25, 2024
1 parent 00c3032 commit af5268f
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//

import Foundation
@_spi(EmbeddedPaymentMethodsViewBeta) import StripePaymentSheet
@_spi(EmbeddedPaymentElementPrivateBeta) import StripePaymentSheet
import UIKit

protocol EmbeddedPlaygroundViewControllerDelegate: AnyObject {
Expand Down
12 changes: 12 additions & 0 deletions StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@
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 */; };
B615E8712CA4CBEE007D684C /* EmbeddedPaymentElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = B615E8702CA4CBEE007D684C /* EmbeddedPaymentElement.swift */; };
B615E8732CA4CC04007D684C /* EmbeddedPaymentElementConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B615E8722CA4CC04007D684C /* EmbeddedPaymentElementConfiguration.swift */; };
B615E8752CA4CC38007D684C /* EmbeddedPaymentElementDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B615E8742CA4CC38007D684C /* EmbeddedPaymentElementDelegate.swift */; };
B61E2C202C5C44FE0045B5CF /* PaymentSheetAnalyticsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61E2C1F2C5C44FE0045B5CF /* PaymentSheetAnalyticsHelper.swift */; };
B626EE932BF2872200B05B05 /* PaymentMethodTypeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B626EE922BF2872200B05B05 /* PaymentMethodTypeImageView.swift */; };
B63B2CF12BF8313D003810F3 /* VerticalPaymentMethodListViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63B2CF02BF8313D003810F3 /* VerticalPaymentMethodListViewControllerTest.swift */; };
Expand Down Expand Up @@ -570,6 +573,9 @@
B4CD2ADFCF34E3F4A35255DC /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Localizable.strings"; sourceTree = "<group>"; };
B51F9A38D82C20E576DEF098 /* String+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localized.swift"; sourceTree = "<group>"; };
B54274B9DEB3F1B0906127D1 /* et-EE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "et-EE"; path = "et-EE.lproj/Localizable.strings"; sourceTree = "<group>"; };
B615E8702CA4CBEE007D684C /* EmbeddedPaymentElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedPaymentElement.swift; sourceTree = "<group>"; };
B615E8722CA4CC04007D684C /* EmbeddedPaymentElementConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedPaymentElementConfiguration.swift; sourceTree = "<group>"; };
B615E8742CA4CC38007D684C /* EmbeddedPaymentElementDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedPaymentElementDelegate.swift; sourceTree = "<group>"; };
B61E2C1F2C5C44FE0045B5CF /* PaymentSheetAnalyticsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetAnalyticsHelper.swift; sourceTree = "<group>"; };
B61FFE76D0960C7F1E34B405 /* PaymentSheetAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetAppearance.swift; sourceTree = "<group>"; };
B626EE922BF2872200B05B05 /* PaymentMethodTypeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentMethodTypeImageView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -904,6 +910,9 @@
6180A5C52C82434A009D1536 /* Embedded */ = {
isa = PBXGroup;
children = (
B615E8702CA4CBEE007D684C /* EmbeddedPaymentElement.swift */,
B615E8722CA4CC04007D684C /* EmbeddedPaymentElementConfiguration.swift */,
B615E8742CA4CC38007D684C /* EmbeddedPaymentElementDelegate.swift */,
6180A5C02C8222A9009D1536 /* EmbeddedPaymentMethodsView.swift */,
6180A5C62C824377009D1536 /* RadioButton.swift */,
6180A5CA2C8249D2009D1536 /* UIStackView+Separator.swift */,
Expand Down Expand Up @@ -1809,6 +1818,7 @@
1ECC1086460E57AE75F18FBF /* OperationDebouncer.swift in Sources */,
E236FE31A51D130F93F9299B /* LinkAccountContext.swift in Sources */,
D0B9FBCB359A7D774B98D19E /* LinkCookieKey.swift in Sources */,
B615E8752CA4CC38007D684C /* EmbeddedPaymentElementDelegate.swift in Sources */,
6B28A6B92BE9712500B47DBF /* CustomerSessionAdapter.swift in Sources */,
B306EA3F66D07CCABF17CB9C /* LinkInlineSignupViewModel.swift in Sources */,
235687C1FBE7417C48F99EE3 /* LinkLegalTermsView.swift in Sources */,
Expand All @@ -1824,6 +1834,7 @@
F8411A9B28F2E2D927ABAE7C /* CustomerAdapter.swift in Sources */,
31699A812BE183B30048677F /* DownloadManager.swift in Sources */,
C113FE145760C5CE94536872 /* CustomerPaymentOption.swift in Sources */,
B615E8712CA4CBEE007D684C /* EmbeddedPaymentElement.swift in Sources */,
466755A0F05CB0EF281E9FAF /* UserDefaults+StripePaymentSheet.swift in Sources */,
FC72575C4CCAF5E317C5D299 /* CustomerAddPaymentMethodViewController.swift in Sources */,
9750C45BC44F4D0F3207A795 /* CustomerSavedPaymentMethodsCollectionViewController.swift in Sources */,
Expand All @@ -1842,6 +1853,7 @@
68E3CF21A7E1525CA05BA260 /* ConnectionsElement.swift in Sources */,
46DB5D39B3B76C08AE2C83C8 /* PaymentMethodElement.swift in Sources */,
335A19D93A5979557DB4CA4D /* PaymentMethodElementWrapper.swift in Sources */,
B615E8732CA4CC04007D684C /* EmbeddedPaymentElementConfiguration.swift in Sources */,
00A3805F91E6F903FA677393 /* SimpleMandateElement.swift in Sources */,
DFA10770E494AFB895BA4EE2 /* TextFieldElement+Card.swift in Sources */,
B6B3481CBA798CF22EE8411A /* TextFieldElement+IBAN.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// EmbeddedPaymentElement.swift
// StripePaymentSheet
//
// Created by Yuki Tokuhiro on 9/25/24.
//

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

/// An object that manages a view that displays payment methods and completes a checkout.
@_spi(EmbeddedPaymentElementPrivateBeta)
public class EmbeddedPaymentElement {

/// A view that displays payment methods. It can present a sheet to collect more details or display saved payment methods.
public let view: UIView

/// A view controller to present on.
public var presentingViewController: UIViewController?

/// See `EmbeddedPaymentElementDelegate`.
public weak var delegate: EmbeddedPaymentElementDelegate?

public struct PaymentOptionDisplayData {
/// An image representing a payment method; e.g. the Apple Pay logo or a VISA logo
public let image: UIImage
/// A user facing string representing the payment method; e.g. "Apple Pay" or "····4242" for a card
public let label: String
/// The billing details associated with the customer's desired payment method
public let billingDetails: PaymentSheet.BillingDetails?
/// A string representation of the customer's desired payment method
/// - If this is a Stripe payment method, see https://stripe.com/docs/api/payment_methods/object#payment_method_object-type for possible values.
/// - If this is an external payment method, see https://stripe.com/docs/payments/external-payment-methods?platform=ios#available-external-payment-methods for possible values.
/// - If this is Apple Pay, the value is "apple_pay"
public let paymentMethodType: String
/// If you set `configuration.hidesMandateText = true`, this text must be displayed to the customer near your “Buy” button to comply with regulations.
public let mandateText: NSAttributedString
}

/// The customer's currently selected payment option.
public var paymentOption: PaymentOptionDisplayData? { return nil /* computed */ }

/// An asynchronous failable initializer
/// This loads the Customer's payment methods, their default payment method, etc.
/// - Parameter intentConfiguration: Information about the PaymentIntent or SetupIntent you will create later to complete the checkout.
/// - Parameter configuration: Configuration for the PaymentSheet. e.g. your business name, customer details, etc.
/// - Returns: A valid EmbeddedPaymentElement instance
/// - Throws: An error if loading failed.
public static func create(
intentConfiguration: IntentConfiguration,
configuration: Configuration
) async throws -> EmbeddedPaymentElement {
let dummyView = await EmbeddedPaymentMethodsView(
savedPaymentMethod: nil,
appearance: .default,
shouldShowApplePay: true,
shouldShowLink: true
)
return .init(view: dummyView)
}

public enum UpdateResult {
case succeeded
case canceled
case failed(error: Error)
}
/// Call this method when the IntentConfiguration values you used to initialize `EmbeddedPaymentElement` (amount, currency, etc.) change.
/// This ensures the appropriate payment methods are displayed, collect the right fields, etc.
/// - Parameter intentConfiguration: An updated IntentConfiguration.
/// - Returns: The result of the update. Any calls made to `update` before this call that are still in progress will return a `.canceled` result.
/// - Note: Upon completion, `paymentOption` may become nil if it's no longer available.
public func update(
intentConfiguration: IntentConfiguration
) async -> UpdateResult {
return .canceled
}

/// - Returns: The result of the payment after any presented view controllers are dismissed.
public func confirm() async -> EmbeddedPaymentElementResult {
return .canceled
}

// MARK: - Internal

private init(view: UIView, delegate: EmbeddedPaymentElementDelegate? = nil) {
self.view = view
self.delegate = delegate
}
}

// MARK: - Typealiases

@_spi(STP) public typealias EmbeddedPaymentElementResult = PaymentSheetResult
extension EmbeddedPaymentElement {
public typealias IntentConfiguration = PaymentSheet.IntentConfiguration
public typealias UserInterfaceStyle = PaymentSheet.UserInterfaceStyle
public typealias SavePaymentMethodOptInBehavior = PaymentSheet.SavePaymentMethodOptInBehavior
public typealias ApplePayConfiguration = PaymentSheet.ApplePayConfiguration
public typealias CustomerConfiguration = PaymentSheet.CustomerConfiguration
public typealias BillingDetails = PaymentSheet.BillingDetails
public typealias Address = PaymentSheet.Address
public typealias BillingDetailsCollectionConfiguration = PaymentSheet.BillingDetailsCollectionConfiguration
public typealias ExternalPaymentMethodConfiguration = PaymentSheet.ExternalPaymentMethodConfiguration
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//
// EmbeddedPaymentElementConfiguration.swift
// StripePaymentSheet
//
// Created by Yuki Tokuhiro on 9/25/24.
//

import UIKit
@_spi(STP) import StripeCore

extension EmbeddedPaymentElement {
public struct Configuration {
/// If true, allows payment methods that do not move money at the end of the checkout. Defaults to false.
/// - Description: Some payment methods can't guarantee you will receive funds from your customer at the end of the checkout because they take time to settle (eg. most bank debits, like SEPA or ACH) or require customer action to complete (e.g. OXXO, Konbini, Boleto). If this is set to true, make sure your integration listens to webhooks for notifications on whether a payment has succeeded or not.
/// - Seealso: https://stripe.com/docs/payments/payment-methods#payment-notification
public var allowsDelayedPaymentMethods: Bool = false

/// If `true`, allows payment methods that require a shipping address, like Afterpay and Affirm. Defaults to `false`.
/// Set this to `true` if you collect shipping addresses and set `Configuration.shippingDetails` or set `shipping` details directly on the PaymentIntent.
/// - Note: PaymentSheet considers this property `true` and allows payment methods that require a shipping address if `shipping` details are present on the PaymentIntent when PaymentSheet loads.
public var allowsPaymentMethodsRequiringShippingAddress: Bool = false

/// The APIClient instance used to make requests to Stripe
public var apiClient: STPAPIClient = STPAPIClient.shared

/// Configuration related to Apple Pay
/// If set, PaymentSheet displays Apple Pay as a payment option
public var applePay: ApplePayConfiguration?

/// The color of the Buy or Add button. Defaults to `.systemBlue` when `nil`.
public var primaryButtonColor: UIColor? {
get {
return appearance.primaryButton.backgroundColor
}

set {
appearance.primaryButton.backgroundColor = newValue
}
}

/// The label to use for the primary button.
///
/// If not set, Payment Sheet will display suitable default labels
/// for payment and setup intents.
public var primaryButtonLabel: String?

private var styleRawValue: Int = 0 // SheetStyle.automatic.rawValue
/// The color styling to use for PaymentSheet UI
/// Default value is SheetStyle.automatic
/// @see SheetStyle
public var style: UserInterfaceStyle { // stored properties can't be marked @available which is why this uses the styleRawValue private var
get {
return UserInterfaceStyle(rawValue: styleRawValue)!
}
set {
styleRawValue = newValue.rawValue
}
}

/// Configuration related to the Stripe Customer
/// If set, the customer can select a previously saved payment method within PaymentSheet
public var customer: CustomerConfiguration?

/// Your customer-facing business name.
/// The default value is the name of your app, using CFBundleDisplayName or CFBundleName
public var merchantDisplayName: String = Bundle.displayName ?? ""

/// A URL that redirects back to your app that PaymentSheet can use to auto-dismiss
/// web views used for additional authentication, e.g. 3DS2
public var returnURL: String?

/// PaymentSheet pre-populates fields with the values provided.
/// If `billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod` is `true`, these values will
/// be attached to the payment method even if they are not collected by the PaymentSheet UI.
public var defaultBillingDetails: BillingDetails = BillingDetails()

/// PaymentSheet offers users an option to save some payment methods for later use.
/// Default value is .automatic
/// @see SavePaymentMethodOptInBehavior
public var savePaymentMethodOptInBehavior: SavePaymentMethodOptInBehavior = .automatic

/// Describes the appearance of PaymentSheet
public var appearance = PaymentSheet.Appearance.default

/// A closure that returns the customer's shipping details.
/// This is used to display a "Billing address is same as shipping" checkbox if `defaultBillingDetails` is not provided
/// If `name` and `line1` are populated, it's also [attached to the PaymentIntent](https://stripe.com/docs/api/payment_intents/object#payment_intent_object-shipping) during payment.
public var shippingDetails: () -> AddressViewController.AddressDetails? = { return nil }

/// The list of preferred networks that should be used to process payments made with a co-branded card.
/// This value will only be used if your user hasn't selected a network themselves.
public var preferredNetworks: [STPCardBrand]? {
didSet {
guard let preferredNetworks = preferredNetworks else { return }
assert(Set<STPCardBrand>(preferredNetworks).count == preferredNetworks.count,
"preferredNetworks must not contain any duplicate card brands")
}
}

/// Override country for test purposes
@_spi(STP) public var userOverrideCountry: String?

/// Describes how billing details should be collected.
/// All values default to `automatic`.
/// If `never` is used for a required field for the Payment Method used during checkout,
/// you **must** provide an appropriate value as part of `defaultBillingDetails`.
public var billingDetailsCollectionConfiguration = BillingDetailsCollectionConfiguration()

/// Optional configuration to display a custom message when a saved payment method is removed.
public var removeSavedPaymentMethodMessage: String?

/// Configuration for external payment methods.
public var externalPaymentMethodConfiguration: ExternalPaymentMethodConfiguration?

/// By default, PaymentSheet will use a dynamic ordering that optimizes payment method display for the customer.
/// You can override the default order in which payment methods are displayed in PaymentSheet with a list of payment method types.
/// See https://stripe.com/docs/api/payment_methods/object#payment_method_object-type for the list of valid types. You may also pass external payment methods.
/// - Example: ["card", "external_paypal", "klarna"]
/// - Note: If you omit payment methods from this list, they’ll be automatically ordered by Stripe after the ones you provide. Invalid payment methods are ignored.
public var paymentMethodOrder: [String]?

/// This is an experimental feature that may be removed at any time.
/// If true (the default), the customer can delete all saved payment methods.
/// If false, the customer can't delete if they only have one saved payment method remaining.
@_spi(ExperimentalAllowsRemovalOfLastSavedPaymentMethodAPI) public var allowsRemovalOfLastSavedPaymentMethod = true

/// The view can display payment methods like “Card” that, when tapped, open a form sheet where customers enter their payment method details. The sheet has a button at the bottom. `FormSheetAction` enumerates the actions the button can perform.
public enum FormSheetAction {
/// The button says “Pay” or “Setup”. When tapped, we confirm the payment or setup in the form sheet.
/// - Parameter completion: Called with the result of the payment or setup.
case confirm(
completion: (EmbeddedPaymentElementResult) -> Void
)

/// The button says “Continue”. When tapped, the form sheet closes.
case `continue`
}

/// The view can display payment methods like “Card” that, when tapped, open a sheet where customers enter their payment method details. The sheet has a button at the bottom. `formSheetAction` controls the action the button performs.
public var formSheetAction: FormSheetAction

/// Controls whether the view displays mandate text at the bottom for payment methods that require it. If set to `true`, your integration must display `PaymentOptionDisplayData.mandateText` to the customer near your “Buy” button to comply with regulations.
public var hidesMandateText: Bool = false

/// Initializes a Configuration with default values
public init(formSheetAction: FormSheetAction) {
self.formSheetAction = formSheetAction
}
}
}
Loading

0 comments on commit af5268f

Please sign in to comment.