Skip to content

Conversation

@joshheald
Copy link
Contributor

@joshheald joshheald commented Nov 13, 2025

Merge after #16345

Description

This PR shows merchants issues where a product is no longer available when they check out.

This can happen when a product or variation hasn't been resynced (i.e. no full sync) since it was deleted. When we create an order with these products, we show an error and prompt the merchant to remove the product, blocking checkout until it's resolved.

We also remove the item from the local catalog, when we can identify it.

There are some difficulties identifying missing variations, as we get different responses for these. That means we have to treat them separately, and when we can't identify the variation we don't give details of the missing items.

If a product is missing, we get the order back missing that line item – so we just compare it to the cart to find missing products.

If a variation is missing, we get an error back. Currently, we don't get the variation_id back in the error, and we don't have any line items to compare with.

In a separate backend PR we'll add the variation_id to the error, and backend folks are ensuring that'll be passed back through the proxy when using a WPCom connection too. We don't want to require a new version of Woo to use this, so older versions will fall back to a generic error and figuring out the broken product themselves. See Unknown variation missing video below.

Test Steps

  1. Launch the app and open POS with a local catalog site
  2. Add a product to the cart
  3. Delete the product in WP-Admin, and empty the trash
  4. Checkout
  5. Observe that you're shown the error and can remove or edit the cart
  6. Observe that the product is no longer in your local catalog after.
  7. Observe that the analytics events are tracked as expected

Repeat with variations... but be aware that you'll only see the generic errors. I can invite you to my test site to see it with the variation ID returned.

Analytics

Event Identifier Properties Description
woo_pos_checkout_outdated_item_detected_screen_shown Reason (deleted), sync_strategy (remote, local_catalog) Tracked when the user moves to checkout and the app detects an outdated item in the cart.
woo_pos_checkout_outdated_item_detected_edit_order_tapped Reason (deleted), sync_strategy (remote, local_catalog) Tracked when the user taps on edit order on the screen.
woo_pos_checkout_outdated_item_detected_remove_tapped Reason (deleted), sync_strategy (remote, local_catalog) Tracked when the user taps on remove item from cart on the screen.

Screenshots

Unknown variation missing

unknown.missing.variation.-.01.mp4

Known missing variations and products

known.missing.products.and.variations.-.01.mp4

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

joshheald and others added 7 commits November 12, 2025 13:41
Only keep the one confirmed error code observed in production:
- order_item_product_invalid_variation_id

Removed unverified error codes that were assumed but not confirmed:
- order_item_product_invalid_id
- woocommerce_rest_invalid_product_id

Additional error codes can be added as they are discovered through testing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…ame product

Previously, when multiple variations of the same product were in the cart and one
was deleted on the web, clicking "Remove products" would remove ALL variations
instead of just the deleted one.

Root cause: The removal logic matched items by product name, which is the same
for all variations of a product.

Solution:
- Added UUID field to MissingProductInfo struct to uniquely identify cart items
- Updated POSCart comparison logic to preserve individual UUIDs for each cart item
- Changed removeMissingProductsFromCart() to match by UUID instead of name
- Server-side errors (no UUID available) still remove all items as fallback

This ensures that only the specific variation that failed is removed from the cart,
not all variations of the parent product.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
When products or variations are detected as missing (either client-side
or server-side validation), they are now automatically removed from the
local catalog database to prevent them from appearing in future
sessions.

Changes:
- Added deleteProducts method to POSCatalogPersistenceService to remove
  products/variations from GRDB
- Added deleteProductsFromCatalog method to POSCatalogSyncCoordinator
  protocol and implementation
- Added removeMissingProductsFromCatalog to PointOfSaleAggregateModel
  that extracts product/variation IDs and calls catalog deletion
- Updated error view to await catalog deletion before retrying order
  creation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Added tests for:
- Cart-to-order comparison detecting missing products
- UUID-based identification distinguishing variations
- Catalog deletion removing products and variations
- Mock improvements to return order with items

Test Coverage:
- POSOrderServiceTests: 4 new tests for missing product detection
- POSCatalogPersistenceServiceTests: 5 new tests for deletion

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Changed missing product identification from UUID-based to product/variation
ID-based matching. This fixes two issues:
1. Multiple variations of the same product were all removed when only one
   was missing (UUID matched each cart item individually)
2. Variation names were computed incorrectly (attributes repeated 3x)

Changes:
- Modified CartOrderComparison.MissingCartItem to use productID/variationID
- Updated cart comparison to consolidate by product/variation ID
- Added extractProductIDs helper to determine product vs variation
- Changed MissingProductInfo to use productID/variationID instead of UUID
- Updated error view to extract and use product/variation IDs
- Updated aggregate model removal to match by product/variation ID
- Fixed test filter syntax to use filter(sql:) pattern
- Updated tests to verify productID/variationID instead of UUID

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
This update improves error handling when products or variations are unavailable during order creation:

- Add `data` field to NetworkError to capture additional error details from the API
- Extract variation_id from error data when available (direct API connections)
- Look up variation names from cart to show specific product information
- Conditionally show "Remove product" button only when specific products can be identified
- Show "Edit order" button for all error cases
- Pass cart through error handling chain to enable variation name lookup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@joshheald joshheald added this to the 23.7 milestone Nov 13, 2025
@joshheald joshheald added type: task An internally driven task. feature: POS labels Nov 13, 2025
@joshheald joshheald requested a review from Copilot November 13, 2025 10:32
@dangermattic
Copy link
Collaborator

dangermattic commented Nov 13, 2025

1 Warning
⚠️ This PR is larger than 300 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
1 Message
📖

This PR contains changes to Tracks-related logic. Please ensure (author and reviewer) the following are completed:

  • The tracks events must be validated in the Tracks system.
  • Verify the internal Tracks spreadsheet has also been updated.
  • Please consider registering any new events.
  • The PR must be assigned the category: tracks label.

Generated by 🚫 Danger

Copilot finished reviewing on behalf of joshheald November 13, 2025 10:36
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds error handling for missing products during order creation in the Point of Sale (POS) system. When products or variations are unavailable (e.g., due to deletion without a full catalog resync), the system now detects these issues during checkout and displays an error message prompting merchants to remove the problematic items.

Key Changes:

  • Implemented cart-to-order comparison logic to detect missing products and variations
  • Added error handling for both client-side validation (after order creation) and server-side validation errors
  • Created UI error messages to inform merchants about missing products with an option to remove them from the cart
  • Added functionality to delete missing products from the local catalog

Reviewed Changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Modules/Sources/Yosemite/Tools/POS/POSOrderService.swift Added validation to compare cart items with created order, throws error when items are missing
Modules/Sources/Yosemite/Tools/POS/POSCart.swift Implemented cart-order comparison logic with new public types for reporting discrepancies
Modules/Sources/Yosemite/Tools/POS/POSCatalogPersistenceService.swift Added method to delete specific products and variations from the local database
Modules/Sources/Yosemite/Tools/POS/POSCatalogSyncCoordinator.swift Added public method to coordinate deletion of products from the catalog
Modules/Sources/PointOfSale/Controllers/PointOfSaleOrderController.swift Enhanced error handling to extract missing product info from various error types and track analytics
Modules/Sources/PointOfSale/Models/PointOfSaleOrderState.swift Added new missingProducts error case with associated product information
Modules/Sources/PointOfSale/Models/PointOfSaleAggregateModel.swift Implemented logic to remove missing products from both cart and local catalog
Modules/Sources/PointOfSale/Presentation/TotalsView.swift Integrated missing products error message view into the UI
Modules/Sources/NetworkingCore/Network/NetworkError.swift Extended to expose errorData field for extracting additional error context
Modules/Sources/Yosemite/Model/Orders/OrderItem+POSMatching.swift Added helper method for matching order items with cart items
Modules/Tests/YosemiteTests/Tools/POS/POSOrderServiceTests.swift Added comprehensive tests for missing product detection scenarios
Modules/Tests/YosemiteTests/Tools/POS/POSCatalogPersistenceServiceTests.swift Added tests for product deletion functionality
Various mock files Updated mocks to support new protocol methods

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Nov 13, 2025

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 Numberpr16353-836c617
Version23.6
Bundle IDcom.automattic.alpha.woocommerce
Commit836c617
Installation URL3l7a2ol41e08o
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

