Skip to content
Open
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 @@ -54,10 +54,20 @@ extension STPAPIClient {
parameters["type"] = "deferred_intent"
parameters["key"] = publishableKey
if let sellerDetails = intentConfig.sellerDetails {
parameters["seller_details"] = [
"network_id": sellerDetails.networkId,
"external_id": sellerDetails.externalId,
]
var sellerDetailsParams: [String: String] = [:]
if let networkId = sellerDetails.networkId {
sellerDetailsParams["network_id"] = networkId
}
if let externalId = sellerDetails.externalId {
sellerDetailsParams["external_id"] = externalId
}
if let businessName = sellerDetails.businessName {
sellerDetailsParams["business_name"] = businessName
}
if let networkBusinessProfile = sellerDetails.networkBusinessProfile {
sellerDetailsParams["network_business_profile"] = networkBusinessProfile
}
parameters["seller_details"] = sellerDetailsParams
}
parameters["deferred_intent"] = {
var deferredIntent = [String: Any]()
Expand Down Expand Up @@ -167,6 +177,21 @@ extension STPAPIClient {
clientDefaultPaymentMethod: String?,
configuration: PaymentElementConfiguration
) async throws -> STPElementsSession {
// Add beta header when networkBusinessProfile is provided
let addedBeta = intentConfig.sellerDetails?.networkBusinessProfile != nil
if addedBeta {
var betas = self.betas
betas.insert("payment_element_seller_payment_methods_beta_1=v1")
self.betas = betas
}
defer {
if addedBeta {
var betas = self.betas
betas.remove("payment_element_seller_payment_methods_beta_1=v1")
self.betas = betas
}
}

let parameters = makeElementsSessionsParams(
mode: .deferredIntent(intentConfig),
epmConfiguration: configuration.externalPaymentMethodConfiguration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public enum PaymentSheetError: Error, LocalizedError {
// MARK: Deferred intent errors
case intentConfigurationValidationFailed(message: String)
case deferredIntentValidationFailed(message: String)
case paymentMethodConfigurationAndSellerDetailsMutuallyExclusive

// MARK: - Link errors
case linkSignUpNotRequired
Expand Down Expand Up @@ -132,6 +133,8 @@ extension PaymentSheetError: CustomDebugStringConvertible {
return "New payment method should not have been created yet"
case .intentConfigurationValidationFailed(message: let message):
return message
case .paymentMethodConfigurationAndSellerDetailsMutuallyExclusive:
return "`paymentMethodConfigurationId` and `sellerDetails` are mutually exclusive and cannot both be set on IntentConfiguration."
case .embeddedPaymentElementAlreadyConfirmedIntent:
return "This instance of EmbeddedPaymentElement has already confirmed an intent successfully. Create a new instance of EmbeddedPaymentElement to confirm a new intent."
case .integrationError(nonPIIDebugDescription: let nonPIIDebugDescription):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,21 @@ public extension PaymentSheet {

/// Seller details for facilitated payment sessions
@_spi(SharedPaymentToken) public struct SellerDetails {
public let networkId: String
public let externalId: String
public let businessName: String

public init(networkId: String, externalId: String, businessName: String) {
public let networkId: String?
public let externalId: String?
public let businessName: String?
public let networkBusinessProfile: String?

public init(
networkId: String? = nil,
externalId: String? = nil,
businessName: String? = nil,
networkBusinessProfile: String? = nil
) {
self.networkId = networkId
self.externalId = externalId
self.businessName = businessName
self.networkBusinessProfile = networkBusinessProfile
}
}

Expand Down Expand Up @@ -261,11 +268,13 @@ public extension PaymentSheet {

@discardableResult
func validate() -> Error? {
let errorMessage: String
if case .payment(let amount, _, _, _, _) = mode, amount <= 0 {
errorMessage = "The amount in `PaymentSheet.IntentConfiguration` must be non-zero! See https://docs.stripe.com/api/payment_intents/create#create_payment_intent-amount"
let errorMessage = "The amount in `PaymentSheet.IntentConfiguration` must be non-zero! See https://docs.stripe.com/api/payment_intents/create#create_payment_intent-amount"
return PaymentSheetError.intentConfigurationValidationFailed(message: errorMessage)
}
if sellerDetails != nil && paymentMethodConfigurationId != nil {
return PaymentSheetError.paymentMethodConfigurationAndSellerDetailsMutuallyExclusive
}
return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,104 @@ class STPAPIClient_PaymentSheetTest: XCTestCase {

XCTAssertEqual(sellerDetailsParams["network_id"] as? String, "network_123")
XCTAssertEqual(sellerDetailsParams["external_id"] as? String, "external_456")
XCTAssertEqual(sellerDetailsParams["business_name"] as? String, "Till's Pills")
XCTAssertNil(sellerDetailsParams["network_business_profile"])
}

func testElementsSessionParameters_DeferredPayment_WithNetworkBusinessProfile() throws {
let sellerDetails = PaymentSheet.IntentConfiguration.SellerDetails(
networkId: "network_123",
externalId: "external_456",
businessName: "Till's Pills",
networkBusinessProfile: "nbp_123"
)
let intentConfig = PaymentSheet.IntentConfiguration(
sharedPaymentTokenSessionWithMode: .payment(amount: 2000, currency: "USD"),
sellerDetails: sellerDetails,
paymentMethodTypes: ["card"],
preparePaymentMethodHandler: { _, _ in }
)

let parameters = STPAPIClient(publishableKey: "pk_test").makeElementsSessionsParams(
mode: .deferredIntent(intentConfig),
epmConfiguration: nil,
cpmConfiguration: nil,
clientDefaultPaymentMethod: nil,
customerAccessProvider: nil,
linkDisallowFundingSourceCreation: []
)

let sellerDetailsParams = try XCTUnwrap(parameters["seller_details"] as? [String: Any])

XCTAssertEqual(sellerDetailsParams["network_id"] as? String, "network_123")
XCTAssertEqual(sellerDetailsParams["external_id"] as? String, "external_456")
XCTAssertEqual(sellerDetailsParams["business_name"] as? String, "Till's Pills")
XCTAssertEqual(sellerDetailsParams["network_business_profile"] as? String, "nbp_123")
}

func testElementsSessionParameters_DeferredPayment_WithSellerDetails_OptionalFields() throws {
let sellerDetails = PaymentSheet.IntentConfiguration.SellerDetails(
networkBusinessProfile: "nbp_only"
)
let intentConfig = PaymentSheet.IntentConfiguration(
sharedPaymentTokenSessionWithMode: .payment(amount: 2000, currency: "USD"),
sellerDetails: sellerDetails,
paymentMethodTypes: ["card"],
preparePaymentMethodHandler: { _, _ in }
)

let parameters = STPAPIClient(publishableKey: "pk_test").makeElementsSessionsParams(
mode: .deferredIntent(intentConfig),
epmConfiguration: nil,
cpmConfiguration: nil,
clientDefaultPaymentMethod: nil,
customerAccessProvider: nil,
linkDisallowFundingSourceCreation: []
)

let sellerDetailsParams = try XCTUnwrap(parameters["seller_details"] as? [String: Any])

XCTAssertNil(sellerDetailsParams["network_id"])
XCTAssertNil(sellerDetailsParams["external_id"])
XCTAssertNil(sellerDetailsParams["business_name"])
XCTAssertEqual(sellerDetailsParams["network_business_profile"] as? String, "nbp_only")
}

func testBetaHeader_AddedWhenNetworkBusinessProfileProvided() throws {
let sellerDetails = PaymentSheet.IntentConfiguration.SellerDetails(
networkBusinessProfile: "nbp_123"
)
let intentConfig = PaymentSheet.IntentConfiguration(
sharedPaymentTokenSessionWithMode: .payment(amount: 2000, currency: "USD"),
sellerDetails: sellerDetails,
paymentMethodTypes: ["card"],
preparePaymentMethodHandler: { _, _ in }
)

let apiClient = STPAPIClient(publishableKey: "pk_test")
XCTAssertFalse(apiClient.betas.contains("payment_element_seller_payment_methods_beta_1=v1"))

// We can't easily test the full request flow, but we can verify the beta would be added
// by checking the intentConfig condition
XCTAssertNotNil(intentConfig.sellerDetails?.networkBusinessProfile)
}

func testValidation_SellerDetailsAndPaymentMethodConfiguration_MutuallyExclusive() {
let sellerDetails = PaymentSheet.IntentConfiguration.SellerDetails(
networkId: "network_123",
externalId: "external_456",
businessName: "Till's Pills"
)
let intentConfig = PaymentSheet.IntentConfiguration(
sharedPaymentTokenSessionWithMode: .payment(amount: 2000, currency: "USD"),
sellerDetails: sellerDetails,
paymentMethodConfigurationId: "pmc_123",
preparePaymentMethodHandler: { _, _ in }
)

let error = intentConfig.validate()
XCTAssertNotNil(error)
XCTAssertTrue(error is PaymentSheetError)
}

func testElementsSessionParameters_DeferredPayment_WithoutSellerDetails() throws {
Expand Down
Loading