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-1972: Add PaymentMethodsUseCase and tests #2255

Merged
merged 3 commits into from
Jan 29, 2025

Conversation

amy-at-kickstarter
Copy link
Contributor

📲 What

Add PaymentsMethodsUseCase and PaymentMethodsUseCaseTests.

n.B. that this does not hook up the use case - I'm holding off on installing it until our next release is cut.

🤔 Why

This puts all of the code related to creating/configuring/displaying a PledgePaymentMethodsViewController in one place.

More broadly: our goal is to simplify our checkout code. One way we can do this is by breaking it down into smaller, more understandable parts. We're following Android's lead here and building 'Use Cases', which are small chunks of code related to a single behavior or feature. These little pieces are much easier to understand, debug, and test.

This also means Late + Live pledges will be able to start sharing some of the code that's currently duplicated.

@amy-at-kickstarter amy-at-kickstarter marked this pull request as ready for review January 22, 2025 16:45
* `selectedPaymentSource` - The currently selected credit card, or `nil` if no card is selected. Sent at least once after `initialData` is sent.
*/
public final class PaymentMethodsUseCase {
init(initialData: Signal<PledgeViewData, Never>, userSessionStarted: Signal<(), Never>) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is almost 100% copy-pasted out of the existing pledge flow.

Copy link
Contributor

Choose a reason for hiding this comment

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

Splitting the comment up so that inputs and outputs have their own parts commented might make sense here

Copy link
Contributor Author

@amy-at-kickstarter amy-at-kickstarter Jan 27, 2025

Choose a reason for hiding this comment

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

Hmm, yeah, fair point. The only catch is that there's an extra input (creditCardSelected()) which doesn't live on init, so I don't want to document them there. Were you thinking the comments would make more sense to put on PaymentMethodsUseCaseInputs and PaymentMethodUseCaseOutputs?

init(initialData: Signal<PledgeViewData, Never>, userSessionStarted: Signal<(), Never>) {
let project = initialData.map(\.project)
let baseReward = initialData.map(\.rewards).map(\.first).skipNil()
let refTag = initialData.map(\.refTag)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As a fun note - pulling this out into its own component makes it a much more obvious that we're passing around extra data we don't need. The pledge payment methods page doesn't need to know about refTag or baseReward, as far as I know. Once this is hooked up to both pledge controllers, refactoring those redundancies will be easier!

.filter { !$3.paymentMethodsViewHidden }
.compactMap { project, reward, refTag, context -> PledgePaymentMethodsValue? in
guard let user = AppEnvironment.current.currentUser else { return nil }
return (user, project, "", reward, context, refTag)
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 removed a defunct comment about NaN here.

private let (creditCardSelectedSignal, creditCardSelectedObserver) = Signal<PaymentSourceSelected, Never>
.pipe()
public func creditCardSelected(with paymentSourceData: PaymentSourceSelected) {
self.creditCardSelectedObserver.send(value: paymentSourceData)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's also some signals about stripe configuration, which I'm debating if they should live here or elsewhere. But we can always re-adjust later.

self.useCase.selectedPaymentSource.observe(self.selectedPaymentSource.observer)
}

func test_LoggedInUser_SendsConfigValue_AndShowsPaymentMethods() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To @ifosli's point, I'm trying to make these tests less of change detectors than our old pledge tests. They're not perfectly comprehensive, but they should exercise most of the important bits of the use case.

@scottkicks scottkicks self-assigned this Jan 23, 2025
Copy link
Contributor

@jovaniks jovaniks left a comment

Choose a reason for hiding this comment

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

This is great work! It's a nice step towards making the view models cleaner and splitting them into smaller pieces focused on single responsibility components.

To align with existing patterns, I’d recommend following the ViewModelType protocol pattern.

* `paymentMethodsViewHidden` - Whether or not to show the payment methods view. Sent at least once after `initialData` is sent.
* `selectedPaymentSource` - The currently selected credit card, or `nil` if no card is selected. Sent at least once after `initialData` is sent.
*/
public final class PaymentMethodsUseCase: PaymentMethodsUseCaseInputs, PaymentMethodsUseCaseOutputs {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's follow the VieModelType protocol pattern here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Whoops! Missed that again, thanks for the eagle eyes

* `selectedPaymentSource` - The currently selected credit card, or `nil` if no card is selected. Sent at least once after `initialData` is sent.
*/
public final class PaymentMethodsUseCase {
init(initialData: Signal<PledgeViewData, Never>, userSessionStarted: Signal<(), Never>) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Splitting the comment up so that inputs and outputs have their own parts commented might make sense here

Comment on lines 32 to 33
.map { _ in AppEnvironment.current.currentUser }
.map { $0 != nil }
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Could probably merge these

Copy link
Contributor

@jovaniks jovaniks left a comment

Choose a reason for hiding this comment

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

Nice, great job

Copy link
Contributor

@scottkicks scottkicks left a comment

Choose a reason for hiding this comment

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

🎉 Nice! Its refreshing to see these behaviors broken into smaller more digestible modules.

@amy-at-kickstarter amy-at-kickstarter merged commit 514c177 into main Jan 29, 2025
5 checks passed
@amy-at-kickstarter amy-at-kickstarter deleted the feat/adyer/mbl-1972 branch January 29, 2025 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants