Skip to content

Commit

Permalink
[MBL-1307] Show processing view while payment is processing (#2013)
Browse files Browse the repository at this point in the history
* Show processing view while payment is processing

* Support rest of payment flow

* Test processing view in apple pay flow

* Minor edits for clarity and more tests
  • Loading branch information
ifosli committed Apr 2, 2024
1 parent 6fcf7ee commit ec45e46
Show file tree
Hide file tree
Showing 3 changed files with 271 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ private enum PostCampaignCheckoutLayout {
}
}

final class PostCampaignCheckoutViewController: UIViewController, MessageBannerViewControllerPresenting {
final class PostCampaignCheckoutViewController: UIViewController,
MessageBannerViewControllerPresenting,
ProcessingViewPresenting {
// MARK: - Properties

internal var messageBannerViewController: MessageBannerViewController?
Expand Down Expand Up @@ -42,6 +44,8 @@ final class PostCampaignCheckoutViewController: UIViewController, MessageBannerV
PostCampaignPledgeRewardsSummaryViewController.instantiate()
}()

internal var processingView: ProcessingView? = ProcessingView(frame: .zero)

private lazy var rootScrollView: UIScrollView = {
UIScrollView(frame: .zero)
|> \.translatesAutoresizingMaskIntoConstraints .~ false
Expand Down Expand Up @@ -92,6 +96,7 @@ final class PostCampaignCheckoutViewController: UIViewController, MessageBannerV
}

deinit {
self.hideProcessingView()
self.sessionStartedObserver.doIfSome(NotificationCenter.default.removeObserver)
}

Expand Down Expand Up @@ -189,6 +194,16 @@ final class PostCampaignCheckoutViewController: UIViewController, MessageBannerV
self?.pledgeCTAContainerView.configureWith(value: value)
}

self.viewModel.outputs.processingViewIsHidden
.observeForUI()
.observeValues { [weak self] isHidden in
if isHidden {
self?.hideProcessingView()
} else {
self?.showProcessingView()
}
}

self.viewModel.outputs.configurePaymentMethodsViewControllerWithValue
.observeForUI()
.observeValues { [weak self] value in
Expand Down Expand Up @@ -297,6 +312,7 @@ final class PostCampaignCheckoutViewController: UIViewController, MessageBannerV
guard error == nil, status == .succeeded else {
self.messageBannerViewController?
.showBanner(with: .error, message: Strings.Something_went_wrong_please_try_again())
self.viewModel.inputs.checkoutTerminated()
return
}

Expand All @@ -307,6 +323,7 @@ final class PostCampaignCheckoutViewController: UIViewController, MessageBannerV
private func goToPaymentAuthorization(_ paymentAuthorizationData: PostCampaignPaymentAuthorizationData) {
let request = PKPaymentRequest.paymentRequest(for: paymentAuthorizationData)
guard let applePayContext = STPApplePayContext(paymentRequest: request, delegate: self) else {
self.viewModel.inputs.checkoutTerminated()
return
}

Expand Down Expand Up @@ -484,12 +501,15 @@ extension PostCampaignCheckoutViewController: STPApplePayContextDelegate {
case .success:
self.viewModel.inputs.applePayContextDidComplete()
case .error:
self.viewModel.inputs.checkoutTerminated()
self.messageBannerViewController?
.showBanner(with: .error, message: Strings.Something_went_wrong_please_try_again())
case .userCancellation:
// User canceled the payment
self.viewModel.inputs.checkoutTerminated()
break
@unknown default:
self.viewModel.inputs.checkoutTerminated()
fatalError()
}
}
Expand Down
60 changes: 45 additions & 15 deletions Library/ViewModels/PostCampaignCheckoutViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public struct PaymentSourceValidation {
}

public protocol PostCampaignCheckoutViewModelInputs {
func checkoutTerminated()
func configure(with data: PostCampaignCheckoutData)
func confirmPaymentSuccessful(clientSecret: String)
func creditCardSelected(source: PaymentSourceSelected, paymentMethodId: String, isNewPaymentMethod: Bool)
Expand All @@ -59,6 +60,7 @@ public protocol PostCampaignCheckoutViewModelOutputs {
var configureStripeIntegration: Signal<StripeConfigurationData, Never> { get }
var goToLoginSignup: Signal<(LoginIntent, Project, Reward?), Never> { get }
var paymentMethodsViewHidden: Signal<Bool, Never> { get }
var processingViewIsHidden: Signal<Bool, Never> { get }
var showErrorBannerWithMessage: Signal<String, Never> { get }
var showWebHelp: Signal<HelpType, Never> { get }
var validateCheckoutSuccess: Signal<PaymentSourceValidation, Never> { get }
Expand Down Expand Up @@ -164,6 +166,8 @@ public class PostCampaignCheckoutViewModel: PostCampaignCheckoutViewModelType,

let selectedCard = self.creditCardSelectedProperty.signal.skipNil()

let processingViewIsHidden = MutableProperty<Bool>(true)

// MARK: - Validate Existing Cards

/// Capture current users stored credit cards in the case that we need to validate an existing payment method
Expand Down Expand Up @@ -274,8 +278,10 @@ public class PostCampaignCheckoutViewModel: PostCampaignCheckoutViewModelType,
self.validateCheckoutSuccess = Signal
.merge(validateCheckoutNewCardSuccess, validateCheckoutExistingCardSuccess)

self.showErrorBannerWithMessage = Signal
let validateCheckoutError = Signal
.merge(validateCheckoutExistingCard.errors(), validateCheckoutNewCard.errors())

self.showErrorBannerWithMessage = validateCheckoutError
.map { _ in Strings.Something_went_wrong_please_try_again() }

// MARK: ApplePay
Expand All @@ -290,21 +296,28 @@ public class PostCampaignCheckoutViewModel: PostCampaignCheckoutViewModelType,
5) Payment authorization form calls paymentAuthorizationDidFinish
*/

let newPaymentIntentForApplePay: Signal<String, Never> = self.configureWithDataProperty.signal
.skipNil()
.takeWhen(self.applePayButtonTappedSignal)
.switchMap { initialData in
let projectId = initialData.project.graphID
let pledgeTotal = initialData.total
let createPaymentIntentForApplePay: Signal<Signal<PaymentIntentEnvelope, ErrorEnvelope>.Event, Never> =
self.configureWithDataProperty.signal
.skipNil()
.takeWhen(self.applePayButtonTappedSignal)
.switchMap { initialData in
let projectId = initialData.project.graphID
let pledgeTotal = initialData.total

return AppEnvironment.current.apiService
.createPaymentIntentInput(input: CreatePaymentIntentInput(
projectId: projectId,
amountDollars: String(format: "%.2f", pledgeTotal),
digitalMarketingAttributed: nil
))
.ksr_delay(AppEnvironment.current.apiDelayInterval, on: AppEnvironment.current.scheduler)
.materialize()
}

return AppEnvironment.current.apiService
.createPaymentIntentInput(input: CreatePaymentIntentInput(
projectId: projectId,
amountDollars: String(format: "%.2f", pledgeTotal),
digitalMarketingAttributed: nil
))
.materialize()
}
let newPaymentIntentForApplePayError: Signal<ErrorEnvelope, Never> = createPaymentIntentForApplePay
.errors()

let newPaymentIntentForApplePay: Signal<String, Never> = createPaymentIntentForApplePay
.values()
.map { $0.clientSecret }

Expand Down Expand Up @@ -391,10 +404,26 @@ public class PostCampaignCheckoutViewModel: PostCampaignCheckoutViewModelType,
.map { $0 }

self.checkoutError = checkoutCompleteSignal.signal.errors()

self.processingViewIsHidden = Signal.merge(
// Processing view starts hidden, so show at the start of a pledge flow.
self.submitButtonTappedProperty.signal.mapConst(false),
self.applePayButtonTappedSignal.mapConst(false),
// Hide view again whenever pledge flow is completed/cancelled/errors.
newPaymentIntentForApplePayError.mapConst(true),
validateCheckoutError.mapConst(true),
self.checkoutTerminatedProperty.signal.mapConst(true),
checkoutCompleteSignal.signal.mapConst(true)
)
}

// MARK: - Inputs

private let checkoutTerminatedProperty = MutableProperty(())
public func checkoutTerminated() {
self.checkoutTerminatedProperty.value = ()
}

private let configureWithDataProperty = MutableProperty<PostCampaignCheckoutData?>(nil)
public func configure(with data: PostCampaignCheckoutData) {
self.configureWithDataProperty.value = data
Expand Down Expand Up @@ -474,6 +503,7 @@ public class PostCampaignCheckoutViewModel: PostCampaignCheckoutViewModelType,
public let configureStripeIntegration: Signal<StripeConfigurationData, Never>
public let goToLoginSignup: Signal<(LoginIntent, Project, Reward?), Never>
public let paymentMethodsViewHidden: Signal<Bool, Never>
public let processingViewIsHidden: Signal<Bool, Never>
public let showErrorBannerWithMessage: Signal<String, Never>
public let showWebHelp: Signal<HelpType, Never>
public let validateCheckoutSuccess: Signal<PaymentSourceValidation, Never>
Expand Down
Loading

0 comments on commit ec45e46

Please sign in to comment.