Skip to content

feat(core): pass a buyer to the order item price strategy and export the default strategy - #5786

Open
dlhck wants to merge 3 commits into
majorfrom
dhoeck/plat-749-pass-a-buyer-to-the-order-item-price
Open

dlhck wants to merge 3 commits into
majorfrom
dhoeck/plat-749-pass-a-buyer-to-the-order-item-price

Conversation

@dlhck

@dlhck dlhck commented Nov 14, 2026 •

Copy link
Copy Markdown
Collaborator

Description

OrderItemPriceCalculationStrategy.calculateUnitPrice() had no buyer argument. The default strategy always priced a line for the customer of the order. A strategy layer that had to price a line for a different buyer, for example an anonymous buyer for a cart that several customers use, had to skip the inner chain and call ProductVariantService.calculateBuyerPriceForOrder() itself. Then a custom base strategy and the inner layers did not run for that line.

Changes:

  • calculateUnitPrice(ctx, productVariant, orderLineCustomFields, order, quantity, options?) gets an optional last argument, options?: { buyer?: Buyer }. Vendure does not pass it. A layer can pass it to inner.
  • DefaultOrderItemPriceCalculationStrategy passes options?.buyer to calculateBuyerPriceForOrder(). Without a buyer, the price is for the customer of the order, as before.
  • DefaultOrderItemPriceCalculationStrategy is now exported from @vendure/core. It follows the same import pattern as DefaultGuestCheckoutStrategy, which also imports a service, and default-config.ts already imports it. So the export adds no new import cycle.
  • The strategy layer types needed no change. StrategyLayer.passThrough<S>() takes its method types from S, so a layer that uses ...args: Parameters<...> forwards the new argument without casts.
  • Tests:
    • Unit tests cover the buyer of options, empty options, and a layer that passes its buyer to the default strategy.
    • The e2e test layer now builds a buyer with BuyerIdentityService.forCustomer() and passes it to inner. The inner layer receives that buyer, and the cart line gets that buyer's price.
    • A new e2e test imports DefaultOrderItemPriceCalculationStrategy from @vendure/core and checks that it is the base of the configured chain.
  • Docs: the pricing guide shows a layer that passes a buyer to inner, and the Vendure 4 upgrade notes describe the new argument and the export.

Also fixed in a separate commit: the Shop API field ProductVariant.buyerPrice(quantity, customFields) did not decode the ID values in customFields. Core enables only guards for field resolvers, so the IdInterceptor does not run for buyerPrice. With an encoding EntityIdStrategy, the ProductVariantBuyerPriceStrategy got the encoded ID (for example of a relation custom field on OrderLine), and addItemToOrder gave it the decoded ID. On Postgres the query then failed. The resolver now decodes a copy of its ID arguments with the same logic as the IdInterceptor. It decodes a copy because GraphqlValueTransformer.transformValues() changes the object in place, and the buyerPrice field of each variant in a query can share the same $customFields variable object. Without the copy, the second field would decode the ID again. A new e2e test with an OrderLine relation custom field checks that the strategy gets the decoded ID. The test fails without the fix.

Breaking changes

No. The new argument is optional, and existing strategies and layers work without changes. A layer that calls inner with its own argument list must pass options on, so that the inner strategies get the buyer of an outer layer. The docs tell you this.

Checklist

📌 Always:

  • I have set a clear title
  • My PR is small and contains a single feature
  • I have checked my own PR

👍 Most of the time:

  • I have added or updated test cases
  • I have updated the README if needed

OrderItemPriceCalculationStrategy.calculateUnitPrice() gets an optional last argument, options?: { buyer?: Buyer }. A strategy layer can pass its own Buyer to the inner chain. DefaultOrderItemPriceCalculationStrategy passes options.buyer to ProductVariantService.calculateBuyerPriceForOrder(). Without a buyer, the price is for the Customer of the Order, as before.

DefaultOrderItemPriceCalculationStrategy is now exported from @vendure/core.
@vercel

vercel Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
vendure-storybook Ready Ready Preview Nov 14, 2026 7:34am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@github-actions

github-actions Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Dashboard Preview: https://admin-dashboard-c6j6j34xi-vendure.vercel.app

The IdInterceptor does not run for field resolvers. Thus the buyerPrice field gave the encoded ID values of the customFields argument to the ProductVariantBuyerPriceStrategy, and addItemToOrder gave the decoded values. The resolver now decodes a copy of its ID arguments. A copy is necessary because the field of each variant can share the same variable object.
@sonarqubecloud

Copy link
Copy Markdown

@dlhck
dlhck marked this pull request as ready for review November 14, 2026 07:53
@vendure-developer-hub

Copy link
Copy Markdown

This branch was successfully deployed

1 active deployment
Preview — fff87d05 Deployed Nov 14, 2026 by vercel[bot]
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.

1 participant