Skip to content

bug: plugin-ecommerce confirmOrder stock $inc leaves _versions stale #16503

@jhb-dev

Description

@jhb-dev

Describe the Bug

The ecommerce plugin's confirmOrder endpoint decrements stock with a direct payload.db.updateOne using a Mongo $inc:

await payload.db.updateOne({
  id,
  collection: variantsSlug, // or productsSlug
  data: {
    inventory: {
      $inc: item.quantity * -1,
    },
  },
})

Source: https://github.com/payloadcms/payload/blob/v3.84.1/packages/plugin-ecommerce/src/endpoints/confirmOrder.ts#L182-L210

When the products / variants collection has drafts enabled (versions.drafts: true), Payload stores the document in two places:

  • the main products collection (latest published doc)
  • the _products_versions collection (all snapshots, with latest: true marking the row that the admin list view reads through queryDrafts)

The payload.db.updateOne call only writes to the main collection. The _versions row marked latest: true keeps its pre-decrement inventory, so the admin list view (and any find({ draft: true }) read) continues to show the old stock value after order confirmation. In practice: a customer purchases a product, the order is created and paid, but the admin "Products" list keeps showing the pre-purchase inventory.

Suggested fix: when the target collection has drafts enabled, mirror the $inc onto the latest: true versions doc as well (e.g. via payload.db.updateVersion).

Link to the code that reproduces this issue

https://github.com/jhb-dev/payload-ecommerce-decrement-versions-stale

Reproduction Steps

  1. Clone the reproduction repository and run the development server
  2. The onInit seed (src/seed.ts) creates a Product with inventory: 10, then calls the same payload.db.updateOne({ inventory: { $inc: -1 } }) shape used by the plugin's confirmOrder
  3. The seed reads back both the main collection doc and the _versions doc with latest: true, and logs them
  4. Expected: both reads return inventory: 9
  5. Actual: main collection returns 9, but _versions (and find({ draft: true }), which is what the admin list view uses) still returns 10

Server log output:

[11:36:54] INFO: --- Reproduction: stock $inc leaves _versions stale ---
[11:36:55] INFO: Created product 69fb0bb628ecf760cde3fa29 with inventory=10 (published)
[11:36:55] INFO: Called payload.db.updateOne({ inventory: { $inc: -1 } })
[11:36:55] INFO: Main collection inventory:    9
[11:36:55] INFO: _versions latest inventory:   10
[11:36:55] INFO: find({ draft: true }) inventory: 10 (this is what the admin list view shows)
[11:36:55] ERROR: BUG REPRODUCED: main collection decremented to 9, but _versions still shows 10. The admin list view will show 10.

Which area(s) are affected?

plugin: ecommerce

Environment Info

```
Binaries:
Node: 24.3.0
npm: 11.4.2
Yarn: 1.22.22
pnpm: 10.33.0
Relevant Packages:
payload: 3.84.1
next: 16.2.3
@payloadcms/db-mongodb: 3.84.1
@payloadcms/graphql: 3.84.1
@payloadcms/next/utilities: 3.84.1
@payloadcms/richtext-lexical: 3.84.1
@payloadcms/translations: 3.84.1
@payloadcms/ui/shared: 3.84.1
react: 19.2.4
react-dom: 19.2.4
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 24.6.0
```

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions