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

Introduce IntegrationConfigurable #4081

Merged
merged 7 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -119,6 +119,7 @@
61C0D3B8C63EB4558AB74A7E /* StripePayments.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A1C7CFA5C9C1A8A73CFA1C0 /* StripePayments.framework */; };
61CB0BD02BED985100E24A4C /* VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61CBE6672BED97EE005F7FEB /* VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift */; };
61CBE6662BED9749005F7FEB /* VerticalSavedPaymentMethodsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61CBE6652BED9749005F7FEB /* VerticalSavedPaymentMethodsViewController.swift */; };
61D842442CAC50C5009D2D51 /* IntegrationConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D842432CAC50C5009D2D51 /* IntegrationConfigurable.swift */; };
61D8688E2C06553E001FAD84 /* RightAccessoryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D8688D2C06553E001FAD84 /* RightAccessoryButton.swift */; };
61FB6BCD2C8901B200F8E074 /* EmbeddedPaymentMethodsViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FB6BCC2C8901B200F8E074 /* EmbeddedPaymentMethodsViewSnapshotTests.swift */; };
623C2D9F87929D6DA9C09E23 /* STPCameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39B31D0B890A4F8E4819B15 /* STPCameraView.swift */; };
Expand Down Expand Up @@ -471,6 +472,7 @@
619AF0882BF56F9100D1C981 /* VerticalSavedPaymentMethodsViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalSavedPaymentMethodsViewControllerTests.swift; sourceTree = "<group>"; };
61CBE6652BED9749005F7FEB /* VerticalSavedPaymentMethodsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalSavedPaymentMethodsViewController.swift; sourceTree = "<group>"; };
61CBE6672BED97EE005F7FEB /* VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift; sourceTree = "<group>"; };
61D842432CAC50C5009D2D51 /* IntegrationConfigurable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationConfigurable.swift; sourceTree = "<group>"; };
61D8688D2C06553E001FAD84 /* RightAccessoryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RightAccessoryButton.swift; sourceTree = "<group>"; };
61FB6BCC2C8901B200F8E074 /* EmbeddedPaymentMethodsViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedPaymentMethodsViewSnapshotTests.swift; sourceTree = "<group>"; };
62CE362B80042827F47ABC3F /* AffirmCopyLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AffirmCopyLabel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -838,6 +840,7 @@
B9A9FDAE66ADA08D85D74E19 /* PaymentSheet+SwiftUI.swift */,
B61FFE76D0960C7F1E34B405 /* PaymentSheetAppearance.swift */,
C46CB5AB992F8EEFE4E5460A /* PaymentSheetConfiguration.swift */,
61D842432CAC50C5009D2D51 /* IntegrationConfigurable.swift */,
0DF4D51EEAB1092637BE144E /* PaymentSheetDeferredValidator.swift */,
E6DDBBAAC2892467CED23402 /* PaymentSheetError.swift */,
58A85D630BDEA7408391EB8B /* PaymentSheetFlowController.swift */,
Expand Down Expand Up @@ -1793,6 +1796,7 @@
B8A7575878C5124CF5482097 /* VerificationSession.swift in Sources */,
9326393E775D29F8C661624B /* STPAPIClient+PaymentSheet.swift in Sources */,
AA3A96D74B1659CB5725E95F /* CardExpiryDate.swift in Sources */,
61D842442CAC50C5009D2D51 /* IntegrationConfigurable.swift in Sources */,
64DE5688E4FBE92E1F49810C /* ExternalPaymentMethod.swift in Sources */,
229A4A578609A3711F02682E /* STPCardBrandChoice.swift in Sources */,
3EDFACA133567159875143C5 /* STPElementsSession.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Foundation
final class PaymentSheetAnalyticsHelper {
let analyticsClient: STPAnalyticsClient
let isCustom: Bool
let configuration: PaymentSheet.Configuration
let configuration: IntegrationConfigurable
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should keep this PaymentSheet.Configuration? I'm not sure this class can reasonably also handle analytics for EMPE

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah looking at the class more, it'll be pretty hard. We'll want some embedded specific one.


// Vars set later as PaymentSheet successfully loads, etc.
var intent: Intent?
Expand All @@ -22,7 +22,7 @@ final class PaymentSheetAnalyticsHelper {

init(
isCustom: Bool,
configuration: PaymentSheet.Configuration,
configuration: IntegrationConfigurable,
analyticsClient: STPAnalyticsClient = .sharedClient
) {
self.isCustom = isCustom
Expand Down Expand Up @@ -354,3 +354,36 @@ extension PaymentSheet.Configuration {
return payload
}
}

extension EmbeddedPaymentElement.Configuration {
/// Serializes the configuration into a safe dictionary containing no PII for analytics logging
var analyticPayload: [String: Any] {
var payload = [String: Any]()
payload["allows_delayed_payment_methods"] = allowsDelayedPaymentMethods
payload["apple_pay_config"] = applePay != nil
payload["style"] = style.rawValue
payload["customer"] = customer != nil
payload["customer_access_provider"] = customer?.customerAccessProvider.analyticValue
payload["return_url"] = returnURL != nil
payload["default_billing_details"] = defaultBillingDetails != PaymentSheet.BillingDetails()
payload["save_payment_method_opt_in_behavior"] = savePaymentMethodOptInBehavior.description
payload["appearance"] = appearance.analyticPayload
payload["billing_details_collection_configuration"] = billingDetailsCollectionConfiguration.analyticPayload
payload["preferred_networks"] = preferredNetworks?.map({ STPCardBrandUtilities.apiValue(from: $0) }).joined(separator: ", ")
payload["form_sheet_action"] = formSheetAction.analyticValue
payload["hide_mandate_text"] = hidesMandateText
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note these new embedded only params.


return payload
}
}

extension EmbeddedPaymentElement.Configuration.FormSheetAction {
var analyticValue: String {
switch self {
case .confirm:
return "confirm"
case .continue:
return "continue"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ extension STPAPIClient {
func retrieveElementsSession(
paymentIntentClientSecret: String,
clientDefaultPaymentMethod: String?,
configuration: PaymentSheet.Configuration
configuration: IntegrationConfigurable
) async throws -> (STPPaymentIntent, STPElementsSession) {
let elementsSession = try await APIRequest<STPElementsSession>.getWith(
self,
Expand All @@ -90,7 +90,7 @@ extension STPAPIClient {
func retrieveElementsSession(
setupIntentClientSecret: String,
clientDefaultPaymentMethod: String?,
configuration: PaymentSheet.Configuration
configuration: IntegrationConfigurable
) async throws -> (STPSetupIntent, STPElementsSession) {
let elementsSession = try await APIRequest<STPElementsSession>.getWith(
self,
Expand All @@ -113,7 +113,7 @@ extension STPAPIClient {
func retrieveDeferredElementsSession(
withIntentConfig intentConfig: PaymentSheet.IntentConfiguration,
clientDefaultPaymentMethod: String?,
configuration: PaymentSheet.Configuration
configuration: IntegrationConfigurable
) async throws -> STPElementsSession {
let parameters = makeElementsSessionsParams(mode: .deferredIntent(intentConfig),
epmConfiguration: configuration.externalPaymentMethodConfiguration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ class CustomerAddPaymentMethodViewController: UIViewController {
isSettingUp: true,
countryCode: nil,
savePaymentMethodConsentBehavior: savePaymentMethodConsentBehavior,
analyticsHelper: .init(isCustom: false, configuration: .init()) // Just use a dummy analytics helper; we don't look at these analytics.
analyticsHelper: .init(isCustom: false, configuration: PaymentSheet.Configuration.init()) // Just use a dummy analytics helper; we don't look at these analytics.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

).make()
formElement.delegate = self
return formElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ extension CustomerSheetDataSource {
case .customerAdapter:
return try await configuration.apiClient.retrieveElementsSession(setupIntentClientSecret: setupIntentClientSecret,
clientDefaultPaymentMethod: nil,
configuration: .init())
configuration: PaymentSheet.Configuration.init())
case .customerSession(let customerSessionAdapter):
return try await customerSessionAdapter.elementsSession(setupIntentClientSecret: setupIntentClientSecret)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,21 @@ public class EmbeddedPaymentElement {
intentConfiguration: IntentConfiguration,
configuration: Configuration
) async throws -> EmbeddedPaymentElement {
// TODO(porter) MOBILESDK-2533 Make a protocol for our configurations
let paymentSheetConfiguration = configuration.makePaymentSheetConfiguration()

// TODO(porter) When we do analytics decide how to handle `isCustom`
let analyticsHelper = PaymentSheetAnalyticsHelper(isCustom: true, configuration: paymentSheetConfiguration)
let analyticsHelper = PaymentSheetAnalyticsHelper(isCustom: true, configuration: configuration)
AnalyticsHelper.shared.generateSessionID()

let loadResult = try await PaymentSheetLoader.load(mode: .deferredIntent(intentConfiguration),
configuration: paymentSheetConfiguration,
configuration: configuration,
analyticsHelper: analyticsHelper,
integrationShape: .embedded)

let paymentMethodTypes = PaymentSheet.PaymentMethodType.filteredPaymentMethodTypes(from: .deferredIntent(intentConfig: intentConfiguration),
elementsSession: loadResult.elementsSession,
configuration: paymentSheetConfiguration,
configuration: configuration,
logAvailability: true)
let shouldShowApplePay = PaymentSheet.isApplePayEnabled(elementsSession: loadResult.elementsSession, configuration: paymentSheetConfiguration)
let shouldShowLink = PaymentSheet.isLinkEnabled(elementsSession: loadResult.elementsSession, configuration: paymentSheetConfiguration)
let shouldShowApplePay = PaymentSheet.isApplePayEnabled(elementsSession: loadResult.elementsSession, configuration: configuration)
let shouldShowLink = PaymentSheet.isLinkEnabled(elementsSession: loadResult.elementsSession, configuration: configuration)
let savedPaymentMethodAccessoryType = await RowButton.RightAccessoryButton.getAccessoryButtonType(
savedPaymentMethodsCount: loadResult.savedPaymentMethods.count,
isFirstCardCoBranded: loadResult.savedPaymentMethods.first?.isCoBrandedCard ?? false,
Expand Down Expand Up @@ -221,41 +218,3 @@ extension EmbeddedPaymentElement {
public typealias BillingDetailsCollectionConfiguration = PaymentSheet.BillingDetailsCollectionConfiguration
public typealias ExternalPaymentMethodConfiguration = PaymentSheet.ExternalPaymentMethodConfiguration
}

// TODO(porter) MOBILESDK-2533 Create a protocol for the commonalities between PaymentSheet.Configuration <> EmbeddedPaymentElement.Configuration
extension EmbeddedPaymentElement.Configuration {
func makePaymentSheetConfiguration() -> PaymentSheet.Configuration {
var paymentConfig = PaymentSheet.Configuration()

paymentConfig.allowsDelayedPaymentMethods = allowsDelayedPaymentMethods
paymentConfig.allowsPaymentMethodsRequiringShippingAddress = allowsPaymentMethodsRequiringShippingAddress
paymentConfig.apiClient = apiClient
paymentConfig.applePay = applePay
paymentConfig.primaryButtonColor = primaryButtonColor
paymentConfig.primaryButtonLabel = primaryButtonLabel
paymentConfig.style = style
paymentConfig.customer = customer
paymentConfig.merchantDisplayName = merchantDisplayName
paymentConfig.returnURL = returnURL
paymentConfig.defaultBillingDetails = defaultBillingDetails
paymentConfig.savePaymentMethodOptInBehavior = savePaymentMethodOptInBehavior
paymentConfig.appearance = appearance
paymentConfig.shippingDetails = shippingDetails
paymentConfig.preferredNetworks = preferredNetworks
paymentConfig.userOverrideCountry = userOverrideCountry
paymentConfig.billingDetailsCollectionConfiguration = billingDetailsCollectionConfiguration
paymentConfig.removeSavedPaymentMethodMessage = removeSavedPaymentMethodMessage
paymentConfig.externalPaymentMethodConfiguration = externalPaymentMethodConfiguration
paymentConfig.paymentMethodOrder = paymentMethodOrder
paymentConfig.allowsRemovalOfLastSavedPaymentMethod = allowsRemovalOfLastSavedPaymentMethod

/* Note:
There are 3 properties that differ today:
hidesMandateText
formSheetAction
paymentMethodLayout
*/

return paymentConfig
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// IntegrationConfigurable.swift
// StripePaymentSheet
//
// Created by Nick Porter on 10/1/24.
//

import Foundation
import UIKit
@_spi(STP) import StripePayments

/// Represents shared configuration properties shared between integration surfaces in mobile payment element.
/// - Note: See the concrete implementations of `IntegrationConfigurable` for detailed doc comments.
/// - Note: Not currently used by CustomerSheet.
protocol IntegrationConfigurable: PaymentMethodRequirementProvider {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open to naming suggestions

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call it PaymentElementConfiguration? I think that's the most accurate name, since this is Configuration for all Mobile Payment Element integration variants. We just need to start thinking of PaymentSheet, FlowController, EmbeddedPaymentElement as Payment Element variants.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means we should probably rename things like PaymentSheetLoader to be PaymentElementLoader.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on renaming other PaymentSheet prefixed classes, maybe something we should tackle when we rename the module.

var allowsDelayedPaymentMethods: Bool { get set }
var allowsPaymentMethodsRequiringShippingAddress: Bool { get set }
var apiClient: STPAPIClient { get set }
var applePay: PaymentSheet.ApplePayConfiguration? { get set }
var primaryButtonColor: UIColor? { get set }
var primaryButtonLabel: String? { get set }
var style: PaymentSheet.UserInterfaceStyle { get set }
var customer: PaymentSheet.CustomerConfiguration? { get set }
var merchantDisplayName: String { get set }
var returnURL: String? { get set }
var defaultBillingDetails: PaymentSheet.BillingDetails { get set }
var savePaymentMethodOptInBehavior: PaymentSheet.SavePaymentMethodOptInBehavior { get set }
var appearance: PaymentSheet.Appearance { get set }
var shippingDetails: () -> AddressViewController.AddressDetails? { get set }
var preferredNetworks: [STPCardBrand]? { get set }
var userOverrideCountry: String? { get set }
var billingDetailsCollectionConfiguration: PaymentSheet.BillingDetailsCollectionConfiguration { get set }
var removeSavedPaymentMethodMessage: String? { get set }
var externalPaymentMethodConfiguration: PaymentSheet.ExternalPaymentMethodConfiguration? { get set }
var paymentMethodOrder: [String]? { get set }
var allowsRemovalOfLastSavedPaymentMethod: Bool { get set }
var analyticPayload: [String: Any] { get }
}

extension IntegrationConfigurable {

/// Returns `true` if the merchant requires the collection of _any_ billing detail fields - name, phone, email, address.
func requiresBillingDetailCollection() -> Bool {
return billingDetailsCollectionConfiguration.name == .always
|| billingDetailsCollectionConfiguration.phone == .always
|| billingDetailsCollectionConfiguration.email == .always
|| billingDetailsCollectionConfiguration.address == .full
}

var fulfilledRequirements: [PaymentMethodTypeRequirement] {
var reqs = [PaymentMethodTypeRequirement]()
if returnURL != nil { reqs.append(.returnURL) }
if allowsDelayedPaymentMethods { reqs.append(.userSupportsDelayedPaymentMethods) }
if allowsPaymentMethodsRequiringShippingAddress { reqs.append(.shippingAddress) }
if FinancialConnectionsSDKAvailability.isFinancialConnectionsSDKAvailable {
reqs.append(.financialConnectionsSDK)
}
return reqs
}
}

extension PaymentSheet.Configuration: IntegrationConfigurable {}
extension EmbeddedPaymentElement.Configuration: IntegrationConfigurable {}
Loading
Loading