Skip to content

Fix Tap to Pay payments using a wrong account after store switch#16676

Open
staskus wants to merge 6 commits intorelease/24.1from
woomob-2197-error-dotcom-invalid-rest-route-stripe-account-sync
Open

Fix Tap to Pay payments using a wrong account after store switch#16676
staskus wants to merge 6 commits intorelease/24.1from
woomob-2197-error-dotcom-invalid-rest-route-stripe-account-sync

Conversation

@staskus
Copy link
Contributor

@staskus staskus commented Feb 13, 2026

Closes: WOOMOB-2197

Description

After switching between stores with separate Stripe accounts, Tap to Pay payments on the new store were charged to the previous store's Stripe account. The capture then failed with "No such payment_intent" because the PI didn't exist on the current store's backend.

Root cause

The Stripe Terminal SDK caches the connection token from when the reader was connected — it does not re-fetch it per payment. If the reader stays connected (or reconnects) with Store A's token after switching to Store B, every payment intent is created on Store A's Stripe account while the server-side capture targets Store B.

On trunk, disconnect() during reset() is the only protection against this. Multiple conditions can cause it to be ineffective:

  1. DispatchGroup bug (most likely trigger): group.leave() fired immediately after dispatching CardPresentPaymentAction.reset, completing the store switch ~1 second before the async disconnect finished. Any preflight starting in this window sees a stale connected reader.
  2. Uncancelled auto-reconnection: TapToPayReconnectionController was not cancelled during store switch — it could reconnect with Store A's cached siteID after disconnect.
  3. No siteID validation in preflight: Path A only checked discoveryMethod, not siteID — it blindly reused any connected TTP reader, skipping discovery (the only code path that refreshes the connection token).
  4. Stale config provider: CommonReaderConfigProvider.siteID was never cleared during store switch — SDK auto-reconnect would fetch a token for the wrong store.
  5. Missing return in connect paths: After detecting connectionAttemptInvalidated, both Bluetooth and TTP connect paths called promise(.failure) but fell through to connectedReadersSubject.send() and promise(.success), publishing the reader as connected even though the connection was invalidated.

Once the reader is connected with the wrong token, the bug is self-reinforcing: Path A skips discovery on every retry, so the token is never corrected until the reader is explicitly disconnected.

Bug was reproduced locally by simulating a failed disconnect during store switch, produced the exact same error the customer reports.

Changes

Fix 1: Reset config provider context during store switch

CardPresentPaymentStore.reset() now calls commonReaderConfigProvider.resetContext() to clear the stale siteID. Auto-reconnect after store switch will fail rather than succeed with wrong credentials.

Fix 2: Validate siteID when reusing a connected reader in preflight

Preflight now checks that the connected reader's payment gateway siteID matches the current store. On mismatch, it disconnects and reconnects through full discovery, forcing a fresh connection token.

Fix 3: Cancel reconnection during store switch

logOutOfCurrentStore() now calls cancelReconnection() to stop in-flight TTP reconnections before the store context changes.

Fix 4: Wait for disconnect before completing store switch

CardPresentPaymentAction.reset now has an onCompletion callback. group.leave() is wired to it instead of firing immediately — finalizeStoreSelection() only runs after disconnect + clear completes.

Fix 5: Return after invalidated connection attempt

Both Bluetooth and TTP connect paths were missing a return after the connectionAttemptInvalidated check. After calling promise(.failure) and disconnect(), execution fell through to publish the reader as connected and call promise(.success). Added return to both paths so invalidated connections don't leak stale reader state.

Fix Protects against
1 — reset config provider Auto-reconnect can't fetch a token for the wrong store
2 — siteID check in preflight Won't reuse a reader connected for a different store
3 — cancel reconnection In-flight reconnections stopped before store context changes
4 — wait for disconnect Closes the ~1s window where store switch outraced disconnect
5 — return after invalidation Invalidated connections don't publish stale reader state

Testing

Multi-store TTP

  • Connect TTP on Store A → switch to Store B → take TTP payment → succeeds on Store B
  • Connect TTP on Store A → switch to Store B → background/foreground → take TTP payment → succeeds on Store B
  • Connect TTP on Store A -> Switch to Store B -> Make TTP Payment -> Background -> Tap a new order notification from store A -> Take TTP payment -> Succeeds on Store A

Single-store regression

  • Normal TTP payment flow works end to end
  • Background/foreground → TTP payment works
  • Bluetooth reader payment flow is unaffected

Unit tests

  • test_cancelReconnection_resets_isReconnecting
  • test_reset_clears_config_provider_context
  • test_reset_disconnects_card_reader

Video - Bug was reproduced locally by simulating a failed disconnect during store switch, produced the exact same error the customer reports.

ScreenRecording_02-16-2026.10-54-53_1.MP4

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

@staskus staskus added type: bug A confirmed bug. feature: mobile payments Related to mobile payments / card present payments / Woo Payments. labels Feb 13, 2026
@dangermattic
Copy link
Collaborator

dangermattic commented Feb 13, 2026

1 Warning
⚠️ This PR is assigned to the milestone 24.1 ❄️. The due date for this milestone has already passed.
Please assign it to a milestone with a later deadline or check whether the release for this milestone has already been finished.

Generated by 🚫 Danger

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Feb 13, 2026

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

App NameWooCommerce iOS Prototype
Build Numberpr16676-fdb7e1d
Version24.1
Bundle IDcom.automattic.alpha.woocommerce
Commitfdb7e1d
Installation URL6l5fpqscr0sco
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@staskus staskus force-pushed the woomob-2197-error-dotcom-invalid-rest-route-stripe-account-sync branch 3 times, most recently from 602fcb8 to ecbf88a Compare February 16, 2026 08:09
When switching stores, CardPresentPaymentStore.reset() only disconnected
the reader and cleared the service, but left the CommonReaderConfigProvider
with a stale siteID and remote from the previous store. This caused the
Stripe Terminal session to bind to the wrong merchant's Stripe account
after reconnection, resulting in payment intents created on the wrong
account and failed captures on the correct one.

- Add resetContext() to CardReaderRemoteConfigLoading protocol
- Clear paymentGatewayAccount, paymentCancellable, and refundCancellable
  during reset
- Add unit tests for reset behavior
When a reader was already connected with the correct discovery method,
preflight would reuse it without checking whether the payment gateway
account's siteID matched the current store. After a store switch, this
could allow a reader connected for Store A to be reused for Store B
without going through discovery, which would skip the config provider
update and result in payment intents on the wrong Stripe account.

Now the preflight disconnects and reconnects if the siteID doesn't
match, similar to existing handling for mismatched discovery methods.
An automatic Tap to Pay reconnection started for Store A could still
be in progress when the user switches to Store B. The reconnection
controller caches its siteID at creation time, so completing the
reconnection after the switch would leave a reader connected with
Store A's credentials.

This adds cancelReconnection() to TapToPayReconnectionController and
calls it from SwitchStoreUseCase.logOutOfCurrentStore() to ensure any
in-flight reconnection is stopped before the store context changes.
…witch

The DispatchGroup in SwitchStoreUseCase.logOutOfCurrentStore() called
group.leave() immediately after dispatching CardPresentPaymentAction.reset,
without waiting for the async disconnect to complete. This created a
~1-second window on every store switch where the reader was still
connected with old store credentials while the app had already switched
to the new store.

Add an onCompletion handler to CardPresentPaymentAction.reset and wire
group.leave() to it, so finalizeStoreSelection() only runs after the
reader is fully disconnected and cleared.
Remove paymentCancellable, refundCancellable, and paymentGatewayAccount
cleanup from reset() — these are unrelated to the wrong-Stripe-account
bug. Keep only commonReaderConfigProvider.resetContext() which is the
core of Fix 1.
…tate

Both Bluetooth and TTP connect paths were missing a return after the
connectionAttemptInvalidated check. After calling promise(.failure) and
disconnect(), execution fell through to connectedReadersSubject.send()
and promise(.success), publishing the reader as connected even though
the connection was invalidated. Add return to both paths.
@staskus staskus force-pushed the woomob-2197-error-dotcom-invalid-rest-route-stripe-account-sync branch from 71f2990 to fdb7e1d Compare February 16, 2026 11:03
@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Feb 16, 2026

🤖 Build Failure Analysis

This build has failures. Claude has analyzed them - check the build annotations for details.

@staskus staskus changed the title Reset card reader config provider context during store switch Fix Tap to Pay payments hitting wrong Stripe account after store switch Feb 16, 2026
@staskus staskus changed the base branch from trunk to release/24.1 February 16, 2026 11:03
@staskus staskus changed the title Fix Tap to Pay payments hitting wrong Stripe account after store switch Fix Tap to Pay payments using a wrong account after store switch Feb 16, 2026
@staskus staskus added this to the 24.1 ❄️ milestone Feb 16, 2026
@staskus staskus requested a review from toupper February 16, 2026 11:05
@staskus staskus marked this pull request as ready for review February 16, 2026 11:07
@staskus
Copy link
Contributor Author

staskus commented Feb 16, 2026

@toupper

The issue was very hard to reproduce. I essentially had to force some conditions to get the same error. However, I didn't jump onto the issue organically. It either means this is a super-rare case, which is possible since we haven't seen many reports about this. And/Or the user setup has some special conditions making the issue more likely to occur. And/Or I have missed a possible path it pay happen.

In the end, I'm confident that the issue happens because we have user switching two stores both using TTP. Therefore, the fix is about making sure one TTP connection and config is cleared, reconnection canceled, and connection properly done when switching to a new store.

@staskus staskus removed the request for review from toupper February 16, 2026 11:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: mobile payments Related to mobile payments / card present payments / Woo Payments. type: bug A confirmed bug.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants