Skip to content

Add price visibility control for B2B stores - #46

Open
koenvdwetering wants to merge 2 commits into
masterfrom
claude/b2b-price-datalayer-hide-c4bp2n
Open

koenvdwetering wants to merge 2 commits into
masterfrom
claude/b2b-price-datalayer-hide-c4bp2n

Conversation

@koenvdwetering

Copy link
Copy Markdown

Summary

Adds a new feature to hide prices in the dataLayer for specific customer groups on B2B stores. This allows stores that only show prices to logged-in customers to prevent price leakage through the dataLayer (which is readable in page source and browser console).

Key Changes

  • New PriceVisibility utility class (Util/PriceVisibility.php): Central logic for determining whether prices should be exposed and how to filter them (remove entirely or send as 0)

  • New configuration options under Stores > Configuration > AdPage > AdPage GTM > Price visibility:

    • isHidePricesEnabled(): Toggle the feature on/off
    • getHidePricesCustomerGroups(): Select which customer groups get no prices
    • getHidePricesMode(): Choose between removing prices or sending them as 0
  • New source models:

    • CustomerGroups: Provides all customer groups including "NOT LOGGED IN" (unlike Magento's default which filters it out)
    • HidePriceMode: Enum for remove vs. zero modes
  • Integration with price-related tags and events:

    • CurrentPrice: Product page price tag now filters through PriceVisibility
    • CartValue: Cart total value now filters through PriceVisibility
    • CartItemDataMapper: Item prices in cart events filtered
    • ProductDataMapper: Product prices in all events filtered
    • AddToCart, AddPaymentInfo, BeginCheckout, ViewCart: Events now handle null prices gracefully
  • TagParser enhancement: Tags that resolve to null are now omitted from the dataLayer entirely (instead of being pushed as null), allowing price hiding to work cleanly

  • Comprehensive unit tests (Test/Unit/Util/PriceVisibilityTest.php): 8 test cases covering feature disabled, removal mode, zero mode, customer group matching, empty groups, and non-frontend contexts

Implementation Details

  • Server-side contexts are unaffected: Order data on success pages and the order_created webhook always keep real amounts to preserve revenue reporting
  • Non-storefront contexts bypass filtering: Cron jobs, admin panel, and REST API contexts (detected via AppState::getAreaCode()) always expose real prices
  • Customer session proxy: Uses Magento\Customer\Model\Session\Proxy to avoid instantiating the session in non-storefront contexts
  • Graceful error handling: If area code cannot be determined, prices are kept (safe default)
  • Configuration defaults: Feature is disabled by default (no impact on existing stores)

Documentation

Updated USAGE.md with detailed explanation of the feature, configuration options, and coverage table showing which price keys are affected across different events.

https://claude.ai/code/session_01UpDMDuDY9kEpftGqrUnDyJ

B2B stores often only show prices to logged in (or approved) customers. Everything
that ends up in the dataLayer is readable in the page source and in the browser
console, so the price was still exposed there even when the storefront hid it.

Adds Stores > Configuration > AdPage > AdPage GTM > Price visibility with:

- Hide prices in the dataLayer (off by default, so nothing changes on upgrade)
- Hide prices for customer groups (multiselect including NOT LOGGED IN)
- Hide prices by: leave the key out (default) or send the price as 0

Util\PriceVisibility is the single decision point. It only guards the storefront:
the admin panel, the REST API and the cron job that retries failed webhooks have
no customer session and keep the real amounts. Order data is deliberately left
alone as well - the purchase and refund events and the order_created webhook keep
their real amounts, since a visitor who cannot see prices cannot place an order and
blanking those would break revenue reporting.

Covered for a hidden customer group: items[].price on every event that carries
items, ecommerce.value on view_item, add_to_cart, view_cart, begin_checkout and
add_payment_info, and the price in the product data block on the product page.

TagParser now drops the key of a tag that resolves to null instead of pushing
"key": null, which is what it already did for literal null values and what the
nullable price tags rely on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpDMDuDY9kEpftGqrUnDyJ
The CI workflow only runs Test/Integration, so the unit test added alongside
Util\PriceVisibility never executes there. This adds an integration test that runs
in CI and exercises the real path: a guest on the storefront, the config switched
on through @magentoConfigFixture, and ProductDataMapper asserted to leave the price
key out (remove mode) or report it as 0 (zero mode).

Asserts on the presence and absence of the key rather than an exact amount, so
the store's tax configuration cannot make it brittle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpDMDuDY9kEpftGqrUnDyJ
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.

2 participants