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

[BANKCON-14534] Add expected_payment_method_type to /confirm params #4077

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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 @@ -11,4 +11,5 @@ import Foundation
var paymentMethodId: String { get }
var bankName: String? { get }
var last4: String? { get }
var expectedPaymentMethodType: String? { get }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ import Foundation
@_spi(STP) public var isPantherPayment: Bool {
self == .linkCardBrand
}

@_spi(STP) public var expectedPaymentMethodType: String {
isPantherPayment ? "card" : "bank_account"
}
}
3 changes: 2 additions & 1 deletion StripeCore/StripeCore/Source/Helpers/Async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ import Foundation
}

public func transformed<T>(
on queue: DispatchQueue = .main,
with closure: @escaping (Value) throws -> T
) -> Future<T> {
chained { value in
chained(on: queue) { value in
try Promise(value: closure(value))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@ protocol FinancialConnectionsAPI {
bankAccountId: String
) -> Future<FinancialConnectionsPaymentDetails>

func sharePaymentDetails(
consumerSessionClientSecret: String,
paymentDetailsId: String,
expectedPaymentMethodType: String
) -> Future<FinancialConnectionsSharePaymentDetails>

func paymentMethods(
consumerSessionClientSecret: String,
paymentDetailsId: String
Expand Down Expand Up @@ -944,6 +950,27 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {
)
}

func sharePaymentDetails(
consumerSessionClientSecret: String,
paymentDetailsId: String,
expectedPaymentMethodType: String
) -> Future<FinancialConnectionsSharePaymentDetails> {
let parameters: [String: Any] = [
"request_surface": requestSurface,
"id": paymentDetailsId,
"credentials": [
"consumer_session_client_secret": consumerSessionClientSecret
],
"expected_payment_method_type": expectedPaymentMethodType,
"expand": ["payment_method"],
]
return post(
resource: APIEndpointSharePaymentDetails,
parameters: parameters,
useConsumerPublishableKeyIfNeeded: false
)
}

func paymentMethods(
consumerSessionClientSecret: String,
paymentDetailsId: String
Expand Down Expand Up @@ -995,4 +1022,5 @@ private let APIEndpointPollAccountNumbers = "link_account_sessions/poll_account_
private let APIEndpointLinkAccountsSignUp = "consumers/accounts/sign_up"
private let APIEndpointAttachLinkConsumerToLinkAccountSession = "consumers/attach_link_consumer_to_link_account_session"
private let APIEndpointPaymentDetails = "consumers/payment_details"
private let APIEndpointSharePaymentDetails = "consumers/payment_details/share"
private let APIEndpointPaymentMethods = "payment_methods"
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

import Foundation

protocol PaymentMethodIDProvider {
var id: String { get }
}

struct FinancialConnectionsPaymentDetails: Decodable {
let redactedPaymentDetails: RedactedPaymentDetails
}
Expand All @@ -24,3 +28,12 @@ struct BankAccountDetails: Decodable {
struct FinancialConnectionsPaymentMethod: Decodable {
let id: String
}

struct FinancialConnectionsSharePaymentDetails: Decodable {
let paymentMethod: FinancialConnectionsPaymentMethod
}

extension FinancialConnectionsPaymentMethod: PaymentMethodIDProvider {}
extension FinancialConnectionsSharePaymentDetails: PaymentMethodIDProvider {
var id: String { paymentMethod.id }
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ private extension HostController {
accountPickerPane: synchronizePayload.text?.accountPickerPane,
apiClient: apiClient,
clientSecret: clientSecret,
analyticsClient: analyticsClient
analyticsClient: analyticsClient,
elementsSessionContext: elementsSessionContext
)
nativeFlowController = NativeFlowController(
dataManager: dataManager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ struct InstantDebitsLinkedBankImplementation: InstantDebitsLinkedBank {
public let paymentMethodId: String
public let bankName: String?
public let last4: String?
public let expectedPaymentMethodType: String?

public init(
paymentMethodId: String,
bankName: String?,
last4: String?
last4: String?,
expectedPaymentMethodType: String?
) {
self.paymentMethodId = paymentMethodId
self.bankName = bankName
self.last4 = last4
self.expectedPaymentMethodType = expectedPaymentMethodType
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ extension NativeFlowController {
for session: StripeAPI.FinancialConnectionsSession,
completion: @escaping (Result<InstantDebitsLinkedBank, Error>) -> Void
) {
// Extract bank account ID from the session
let bankAccountId: String?
switch session.paymentAccount {
case .bankAccount(let account):
Expand All @@ -491,40 +492,58 @@ extension NativeFlowController {
bankAccountId = nil
}

// Validate bank account ID
guard let bankAccountId else {
let error = "InstantDebitsCompletionError: No bank account ID available when trying to create a payment method."
completion(.failure(FinancialConnectionsSheetError.unknown(debugDescription: error)))
return
}

// Validate consumer session
guard let consumerSession = dataManager.consumerSession else {
let error = "InstantDebitsCompletionError: No consumer session available when trying to create a payment method."
completion(.failure(FinancialConnectionsSheetError.unknown(debugDescription: error)))
return
}

// Bank account details extraction for the linked bank
var bankAccountDetails: BankAccountDetails?
let linkMode = dataManager.elementsSessionContext?.linkMode
dataManager.createPaymentDetails(
consumerSessionClientSecret: consumerSession.clientSecret,
bankAccountId: bankAccountId
).chained { [weak self] paymentDetails -> Future<FinancialConnectionsPaymentMethod> in
)
.chained { [weak self] paymentDetails -> Future<PaymentMethodIDProvider> in
guard let self else {
return Promise(error: FinancialConnectionsSheetError.unknown(debugDescription: "data source deallocated"))
}

bankAccountDetails = paymentDetails.redactedPaymentDetails.bankAccountDetails
return self.dataManager.createPaymentMethod(
consumerSessionClientSecret: consumerSession.clientSecret,
paymentDetailsId: paymentDetails.redactedPaymentDetails.id
)

// Decide which API to call based on the payment mode
if let linkMode, linkMode.isPantherPayment {
return self.dataManager.apiClient.sharePaymentDetails(
consumerSessionClientSecret: consumerSession.clientSecret,
paymentDetailsId: paymentDetails.redactedPaymentDetails.id,
expectedPaymentMethodType: linkMode.expectedPaymentMethodType
)
.transformed { $0 as PaymentMethodIDProvider }
} else {
return self.dataManager.createPaymentMethod(
consumerSessionClientSecret: consumerSession.clientSecret,
paymentDetailsId: paymentDetails.redactedPaymentDetails.id
)
.transformed { $0 as PaymentMethodIDProvider }
}
}
.observe { result in
switch result {
case .success(let paymentMethod):
let linkedBank = InstantDebitsLinkedBankImplementation(
paymentMethodId: paymentMethod.id,
bankName: bankAccountDetails?.bankName,
last4: bankAccountDetails?.last4
last4: bankAccountDetails?.last4,
expectedPaymentMethodType: linkMode?.expectedPaymentMethodType
)
completion(.success(linkedBank))
case .failure(let error):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ protocol NativeFlowDataManager: AnyObject {
var apiClient: FinancialConnectionsAPIClient { get }
var clientSecret: String { get }
var analyticsClient: FinancialConnectionsAnalyticsClient { get }
var elementsSessionContext: ElementsSessionContext? { get }
var reduceManualEntryProminenceInErrors: Bool { get }

var institution: FinancialConnectionsInstitution? { get set }
Expand Down Expand Up @@ -83,6 +84,7 @@ class NativeFlowAPIDataManager: NativeFlowDataManager {
let apiClient: FinancialConnectionsAPIClient
let clientSecret: String
let analyticsClient: FinancialConnectionsAnalyticsClient
let elementsSessionContext: ElementsSessionContext?

var institution: FinancialConnectionsInstitution?
var authSession: FinancialConnectionsAuthSession?
Expand Down Expand Up @@ -117,7 +119,8 @@ class NativeFlowAPIDataManager: NativeFlowDataManager {
accountPickerPane: FinancialConnectionsAccountPickerPane?,
apiClient: FinancialConnectionsAPIClient,
clientSecret: String,
analyticsClient: FinancialConnectionsAnalyticsClient
analyticsClient: FinancialConnectionsAnalyticsClient,
elementsSessionContext: ElementsSessionContext?
) {
self.manifest = manifest
self.visualUpdate = visualUpdate
Expand All @@ -127,6 +130,7 @@ class NativeFlowAPIDataManager: NativeFlowDataManager {
self.apiClient = apiClient
self.clientSecret = clientSecret
self.analyticsClient = analyticsClient
self.elementsSessionContext = elementsSessionContext
// Use server provided active AuthSession.
self.authSession = manifest.activeAuthSession
// If the server returns active institution use that, otherwise resort to initial institution.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ extension FinancialConnectionsWebFlowViewController {
bankName: Self.extractValue(from: returnUrl, key: "bank_name")?
// backend can return "+" instead of a more-common encoding of "%20" for spaces
.replacingOccurrences(of: "+", with: " "),
last4: Self.extractValue(from: returnUrl, key: "last4")
last4: Self.extractValue(from: returnUrl, key: "last4"),
expectedPaymentMethodType: Self.extractValue(from: returnUrl, key: "expected_payment_method_type")
)
self.notifyDelegateOfSuccess(result: .instantDebits(instantDebitsLinkedBank))
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ class EmptyFinancialConnectionsAPIClient: FinancialConnectionsAPI {
Promise<StripeFinancialConnections.FinancialConnectionsPaymentDetails>()
}

func sharePaymentDetails(consumerSessionClientSecret: String, paymentDetailsId: String, expectedPaymentMethodType: String) -> Future<FinancialConnectionsSharePaymentDetails> {
Promise<StripeFinancialConnections.FinancialConnectionsSharePaymentDetails>()
}

func paymentMethods(consumerSessionClientSecret: String, paymentDetailsId: String) -> StripeCore.Future<StripeFinancialConnections.FinancialConnectionsPaymentMethod> {
Promise<StripeFinancialConnections.FinancialConnectionsPaymentMethod>()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,14 @@ extension PaymentSheet {
configuration: configuration
)
if case .new(let confirmParams) = paymentOption {
if let paymentMethodId = confirmParams.instantDebitsLinkedBank?.paymentMethodId {
params.paymentMethodId = paymentMethodId
if let linkedBank = confirmParams.instantDebitsLinkedBank {
params.paymentMethodId = linkedBank.paymentMethodId
params.paymentMethodParams = nil

if let expectedPaymentMethodType = linkedBank.expectedPaymentMethodType {
params.additionalAPIParameters["expected_payment_method_type"] = expectedPaymentMethodType
}

if paymentIntent.isSetupFutureUsageSet {
params.mandateData = STPMandateDataParams.makeWithInferredValues()
}
Expand Down
Loading