Skip to content

v2.7.0: Save payment methods, improved links, and performance improvements

Compare
Choose a tag to compare
@olivermrbl olivermrbl released this 11 Apr 08:53
· 119 commits to develop since this release

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 its package.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

Documentation

Chores

Other Changes

New Contributors

Full Changelog: v2.6.1...v2.7.0