Skip to content

[Migrate simple payments] Fix card reader payment methods from Menu > Payments > Collect Payment #12801

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

Merged
merged 4 commits into from
May 23, 2024

Conversation

jaclync
Copy link
Contributor

@jaclync jaclync commented May 21, 2024

Closes: #12790

Why

A merchant reported that the IPP flow from Menu > Payments > Collect Payment entry point stopped working, while the same flow in the orders tab worked. With @joshheald's helpful information peaMlT-Ec-p2#comment-1526, the issue was from not passing a rootViewController to PaymentMethodsView. It was probably missed from the beginning, but the card reader flow wasn't tested before the release since "card reader" and "Tap to Pay" are the only two payment methods that use the rootViewController with UIKit views (I thought I tested this but maybe at an early stage).

How

To fix this, I created PaymentMethodsWrapperHostingController (UIKit) and PaymentMethodsWrapperHosted (SwiftUI) to provide a root view controller to PaymentMethodsView to fix card reader payment method flow (as suggested in Josh's first point peaMlT-Ec-p2#comment-1526). There is also another existing hosting controller and its SwiftUI version in PaymentMethodsHostingController/PaymentMethodsHostingView, but there's more logic in it than what's needed in the Payments menu flow so I created "wrapper" versions (feel free to suggest other names).

It's quite dangerous how PaymentMethodsView.rootViewController isn't easy to enforce and missing it could result in a broken IPP flow. I thought about assertionFailure somewhere but couldn't find a good place to do it. Not mixing SwiftUI and UIKit with optional view controller setup would be great in the future.

Testing instructions

  • @jaclync tests on a device for card reader payment method
  • @jaclync tests on a device for Tap To Pay payment method

Prerequisite: a card reader is available

  • Go to Menu > Payments > Collect Payment
  • Enter a custom amount, or add some products to the order
  • Tap Collect Payment in the order form
  • Tap Card Reader --> the card reader flow should start
  • Continue to the payment flow --> the payment should be successful and it should navigate back to the Payments menu

Screenshots

Simulator.Screen.Recording.-.iPad.10th.generation.-.2024-05-21.at.13.24.22.mp4

  • I have considered if this change warrants user-facing release notes and have added them to RELEASE-NOTES.txt if necessary.

…ethodsWrapperHosted` (SwiftUI) to provide a root view controller to `PaymentMethodsView` to fix card reader payment method flow.
@jaclync jaclync added type: bug A confirmed bug. feature: hub menu Related to the Hub Menu tab. feature: simple payments Related to the Simple Payments feature labels May 21, 2024
@jaclync jaclync added this to the 18.8 milestone May 21, 2024
@jaclync jaclync changed the title [Migrate simple payments] Fix card reader payment methods [Migrate simple payments] Fix card reader payment methods from Menu > Payments > Collect Payment May 21, 2024
@wpmobilebot
Copy link
Collaborator

wpmobilebot commented May 21, 2024

WooCommerce iOS📲 You can test the changes from this Pull Request in WooCommerce iOS by scanning the QR code below to install the corresponding build.

App NameWooCommerce iOS WooCommerce iOS
Build Numberpr12801-6e205a0
Version18.7
Bundle IDcom.automattic.alpha.woocommerce
Commit6e205a0
App Center BuildWooCommerce - Prototype Builds #9184
Automatticians: You can use our internal self-serve MC tool to give yourself access to App Center if needed.

@jaclync jaclync marked this pull request as ready for review May 21, 2024 06:07
@iamgabrielma iamgabrielma self-assigned this May 21, 2024
@iamgabrielma
Copy link
Contributor

iamgabrielma commented May 21, 2024

Looks good!

It's quite dangerous how PaymentMethodsView.rootViewController isn't easy to enforce and missing it could result in a broken IPP flow. I thought about assertionFailure somewhere but couldn't find a good place to do it. Not mixing SwiftUI and UIKit with optional view controller setup would be great in the future.

I agree with asserting, perhaps through an Unit Test? At least to assert that the root is not nil.

Additionally, I'd also say that we should think of crashing on purpose in production if we land in this scenario, if we can: The weak reference to rootViewController here is unsafe since we may retain the reference to the class but have deallocated already the reference to the UIViewController. The problem I see is not that the app could crash by itself if tried to access the object that already has been deallocated (that would be the best), but that there's something else in memory and could trigger undefined behaviour.

@joshheald
Copy link
Contributor

Looks good!

It's quite dangerous how PaymentMethodsView.rootViewController isn't easy to enforce and missing it could result in a broken IPP flow. I thought about assertionFailure somewhere but couldn't find a good place to do it. Not mixing SwiftUI and UIKit with optional view controller setup would be great in the future.

I agree with asserting, perhaps through an Unit Test? At least to assert that the root is not nil.

Additionally, I'd also say that we should think of crashing on purpose in production if we land in this scenario, if we can: The weak reference to rootViewController here is unsafe since we may retain the reference to the class but have deallocated already the reference to the UIViewController. The problem I see is not that the app could crash by itself if tried to access the object that already has been deallocated (that would be the best), but that there's something else in memory and could trigger undefined behaviour.

A simpler/more effective check would probably be requiring the vc at init:

    init(dismiss: @escaping () -> Void, 
         rootViewController: UIViewController,
         viewModel: PaymentMethodsViewModel) {
        self.dismiss = dismiss
        self.rootViewController = rootViewController
        self.viewModel = viewModel
    }

That works but it's a bit annoying because requires the chain of SwiftUI callers to unwrap their rootViewController references, which all have to be optional because they're weak.

I think the main issue is that we're passing view controllers around :(

…ewController` is missing on view appearance.
@jaclync
Copy link
Contributor Author

jaclync commented May 22, 2024

Re @iamgabrielma:

I agree with asserting, perhaps through an Unit Test? At least to assert that the root is not nil.

Hmm as the problem is PaymentMethodsView usage inside InPersonPaymentsMenu (both SwiftUI views), I'm not sure if it's a common practice to test a SwiftUI view since we can't access subviews easily to check that PaymentMethodsView's rootViewController is set.

Additionally, I'd also say that we should think of crashing on purpose in production if we land in this scenario, if we can: The weak reference to rootViewController here is unsafe since we may retain the reference to the class but have deallocated already the reference to the UIViewController. The problem I see is not that the app could crash by itself if tried to access the object that already has been deallocated (that would be the best), but that there's something else in memory and could trigger undefined behaviour.

I agree some form of crashing/logging would be helpful for us to detect this misconfiguration early. In 6e205a0, when PaymentMethodsView appears, it now checks if rootViewController is nil and if so it:

  • Logs a non-fatal error in Sentry
  • Crash in debug builds only (assertionFailure)

Lemme know if you think crashing in production would be better!


Re @joshheald:

A simpler/more effective check would probably be requiring the vc at init:

    init(dismiss: @escaping () -> Void, 
         rootViewController: UIViewController,
         viewModel: PaymentMethodsViewModel) {
        self.dismiss = dismiss
        self.rootViewController = rootViewController
        self.viewModel = viewModel
    }

That works but it's a bit annoying because requires the chain of SwiftUI callers to unwrap their rootViewController references, which all have to be optional because they're weak.

This would be ideal, but in the wrapper hosting controller, self isn't available when initializing PaymentMethodsView 😞 Lemme know if you have other ideas to get around it.

I think the main issue is that we're passing view controllers around :(

Soo true!


I'm planning to merge the PR today, but I can open another PR for any further suggestions.

@iamgabrielma
Copy link
Contributor

Lemme know if you think crashing in production would be better!

In this case, since we haven't seen the problem while developing I think I gravitate more towards crashing in prod and having this recorded explicitly than allowing for undefined behaviour and crashing somewhere else pointing to the wrong trace just because it's using the same memory block. But since since we're logging the case anyway in Sentry I have no strong preference.

@jaclync
Copy link
Contributor Author

jaclync commented May 23, 2024

In this case, since we haven't seen the problem while developing I think I gravitate more towards crashing in prod and having this recorded explicitly than allowing for undefined behaviour and crashing somewhere else pointing to the wrong trace just because it's using the same memory block. But since since we're logging the case anyway in Sentry I have no strong preference.

Okay let's go with logging a non-fatal error in Sentry for now, it's a tradeoff where the app won't crash but we don't get alerted right away. Non-fatal events should still appear as issues in each release.

@jaclync jaclync merged commit 556b7fd into trunk May 23, 2024
22 checks passed
@jaclync jaclync deleted the fix/12790-fix-menu-collect-payment-card-reader branch May 23, 2024 01:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature: hub menu Related to the Hub Menu tab. feature: simple payments Related to the Simple Payments feature type: bug A confirmed bug.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Payments -> Collect Payment Flow is Broken
5 participants