joshheald and others added 5 commits November 13, 2025 10:48
This update removes unavailable products from the catalog immediately when they're detected during order creation, rather than waiting for user action:

- Remove identified products from catalog automatically in `checkOut()` after detecting missing products error
- Add `removeMissingProductsFromCart()` method to remove items from cart only
- Add `removeIdentifiedMissingProductsFromCatalog()` to handle catalog cleanup
- Update "Remove product" button to only remove from cart (catalog already cleaned)
- Works for both "Edit order" and "Remove product" flows

This ensures unavailable items are removed from the catalog regardless of which button the merchant chooses, preventing the same items from being added again.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Move deleteProductsFromCatalog out of private extension to make it properly public
- Add documentation for CartOrderComparison and nested types
- Remove unused OrderItem+POSMatching extension
- Optimize batch deletion using filter-based deleteAll instead of individual fetches and deletes

This improves code organization, documentation, and performance for catalog operations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
This method is no longer needed since we now automatically remove products from the catalog when they're detected as missing during checkout.

The other periphery warnings are for public API properties that are intentionally kept for API completeness even though not currently used internally.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
These properties are part of the public cart-order comparison API and are intentionally kept for API completeness and future use, even though they're not currently used internally.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Split long DDLogWarn message across multiple lines to comply with 163 character limit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@joshheald joshheald requested a review from Copilot November 13, 2025 11:20
Copilot finished reviewing on behalf of joshheald November 13, 2025 11:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

joshheald and others added 3 commits November 13, 2025 11:34
Remove descriptions from periphery:ignore comments to ensure they're properly recognized by the tool.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Move periphery:ignore comments from inline to the line before declarations as required by periphery tool.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Copilot finished reviewing on behalf of joshheald November 13, 2025 11:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@joshheald joshheald modified the milestones: 23.7, 23.8 Nov 13, 2025
joshheald and others added 8 commits November 13, 2025 15:21
Add three new analytics events to track when users encounter deleted/unavailable items during checkout:
- woo_pos_checkout_outdated_item_detected_screen_shown: Tracked when the error screen is shown
- woo_pos_checkout_outdated_item_detected_edit_order_tapped: Tracked when "Edit order" button is tapped
- woo_pos_checkout_outdated_item_detected_remove_tapped: Tracked when "Remove product" button is tapped

All events include:
- reason: "deleted" (hardcoded)
- sync_strategy: "local_catalog" or "remote" (based on isLocalCatalogEligible)

Changes:
- Added stat name constants to WooAnalyticsStat.swift
- Added events to TracksProvider decoration list for pos_ prefix
- Added syncStrategy property key to WooAnalyticsEvent+PointOfSale.swift
- Added three event factory functions to WooAnalyticsEvent+PointOfSale.swift
- Added tracking calls in PointOfSaleOrderSyncMissingProductsErrorMessageView.swift

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Remove incorrect 'public extension' wrapper - the functions should be directly inside the PointOfSale enum, not in a separate extension.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add public access modifier to struct, init, and body to allow usage from TotalsView.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Remove public access modifiers - the view should be internal like other error message views in the same module.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
The quantity property was never read anywhere in the code - it was only set when creating MissingProductInfo instances. Removed it to simplify the data structure.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Move catalog cleanup logic into handlePostSyncCleanup() method to make it clear this is a post-sync operation rather than part of the checkout flow itself.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
1. Extract shared ID extraction logic into Array extension on MissingProductInfo
2. Rename handlePostSyncCleanup to removeMissingProductsFromCatalogAfterSync for clarity
3. Use the new helper in both the error view and aggregate model

This eliminates code duplication and makes the purpose of the method more explicit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@joshheald joshheald added the category: tracks Related to analytics, including Tracks Events. label Nov 13, 2025
@joshheald
Copy link
Contributor Author

@iamgabrielma Apologies that these are getting long. I can split it if you like, e.g. move analytics out, but it seems to make more sense to review and test as a single feature.

You should have an invite to a store for testing the specific variations removal.

@joshheald joshheald marked this pull request as ready for review November 13, 2025 16:16
@iamgabrielma
Copy link
Contributor

👋 I've started to review this one but I'm not sure I'll be able to finish it EOD

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

category: tracks Related to analytics, including Tracks Events. feature: POS type: task An internally driven task.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants