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

Create ApplePayTokenUseCase and tests #2258

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

amy-at-kickstarter
Copy link
Contributor

📲 What

Create a use case that will handle ApplePay in the regular (live) pledge flow.

🤔 Why

This puts a lot of ApplePay code in one place, so we can pull it out of NoShippingPledgeViewModel in the next release.

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.

It's worth noting that ApplePay in late + live pledges is fundamentally different - they use different Stripe APIs. It would be nice to consolidate that, but that would be out of scope without further API changes. This is just a refactoring for niceness and modularity, instead of code re-use.

@@ -279,7 +279,6 @@
33AF01BC2D2D74B30085A153 /* PledgePaymentIncrementState+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33AF01BB2D2D748B0085A153 /* PledgePaymentIncrementState+Extension.swift */; };
33AF01BF2D2E72430085A153 /* PledgeOverTimePaymentScheduleViewModelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33AF01BD2D2E71E30085A153 /* PledgeOverTimePaymentScheduleViewModelTest.swift */; };
33AF01C22D2E973A0085A153 /* PledgeOverTimePaymentScheduleViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33AF01C02D2E95FD0085A153 /* PledgeOverTimePaymentScheduleViewControllerTest.swift */; };
33C9F0BE2CF5104C00B62E14 /* PledgePaymentPlanOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33C9F0BC2CED6DEF00B62E14 /* PledgePaymentPlanOptionView.swift */; };
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Must have been an automated change, I didn't touch this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting, the same change showed up on my branch as well, maybe it was a merge issue or something as it appeared twice in the pbxproj. Agreed that it appears to be automated, should be fine to include it IMO

}

public protocol ApplePayTokenUseCaseOutputs {
var goToApplePayPaymentAuthorization: Signal<PKPaymentRequest, Never> { get }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Slight tweak; I made this a PKPaymentRequest instead of PaymentAuthorizationData.

*/

public final class ApplePayTokenUseCase: ApplePayTokenUseCaseInputs, ApplePayTokenUseCaseOutputs {
init(initialData: Signal<PaymentAuthorizationData, 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 largely copy-pasted from NoShippingPledgeViewModel.

)
.mapConst(PKPaymentAuthorizationStatus.failure)

self.createApplePayBackingStatusProperty <~ Signal.merge(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TIL this is a ReactiveSwift operator that means bind.

Copy link
Contributor

Choose a reason for hiding this comment

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

is it fundamentally the same as =? I guess I'm wondering why use a different approach suddenly for this output.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not quite - here's the comment on it in ReactiveSwift: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/UnidirectionalBinding.swift#L68

My guess is that we're doing this because Signal.merge outputs a Signal, but createApplePayBackingStatusProperty is a MutableProperty - so you couldn't assign this with =.

More broadly, though, it's not clear to me whether using MutableProperty everywhere is appropriate in our codebase. I don't think we ever read the current value of a mutable property, we just observe them as if they were signals. This is something I'd like to understand better about the guts of ReactiveSwift.

@amy-at-kickstarter amy-at-kickstarter marked this pull request as ready for review January 22, 2025 22:57
@amy-at-kickstarter amy-at-kickstarter changed the title Create ApplePayTokenUseCase Create ApplePayTokenUseCase and tests 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.

Same as other UseCase components, just implement the ViewModelType protocol pattern.

Comment on lines +26 to +41
To complete an ApplePay payment, the use case should be used in this order:
- `input.applePayButtonTapped()` - The ApplePay button has been tapped
- `output.goToApplePayPaymentAuthorization` - The view controller should display a `PKPaymentAuthorizationViewController` with the sent `PKPaymentRequest`
- `input.paymentAuthorizationDidAuthorizePayment(paymentData:)` - The `PKPaymentAuthorizationViewController` successfully authorized a payment
- `input.stripeTokenCreated(token:error:)` - Stripe successfully turned the `PKPayment` into a Stripe token. Returns a status, which is also sent by the `applePayAuthorizationStatus` signal.
- `input.paymentAuthorizationViewControllerDidFinish()` - The `PKPaymentAuthorizationViewController` was dismissed
- `output.applePayParams` - Sends parameters which can be used in `CreateBacking` or `UpdateBacking`. Sends an initial `nil`value, by default.
Copy link
Contributor

Choose a reason for hiding this comment

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

Not necessary for this PR, but long term it might be good to build a little enum-based state machine for this if these must be called in order. Then the compiler can enforce they are called correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Love this idea. Would be great to make this enforced in code instead of with hopes and comments.

Comment on lines +63 to +79
.map { pkPayment -> PKPaymentData? in
guard let displayName = pkPayment.displayName, let network = pkPayment.network else {
return nil
}

return (displayName, network, pkPayment.transactionIdentifier)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like all the downstream signal processing is skipping nils. You could move the skipNils to this step or use compactMap instead of map here.

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.

LGTM

Just a couple minor comments.

}

/**
A use case for ApplePay transactions in the regular (not post!) 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.

nit: I know that sometimes we use live instead of post. IMO late is a better naming convention, but either way It'd be a good idea to align on one and stick to it. You used live in your other PR so maybe we can go with that here.

)
.mapConst(PKPaymentAuthorizationStatus.failure)

self.createApplePayBackingStatusProperty <~ Signal.merge(
Copy link
Contributor

Choose a reason for hiding this comment

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

is it fundamentally the same as =? I guess I'm wondering why use a different approach suddenly for this output.

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