v2.7.0: Save payment methods, improved links, and performance improvements
Highlights
Support multiple payment account holders for customers
Warning
Breaking changes
This release adds support for a customer to have multiple account holders, therefore supporting multiple payment providers at the same time.
If you were using customer.account_holder
in your implementation, you must change it to customer.account_holders
and choose the relevant one for the provider_id you want to use.
If you haven't done anything custom with payments and account holders, there are no breaking changes.
Enforce integrity constraints on module links
This release enforces integrity constraints on module links in the application layer.
The constraints are enforced as exemplified below:
One-to-one
export default defineLink(
ProductModule.linkable.product,
CustomModule.linkable.customModel
)
// This succeeds
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"customModule": {
custom_id: "cus_123",
},
})
// This throws
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"customModule": {
custom_id: "cus_321",
},
})
// This throws
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_321",
},
"customModule": {
custom_id: "cus_123",
},
})
One-to-many / Many-to-one
export default defineLink(
ProductModule.linkable.product,
{
linkable: CustomModule.linkable.customModel,
isList: true,
}
)
// export default defineLink(
// {
// linkable: ProductModule.linkable.product,
// isList: true,
// },
// CustomModule.linkable.customModel
// )
// This succeeds
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"customModule": {
custom_id: "cus_123",
},
})
// This succeeds
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"customModule": {
custom_id: "cus_321",
},
})
// This throws
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_456",
},
"customModule": {
custom_id: "cus_123",
},
})
Many-to-many
export default defineLink(
{
linkable: ProductModule.linkable.product,
isList: true
},
{
linkable: CustomModule.linkable.customModel,
isList: true,
}
)
// This succeeds
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"customModule": {
custom_id: "cus_123",
},
})
// This succeeds
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"customModule": {
custom_id: "cus_321",
},
})
// This succeeds
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_456",
},
"customModule": {
custom_id: "cus_123",
},
})
Introduce dynamic pricing workflow hooks
This release introduces workflow hooks to override the default pricing context passed to the pricing module when calculating prices for variants and shipping options.
Prices with custom rules need a different context than what is passed by default. For example, say you have a custom rule that ties prices with a specific location location_id: loc_1234
. To account for this rule in the price calculation logic, the pricing context needs to be populated with a location_id
.
The pricing hooks are used as follows:
// src/workflows/hooks/pricing-hooks.ts
import { addToCartWorkflow } from "@medusajs/medusa/core-flows";
import { StepResponse } from "@medusajs/workflows-sdk";
addToCartWorkflow.hooks.setPricingContext(() => {
return new StepResponse({
location_id: "loca_1234", // Special price for in-store purchases
});
});
Assuming you have a custom pricing rule { attribute: "location_id", operator: "eq", value: "loca_1234" }
, this would be part of the pricing result set when retrieving the associated product variant or adding the same to the cart.
The hooks have been introduced to the following workflows:
addToCartWorkflow
createCartsWorkflow
listShippingOptionsForCartWorkflow
refreshCartItemsWorkflow
updateLineItemInCartWorkflow
addLineItemsToOrderWorkflow
updateClaimShippingMethodWorkflow
createOrderWorkflow
updateExchangeShippingMethodWorkflow
createOrderEditShippingMethodWorkflow
updateOrderEditShippingMethodWorkflow
createCompleteReturnWorkflow
updateReturnShippingMethodWorkflow
Please don't hesitate to submit a Github issue, if you want us to consider adding the hook to more workflows.
Resolve issues with broken plugin dependencies in development server
This release reworks how admin extensions are loaded from plugins and how extensions are managed internally in the dashboard project.
Previously we loaded extensions from plugins the same way we do for extensions found in a user's application. This being scanning the source code for possible extensions in .medusa/server/src/admin
, and including any extensions that were discovered in the final virtual modules.
This was causing issues with how Vite optimizes dependencies and would lead to CJS/ESM issues.
To circumvent the above issue, we have changed the strategy for loading extensions from plugins. We now build plugins slightly differentβif a plugin has admin extensions, we build those to .medusa/server/src/admin/index.mjs
and .medusa/server/src/admin/index.js
for a ESM and CJS build.
When determining how to load extensions from a source, we follow these rules:
- If the source has a
medusa-plugin-options.json
or is the root application, we determine that it is a local extension source and load extensions as previously through a virtual module. - If it has neither of the above, but has a
./admin
export in itspackage.json
, then we determine that it is a package extension, and we update the entry point for the dashboard to import the package and pass its extensions along to the dashboard manager.
Changes required by plugin authors
The change has no breaking changes, but requires plugin authors to update the package.json of their plugins to also include a ./admin export. It should look like this:
{
"name": "@medusajs/plugin",
"version": "0.0.1",
"description": "A starter for Medusa plugins.",
"author": "Medusa (https://medusajs.com)",
"license": "MIT",
"files": [
".medusa/server"
],
"exports": {
"./package.json": "./package.json",
"./workflows": "./.medusa/server/src/workflows/index.js",
"./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
"./modules/*": "./.medusa/server/src/modules/*/index.js",
"./providers/*": "./.medusa/server/src/providers/*/index.js",
"./*": "./.medusa/server/src/*.js",
"./admin": {
"import": "./.medusa/server/src/admin/index.mjs",
"require": "./.medusa/server/src/admin/index.js",
"default": "./.medusa/server/src/admin/index.js"
}
},
}
Improve performance of price-selection
This release comes with significant optimizations of the pricing retrieval query to improve performance, especially on large datasets.
We have:
- Removed costly subqueries and excessive joins
- Leveraged targeted subqueries and indexed filters for rule checks
- Optimized queries without rule contexts to bypass unnecessary logic
The improvements have been tested on a dataset with ~1,200,000 prices and ~1,200,000 price rules. The impact of the changes are as follows:
- Before: ~718ms execution time
- After: ~17ms execution time
We are dedicated to continuously improve performance, so you can expect further improvements going forward.
Improve the robustness of payment workflows
This release improves the robustness of payment workflows by squashing a range of minor bugs.
These fixes can be found in the following PRs:
Features
- fix(medusa,utils,test-utils,types,framework,dashboard,admin-vite-plugin,admin-bundler): Fix broken plugin dependencies in development server by @kasperkristensen in #11720
- refactor: use module name as the snapshot name by @thetutlage in #11802
- feat: Change customer to account_holder to be one-to-many by @sradevski in #11803
- feat: add check for uniqueness when creating links with isList=false by @thetutlage in #11767
- fix(types,order,medusa): Create credit lines + hooks by @riqwan in #11569
- feat(dashboard,js-sdk,admin-shared): add customer addresses + layout change by @riqwan in #11871
- feat(core-flows,types,utils): make payment optional when cart balance is 0 by @riqwan in #11872
- feat(medusa,types): add enabled plugins route by @riqwan in #11876
- feat: Add support for generating a production-ready config out of the⦠by @sradevski in #11850
- feat: add support for accessing step results via context by @thetutlage in #11907
- feat(core-flows, types): calculated shipping in RMA flows by @fPolic in #11533
- feat(core-flows,types,order,cart): assign tax lines only to regular products by @riqwan in #11994
- feat: Set pricing context hook by @thetutlage in #11891
- feat(medusa,core-flows,types,js-sdk): Draft Order workflows and API endpoints by @kasperkristensen in #11805
- feat: add
hasMany
flag to enforce in app link uniqueness by @thetutlage in #12039 - feat(core-flows,types): export fetchShippingOptionForOrderWorkflow by @shahednasser in #12103
Bugs
- fix(workflow-engines): race condition when retry interval is used by @adrien2p in #11771
- fix(workflow-engine-*): scheduled jobs interval by @adrien2p in #11800
- fix(dashboard): Include thumbnail in default product query by @kasperkristensen in #11825
- fix(promotion): percentage value is accounted for in buyget promotions by @riqwan in #11799
- fix(workflow-engine-inmemory): Fail trap for integration tests by @adrien2p in #11839
- fix(dashboard): display inventory item sku by @fPolic in #11856
- fix(dashboard): location injection zones by @fPolic in #11845
- fix: register locking provider with its unique id by @thetutlage in #11874
- fix: hooks auto-generated types to have precise input options by @thetutlage in #11886
- feat(core-flows): revisit fulfillment flow reservations and support inventory kit by @fPolic in #11790
- fix(ui): Disable select cells if the row cannot be selected by @kasperkristensen in #11442
- fix(js-sdk): fix the API route used in fulfillment.cancel method by @shahednasser in #11936
- fix(js-sdk): fix the return type of the
promotion.listRules
method by @shahednasser in #11937 - fix(dashboard): Reduce size of Notifications Trigger by @kasperkristensen in #11955
- fix(js-sdk, types): fixes types and deprecate duplicate methods by @shahednasser in #11975
- fix(order): caculate received total for pending difference by @fPolic in #11919
- fix(utils,promotion): fix case where mutliple percentage promotions werent applied by @riqwan in #11992
- fix(dashboard): Product export fields by @olivermrbl in #11999
- fix(medusa): skip index when tags or categories present by @carlos-r-l-rodrigues in #12006
- fix(dashboard): refund form input by @fPolic in #11993
- fix(dashboard): notification list scroll flickering by @fPolic in #11911
- fix(dashboard): trim some product create inputs to prevent creation error by @fPolic in #12004
- fix(dashboard): return form pending diff by @fPolic in #12007
- fix(dashboard): RMA allow shipping unset by @fPolic in #12021
- fix(dashboard): Allow empty subpaths for route extensions by @kasperkristensen in #12030
- fix(dashboard,medusa,types): allow searching for promotion rule options by @kasperkristensen in #12028
- feat(core-flows, types): try payment cancelation if cart complete fails in the webhook by @fPolic in #11832
- fix(utils): Enable Redis locking by default in Cloud by @olivermrbl in #12017
- fix: validate module names to disallow unallowed characters by @thetutlage in #12025
- fix(core-flows): applying product type promotion by @fPolic in #12051
- fix(core-flows,dashboard): handling authorized payment collection on OE by @fPolic in #11958
- fix(order): update add item unit price by @fPolic in #12072
- fix(dashboard): product create discountable flag by @fPolic in #12073
- fix(orchestration): handle multiple shortcuts with same name by @carlos-r-l-rodrigues in #12066
- fix(workflow-engine-*): Prevent passing shared context reference by @adrien2p in #11873
- fix: allow backorder variants to be added to cart even if no locations by @peterlgh7 in #12083
- fix(orchestrator): save checkpoint before async step by @carlos-r-l-rodrigues in #12138
- fix(dashboard): product type metadata form by @fPolic in #12149
Documentation
- feat: add Quote Management guide by @shahednasser in #11741
- docs: update sidebars and navigation bar by @shahednasser in #11793
- docs: fix restaurant-delivery recipe after latest update by @shahednasser in #11806
- docs: add missing details related to links and migrations by @shahednasser in #11809
- docs: improvement to sidebar by @shahednasser in #11810
- docs: add worker mode documentation + improvements by @shahednasser in #11812
- docs: add documentation for Locking Module by @shahednasser in #11824
- Update page.mdx by @MedusaNick in #11841
- Update page.mdx by @AmbroziuBaban in #11859
- docs: improve links to other modules docs by @shahednasser in #11868
- docs: update and improve marketplace recipe by @shahednasser in #11870
- docs: read-only links + other changes by @shahednasser in #11878
- docs: added how-to guides for architectural modules by @shahednasser in #11883
- docs: improved data model docs by @shahednasser in #11884
- docs: cache revalidation in next.js storefront + storefront totals by @shahednasser in #11887
- docs: generated references after collapse types improvement by @shahednasser in #11889
- docs-util: collapse types in references by @shahednasser in #11888
- docs: add prepare script to generate sidebar by @shahednasser in #11894
- docs: added to workflow legend + example improvements by @shahednasser in #11895
- docs: fix request http method in wishlist guide by @shahednasser in #11902
- docs: added product review guide by @shahednasser in #11852
- docs: improved search results by @shahednasser in #11909
- docs: added troubleshooting guides + improvements by @shahednasser in #11927
- docs: fix sitemap of ui docs by @shahednasser in #11928
- docs: add a note to stripe customization in next.js starter by @shahednasser in #11929
- docs: add missing ui dependency by @shahednasser in #11931
- docs-util: add tool to generate mapping between JS SDK examples and routes by @shahednasser in #11933
- docs: typos and examples fixes by @shahednasser in #11954
- docs: fixes to search in API references by @shahednasser in #11959
- docs-util: add JS SDK examples to generated OAS by @shahednasser in #11935
- docs-utils: support routes that include global variables by @shahednasser in #11976
- docs: fixes to quotes management guide by @shahednasser in #11981
- docs: clarify limitations in query brands guide by @shahednasser in #11980
- docs-util: sort examples to show JS SDK first by @shahednasser in #11986
- docs: small improvements to the general deployment guide by @shahednasser in #11978
- docs: add sitemap to API reference by @shahednasser in #11985
- docs: remove custom searching mechanism by @shahednasser in #11984
- docs: improve page titles by @shahednasser in #11987
- docs-util: add a prefix for JS SDK examples in OAS by @shahednasser in #11989
- docs: update storefront development guides to use JS SDK [1] by @shahednasser in #11991
- docs: update storefront development guides to use JS SDK [2] by @shahednasser in #12015
- docs: add JS SDK snippets in API reference introduction sections by @shahednasser in #12018
- docs: added tutorial for abandoned cart by @shahednasser in #11917
- docs: added algolia guide by @shahednasser in #11971
- docs: migrate magento to medusa guide by @shahednasser in #11418
- docs: updates and improvements to JS SDK guides by @shahednasser in #12026
- docs: small fix to abandoned cart guide by @shahednasser in #12029
- docs: sort API reference sidebar items and sections by @shahednasser in #12032
- docs: added a routes summary section in API reference by @shahednasser in #12035
- docs-util: handle workflows with intersection hook types by @shahednasser in #12099
- docs: fixes to content and UI by @shahednasser in #12101
- docs: update bulk editor user guide + add redirect from old v1 next js by @shahednasser in #12104
- docs: added missing information on plugin migrations by @shahednasser in #12108
- Fixed typo by @Dev-Abdullah-H in #12107
- docs: remove start-cluster command + update start options by @shahednasser in #12116
Chores
- chore(types): add tsdocs for locking module and provider by @shahednasser in #11818
- chore(types): small fixes to locking TSDocs by @shahednasser in #11823
- chore(workflow-engine-redis): remove repeatable jobs from old queue by @carlos-r-l-rodrigues in #11822
- chore: admin product list transform filter by @carlos-r-l-rodrigues in #11821
- chore(workflow-engine): export cancel method by @carlos-r-l-rodrigues in #11844
- chore(ui-preset,icons,dashboard) by @kasperkristensen in #11734
- chore: added / improved tsdocs to cache, event, file, and notification modules by @shahednasser in #11879
- chore: fix links in contribution guidelines by @shahednasser in #11829
- chore(js-sdk,types): add missing examples for JS SDK methods by @shahednasser in #11934
- chore(core-flows): price list skip when no data by @carlos-r-l-rodrigues in #11977
- chore(js-sdk): improve the TSDocs of auth methods in JS SDK by @shahednasser in #12033
- chore(link-modules): backward compatible links by @carlos-r-l-rodrigues in #12062
- chore(core-flows): added TSDocs for setPricingContext hooks by @shahednasser in #12100
- chore(types,utils): fixes to TSDocs for HTTP types and payment provider by @shahednasser in #12102
- chore(js-sdk, types): add TSDocs for plugin methods in JS SDK by @shahednasser in #12106
- chore(): Emit events in batch and index process event ids in batch by @adrien2p in #12097
- chore(framework): slightly improve maybe apply link filter middleware by @adrien2p in #12113
- chore(pricing): improve calculate prices performances by @adrien2p in #12120
- chore: retry util on tests by @carlos-r-l-rodrigues in #12126
- chore(medusa-test-utils): Prevent waiting for event indefinately by @adrien2p in #12137
- chore(pricing): Pricing retrieval improvements by @adrien2p in #12128
- chore(promotion): Improve performances [1] by @adrien2p in #12129
- chore: Cache available price rule attributes by @adrien2p in #12144
- chore: Patch changesets to avoid major bumps by @olivermrbl in #12151
- chore(product): Missing index in migration by @adrien2p in #12150
Other Changes
- Polish translations improvements by @RadzioN in #11808
- fix: preserve payment sessions during certain Stripe errors for webhook reconciliation by @Saryazdi-Saman in #11798
- fix(core-flows): add no_notification to shipment created event by @OttavioCas in #11836
- fix(dashboard): repaired scrollbar behavior in variant inventory form by @RadzioN in #11710
- fix(dashboard): EN translation mistypes, missing words by @bandzaitis in #11904
- feat(dashboard): add support for Lithuanian language by @bandzaitis in #11903
- Minor Translation Corrections (German / DE) by @pixelwiese in #11990
- Add Vietnamese Language by @sonvivu in #11969
- fix: fix German translation for 'captured' by @philippeckelintive in #12008
- fix(dashboard): add missing IT translations for "filters" group by @SteelRazor47 in #12069
- Added Korean translation by @PauseB in #12076
- Feat: Add quantity inventory translation for es and en by @paolorossig in #11564
New Contributors
- @RadzioN made their first contribution in #11808
- @Saryazdi-Saman made their first contribution in #11798
- @OttavioCas made their first contribution in #11836
- @AmbroziuBaban made their first contribution in #11859
- @bandzaitis made their first contribution in #11904
- @sonvivu made their first contribution in #11969
- @philippeckelintive made their first contribution in #12008
- @SteelRazor47 made their first contribution in #12069
- @PauseB made their first contribution in #12076
- @Dev-Abdullah-H made their first contribution in #12107
- @paolorossig made their first contribution in #11564
- @peterlgh7 made their first contribution in #12083
Full Changelog: v2.6.1...v2.7.0