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

MBL-1123: Implement CreatePaymentIntent #1994

Merged
merged 6 commits into from
Mar 25, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ final class PledgePaymentMethodsViewController: UIViewController {
self.tableView.reloadData()
} else {
switch selectedPaymentMethod {
case let .paymentIntentClientSecret(selectedPaymentSheetCardId):
fallthrough
case let .setupIntentClientSecret(selectedPaymentSheetCardId):
self.tableView.visibleCells
.compactMap { $0 as? PledgePaymentSheetPaymentMethodCell }
Expand Down Expand Up @@ -144,33 +146,48 @@ final class PledgePaymentMethodsViewController: UIViewController {
// MARK: - Functions

private func goToPaymentSheet(data: PaymentSheetSetupData) {
PaymentSheet.FlowController
.create(
setupIntentClientSecret: data.clientSecret,
configuration: data.configuration
) { [weak self] result in
guard let strongSelf = self else { return }
let completion: (Result<PaymentSheet.FlowController, Error>) -> Void = { [weak self] result in
guard let strongSelf = self else { return }

switch result {
case let .failure(error):
strongSelf.viewModel.inputs.shouldCancelPaymentSheetAppearance(state: true)
strongSelf.messageDisplayingDelegate?
.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 }

strongSelf.confirmPaymentResult(with: data.clientSecret)
}
}
strongSelf.viewModel.inputs.stripePaymentSheetDidAppear()
switch result {
case let .failure(error):
strongSelf.viewModel.inputs.shouldCancelPaymentSheetAppearance(state: true)
strongSelf.messageDisplayingDelegate?
.pledgeViewController(strongSelf, didErrorWith: error.localizedDescription)
case let .success(paymentSheetFlowController):
let topViewController = strongSelf.navigationController?.topViewController

assert(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that I got rid of the if paymentSheetShownWithinPledgeContext and changed it to an assert, instead. AFAIK this view controller is already only ever used from a pledge context.

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 }

strongSelf.confirmPaymentResult(with: data.clientSecret)
}
strongSelf.viewModel.inputs.stripePaymentSheetDidAppear()
}
}

switch data.paymentSheetType {
case .setupIntent:
PaymentSheet.FlowController.create(
setupIntentClientSecret: data.clientSecret,
configuration: data.configuration,
completion: completion
)
case .paymentIntent:
PaymentSheet.FlowController
.create(
paymentIntentClientSecret: data.clientSecret,
configuration: data.configuration,
completion: completion
)
}
}

private func confirmPaymentResult(with clientSecret: String) {
Expand All @@ -195,7 +212,7 @@ final class PledgePaymentMethodsViewController: UIViewController {
label: existingPaymentOption.label
)
strongSelf.viewModel.inputs
.paymentSheetDidAdd(newCard: paymentDisplayData, setupIntent: clientSecret)
.paymentSheetDidAdd(newCard: paymentDisplayData, clientSecret: clientSecret)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've been trying to rename setupIntent to clientSecret or stripeIntent wherever I can, but definitely have missed some spots.

case .canceled:
strongSelf.messageDisplayingDelegate?
.pledgeViewController(strongSelf, didErrorWith: Strings.general_error_something_wrong())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ final class PledgePaymentMethodsViewControllerTests: TestCase {
project: project,
reward: reward,
context: .pledge,
refTag: nil
refTag: nil,
pledgeTotal: Double.nan,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

paymentSheetType: .setupIntent
)

controller.configure(with: data)
Expand Down Expand Up @@ -89,7 +91,9 @@ final class PledgePaymentMethodsViewControllerTests: TestCase {
project: project,
reward: reward,
context: .pledge,
refTag: nil
refTag: nil,
pledgeTotal: Double.nan,
paymentSheetType: .setupIntent
)

controller.configure(with: data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ final class PledgePaymentMethodsDataSourceTests: XCTestCase {
(
image: UIImage(),
redactedCardNumber: "test1",
setupIntent: "seti_1LVlHO4VvJ2PtfhK43R6p7FI_secret_MEDiGbxfYVnHGsQy8v8TbZJTQhlNKLZ",
clientSecret: "seti_1LVlHO4VvJ2PtfhK43R6p7FI_secret_MEDiGbxfYVnHGsQy8v8TbZJTQhlNKLZ",
isSelected: true,
isEnabled: true
),
(
image: UIImage(),
redactedCardNumber: "test2",
setupIntent: "seti_2LVlHO4VvJ2PtfhK43R6p7FI_secret_MEDiGbxfYVnHGsQy8v8TbZJTQhlNKLP",
clientSecret: "seti_2LVlHO4VvJ2PtfhK43R6p7FI_secret_MEDiGbxfYVnHGsQy8v8TbZJTQhlNKLP",
isSelected: false,
isEnabled: true
)
Expand Down Expand Up @@ -92,14 +92,14 @@ final class PledgePaymentMethodsDataSourceTests: XCTestCase {
(
image: UIImage(),
redactedCardNumber: "test1",
setupIntent: "seti_1LVlHO4VvJ2PtfhK43R6p7FI_secret_MEDiGbxfYVnHGsQy8v8TbZJTQhlNKLZ",
clientSecret: "seti_1LVlHO4VvJ2PtfhK43R6p7FI_secret_MEDiGbxfYVnHGsQy8v8TbZJTQhlNKLZ",
isSelected: true,
isEnabled: true
),
(
image: UIImage(),
redactedCardNumber: "test2",
setupIntent: "seti_2LVlHO4VvJ2PtfhK43R6p7FI_secret_MEDiGbxfYVnHGsQy8v8TbZJTQhlNKLP",
clientSecret: "seti_2LVlHO4VvJ2PtfhK43R6p7FI_secret_MEDiGbxfYVnHGsQy8v8TbZJTQhlNKLP",
isSelected: false,
isEnabled: true
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import KsApi
import Library
import Prelude
import Stripe
import UIKit

private enum PostCampaignCheckoutLayout {
Expand Down Expand Up @@ -204,6 +205,13 @@ final class PostCampaignCheckoutViewController: UIViewController {
}

self.paymentMethodsViewController.view.rac.hidden = self.viewModel.outputs.paymentMethodsViewHidden

self.viewModel.outputs.configureStripeIntegration
.observeForUI()
.observeValues { merchantIdentifier, publishableKey in
STPAPIClient.shared.publishableKey = publishableKey
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @scottkicks! Just copied this directly over from PledgeViewModel/PledgeViewController.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not clear to me why we only set the Stripe keys if the payment view isn't hidden - does Stripe charge us for setup? Seems odd, but I'm not willing to plumb those depths right now.

STPAPIClient.shared.configuration.appleMerchantIdentifier = merchantIdentifier
}
}

// MARK: - Functions
Expand Down
7 changes: 4 additions & 3 deletions Library/ViewModels/PaymentMethodsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@ public final class PaymentMethodsViewModel: PaymentMethodsViewModelType,
return (
image: displayData.image,
redactedCardNumber: displayData.label,
setupIntent: setupIntent,
clientSecret: setupIntent,
isSelected: false,
isEnabled: true
)
}
.map { paymentMethodData -> String? in
guard let selectedPaymentSheetPaymentMethodCardId = paymentMethodData?.setupIntent else {
guard let selectedPaymentSheetPaymentMethodCardId = paymentMethodData?.clientSecret else {
return nil
}

Expand Down 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
Copy link
Contributor Author

Choose a reason for hiding this comment

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

PaymentMethodsViewModel is used by PaymentMethodSettingsViewControllerDelegate, which is in the settings. That page only ever needs to use a SetupIntent.

)
return SignalProducer(value: data)
}
Expand Down
10 changes: 10 additions & 0 deletions Library/ViewModels/PaymentSourceSelected.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Foundation
public enum PaymentSourceSelected: Equatable {
case paymentSourceId(String)
case setupIntentClientSecret(String)
case paymentIntentClientSecret(String)

public var paymentSourceId: String? {
switch self {
Expand All @@ -20,4 +21,13 @@ public enum PaymentSourceSelected: Equatable {
return nil
}
}

public var paymentIntentClientSecret: String? {
switch self {
case let .paymentIntentClientSecret(value):
return value
default:
return nil
}
}
}