Skip to content

Commit

Permalink
MBL-1123: Clarify payments methods flow by explicitly passing payment…
Browse files Browse the repository at this point in the history
…SheetType
  • Loading branch information
amy-at-kickstarter committed Mar 25, 2024
1 parent 5e1faf8 commit 10b8daa
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,6 @@ final class PledgePaymentMethodsViewController: UIViewController {
// MARK: - Functions

private func goToPaymentSheet(data: PaymentSheetSetupData) {
let isSetupIntent = data.configuration.allowsDelayedPaymentMethods
let isPaymentIntent = !isSetupIntent

let completion: (Result<PaymentSheet.FlowController, Error>) -> Void = { [weak self] result in
guard let strongSelf = self else { return }

Expand All @@ -159,28 +156,31 @@ final class PledgePaymentMethodsViewController: UIViewController {
.pledgeViewController(strongSelf, didErrorWith: error.localizedDescription)
case let .success(paymentSheetFlowController):
let topViewController = strongSelf.navigationController?.topViewController
let paymentSheetShownWithinPledgeContext = topViewController is PledgeViewController

if paymentSheetShownWithinPledgeContext {
strongSelf.paymentSheetFlowController = paymentSheetFlowController
strongSelf.paymentSheetFlowController?.presentPaymentOptions(from: strongSelf) { [weak self] in
guard let strongSelf = self else { return }
assert(
topViewController is PledgeViewController ||
topViewController is PostCampaignCheckoutViewController,
"PledgePaymentMethodsViewController is only intended to be presented as part of a pledge flow."
)

strongSelf.paymentSheetFlowController = paymentSheetFlowController
strongSelf.paymentSheetFlowController?.presentPaymentOptions(from: strongSelf) { [weak self] in
guard let strongSelf = self else { return }

// TODO: this needs to not just be a SetupIntent
strongSelf.confirmPaymentResult(with: data.clientSecret)
}
strongSelf.confirmPaymentResult(with: data.clientSecret)
}
strongSelf.viewModel.inputs.stripePaymentSheetDidAppear()
}
}

if isSetupIntent {
switch data.paymentSheetType {
case .setupIntent:
PaymentSheet.FlowController.create(
setupIntentClientSecret: data.clientSecret,
configuration: data.configuration,
completion: completion
)
} else {
case .paymentIntent:
PaymentSheet.FlowController
.create(
paymentIntentClientSecret: data.clientSecret,
Expand Down
3 changes: 2 additions & 1 deletion Library/ViewModels/PaymentMethodsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ public final class PaymentMethodsViewModel: PaymentMethodsViewModelType,
configuration.allowsDelayedPaymentMethods = true
let data = PaymentSheetSetupData(
clientSecret: envelope.clientSecret,
configuration: configuration
configuration: configuration,
paymentSheetType: .setupIntent
)
return SignalProducer(value: data)
}
Expand Down
122 changes: 67 additions & 55 deletions Library/ViewModels/PledgePaymentMethodsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,20 @@ public typealias PledgePaymentMethodsValue = (
project: Project,
reward: Reward,
context: PledgeViewContext,
refTag: RefTag?
refTag: RefTag?,
pledgeTotal: Double,
paymentSheetType: PledgePaymentSheetType
)

public enum PledgePaymentSheetType {
case setupIntent
case paymentIntent
}

public typealias PaymentSheetSetupData = (
clientSecret: String,
configuration: PaymentSheet.Configuration
configuration: PaymentSheet.Configuration,
paymentSheetType: PledgePaymentSheetType
)

public typealias PledgePaymentMethodsAndSelectionData = (
Expand Down Expand Up @@ -68,9 +76,8 @@ public final class PledgePaymentMethodsViewModel: PledgePaymentMethodsViewModelT
let project = configureWithValue.map { $0.project }
let context = configureWithValue.map { $0.context }
let availableCardTypes = project.map { $0.availableCardTypes }.skipNil()

let usePaymentIntent = configureWithValue
.map { $0.project.isInPostCampaignPledgingPhase && featurePostCampaignPledgeEnabled() }
let pledgeTotal = configureWithValue.map { $0.pledgeTotal }
let paymentSheetType = configureWithValue.map { $0.paymentSheetType }

let paymentSheetEnabled = true

Expand Down Expand Up @@ -122,12 +129,6 @@ public final class PledgePaymentMethodsViewModel: PledgePaymentMethodsViewModelT
)
.scan([]) { current, new in new + current }

let allCardData = Signal.combineLatest(
allCards,
availableCardTypes,
project
)

let cards = initialCardData
.map(pledgePaymentMethodCellDataAndSelectedCard)

Expand Down Expand Up @@ -158,15 +159,18 @@ public final class PledgePaymentMethodsViewModel: PledgePaymentMethodsViewModelT
)
}

let configuredCardsWithNewSetupIntentCards = Signal.combineLatest(configuredCards, project)
let configuredCardsWithNewSetupIntentCards = Signal.combineLatest(configuredCards, paymentSheetType)
.takePairWhen(newSetupIntentCards)
.map { cardsAndProject, setupIntentCards -> PledgePaymentMethodsAndSelectionData in
let (cards, project) = cardsAndProject
.map { cardsAndPaymentSheetType, setupIntentCards -> PledgePaymentMethodsAndSelectionData in
let (cards, paymentSheetType) = cardsAndPaymentSheetType
let updatedCardData = cards
|> \.paymentSheetPaymentMethodsCellData .~ setupIntentCards

let updatedPaymentMethodSelectionData =
pledgePaymentSheetMethodCellDataAndSelectedCardSetupIntent(with: updatedCardData, project: project)
pledgePaymentSheetMethodCellDataAndSelectedCardSetupIntent(
with: updatedCardData,
paymentSheetType: paymentSheetType
)

return updatedPaymentMethodSelectionData
}
Expand All @@ -188,9 +192,9 @@ public final class PledgePaymentMethodsViewModel: PledgePaymentMethodsViewModelT
.filter { _, indexPath in
indexPath.section == PaymentMethodsTableViewSection.paymentMethods.rawValue
}
.combineLatest(with: project)
.combineLatest(with: paymentSheetType)
.map { ($0.0.0, $0.0.1, $0.1) } // turn ((a, b), c) into (a, b, c)
.map { (data, indexPath, project) -> PledgePaymentMethodsAndSelectionData? in
.map { (data, indexPath, paymentSheetType) -> PledgePaymentMethodsAndSelectionData? in
let updatedData = data
|> \.paymentMethodsCellData .~ []
|> \.paymentSheetPaymentMethodsCellData .~ []
Expand All @@ -215,7 +219,7 @@ public final class PledgePaymentMethodsViewModel: PledgePaymentMethodsViewModelT
)
}

let usePaymentIntent = featurePostCampaignPledgeEnabled() && project.isInPostCampaignPledgingPhase
let usePaymentIntent = paymentSheetType == .paymentIntent

let selectionUpdatedData = updatedData
|> \.paymentMethodsCellData .~ cellData(data.paymentMethodsCellData, selecting: nil)
Expand Down Expand Up @@ -292,47 +296,55 @@ public final class PledgePaymentMethodsViewModel: PledgePaymentMethodsViewModelT

let createSetupIntentEvent = Signal.combineLatest(
project,
paymentSheetOnPledgeContext.filter(isTrue)
pledgeTotal,
paymentSheetType
)
.takeWhen(didTapToAddNewCard)
.switchMap { (project, _) -> SignalProducer<Signal<PaymentSheetSetupData, ErrorEnvelope>.Event, Never> in

let usePaymentIntent = featurePostCampaignPledgeEnabled() && project.isInPostCampaignPledgingPhase
let useSetupIntent = !usePaymentIntent
let clientSecretSignal: SignalProducer<String, ErrorEnvelope>

if useSetupIntent {
clientSecretSignal = AppEnvironment.current.apiService
.createStripeSetupIntent(input: CreateSetupIntentInput(projectId: project.graphID))
.map { $0.clientSecret }
} else {
// TODO: real amt, real digital marketing attribution
clientSecretSignal = AppEnvironment.current.apiService
.createPaymentIntentInput(input: CreatePaymentIntentInput(
projectId: project.graphID,
amountDollars: "10.00",
digitalMarketingAttributed: nil
))
.map { $0.clientSecret }
}
.switchMap { (project, pledgeTotal, paymentSheetType) -> SignalProducer<
Signal<PaymentSheetSetupData, ErrorEnvelope>.Event,
Never
> in

let clientSecretSignal: SignalProducer<String, ErrorEnvelope>

switch paymentSheetType {
case .setupIntent:
clientSecretSignal = AppEnvironment.current.apiService
.createStripeSetupIntent(input: CreateSetupIntentInput(projectId: project.graphID))
.map { $0.clientSecret }

case .paymentIntent:
assert(
!pledgeTotal.isNaN,
"Pledge total must be set when using a PaymentIntent. Did you accidentally get here via PledgeViewModel instead of PostCampaignCheckoutViewModel?"
)
clientSecretSignal = AppEnvironment.current.apiService
.createPaymentIntentInput(input: CreatePaymentIntentInput(
projectId: project.graphID,
amountDollars: String(format: "%.2f", pledgeTotal),
digitalMarketingAttributed: nil
))
.map { $0.clientSecret }
}

return clientSecretSignal
.ksr_debounce(.seconds(1), on: AppEnvironment.current.scheduler)
.ksr_delay(AppEnvironment.current.apiDelayInterval, on: AppEnvironment.current.scheduler)
.switchMap { clientSecret -> SignalProducer<PaymentSheetSetupData, ErrorEnvelope> in
return clientSecretSignal
.ksr_debounce(.seconds(1), on: AppEnvironment.current.scheduler)
.ksr_delay(AppEnvironment.current.apiDelayInterval, on: AppEnvironment.current.scheduler)
.switchMap { clientSecret -> SignalProducer<PaymentSheetSetupData, ErrorEnvelope> in

var configuration = PaymentSheet.Configuration()
configuration.merchantDisplayName = Strings.general_accessibility_kickstarter()
configuration.allowsDelayedPaymentMethods = useSetupIntent
var configuration = PaymentSheet.Configuration()
configuration.merchantDisplayName = Strings.general_accessibility_kickstarter()
configuration.allowsDelayedPaymentMethods = paymentSheetType == .setupIntent

let data = PaymentSheetSetupData(
clientSecret: clientSecret,
configuration: configuration
)
let data = PaymentSheetSetupData(
clientSecret: clientSecret,
configuration: configuration,
paymentSheetType: paymentSheetType
)

return SignalProducer(value: data)
}
.materialize()
return SignalProducer(value: data)
}
.materialize()
}

self.goToAddCardViaStripeScreen = createSetupIntentEvent.values()
Expand Down Expand Up @@ -477,7 +489,7 @@ public final class PledgePaymentMethodsViewModel: PledgePaymentMethodsViewModelT

private func pledgePaymentSheetMethodCellDataAndSelectedCardSetupIntent(
with paymentMethodData: PledgePaymentMethodsAndSelectionData,
project: Project
paymentSheetType: PledgePaymentSheetType
) -> PledgePaymentMethodsAndSelectionData {
// We know we have a new payment sheet card, so de-select all existing non-payment sheet cards.
let preexistingCardDataUnselected: [PledgePaymentMethodCellData] = {
Expand Down Expand Up @@ -525,7 +537,7 @@ private func pledgePaymentSheetMethodCellDataAndSelectedCardSetupIntent(

data[0] = updatedSelectedPaymentSheetPaymentMethod

let usePaymentIntent = featurePostCampaignPledgeEnabled() && project.isInPostCampaignPledgingPhase
let usePaymentIntent = paymentSheetType == .paymentIntent

let updatePaymentMethodData = paymentMethodData
|> \.paymentMethodsCellData .~ preexistingCardDataUnselected
Expand Down
5 changes: 4 additions & 1 deletion Library/ViewModels/PledgeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,10 @@ public class PledgeViewModel: PledgeViewModelType, PledgeViewModelInputs, Pledge
.compactMap { project, reward, refTag, context -> PledgePaymentMethodsValue? in
guard let user = AppEnvironment.current.currentUser else { return nil }

return (user, project, reward, context, refTag)
// This second to last value - pledgeTotal - is only needed when the payment methods controller
// is used in late campaign pledges. There is an assert in PledgePaymentMethodsViewModel to ensure
// we don't accidentally propagate this nan downstream.
return (user, project, reward, context, refTag, Double.nan, .setupIntent)
}

self.goToLoginSignup = Signal.combineLatest(project, baseReward, self.goToLoginSignupSignal)
Expand Down
2 changes: 1 addition & 1 deletion Library/ViewModels/PostCampaignCheckoutViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public class PostCampaignCheckoutViewModel: PostCampaignCheckoutViewModelType,
guard let user = AppEnvironment.current.currentUser else { return nil }
guard let reward = data.rewards.first else { return nil }

return (user, data.project, reward, data.context, data.refTag)
return (user, data.project, reward, data.context, data.refTag, data.total, .paymentIntent)
}

self.goToLoginSignup = initialData.takeWhen(self.goToLoginSignupSignal)
Expand Down

0 comments on commit 10b8daa

Please sign in to comment.