fix(core-flows): prevent completing cart from webhook when order already exists (#14108)

* Prevent completing cart when order already exists in processPaymentWorkflow

* Add tests

* Add changeset
This commit is contained in:
Nicolas Gorga
2025-11-24 05:23:39 -03:00
committed by GitHub
parent d6a9e36bf0
commit 79582bc94e
3 changed files with 206 additions and 3 deletions

View File

@@ -1,11 +1,14 @@
import {
addToCartWorkflow,
beginOrderEditOrderWorkflow,
completeCartWorkflow,
confirmOrderEditRequestWorkflow,
createCartWorkflow,
createPaymentCollectionForCartWorkflow,
createPaymentSessionsWorkflow,
getOrderDetailWorkflow,
listShippingOptionsForCartWorkflow,
orderEditAddNewItemWorkflow,
processPaymentWorkflow,
} from "@medusajs/core-flows"
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
@@ -26,6 +29,7 @@ import {
import {
ContainerRegistrationKeys,
Modules,
PaymentCollectionStatus,
ProductStatus,
remoteQueryObjectFromString,
} from "@medusajs/utils"
@@ -1024,6 +1028,187 @@ medusaIntegrationTestRunner({
"Failed to do something before ending complete cart workflow"
)
})
it("should avoid completing cart when order already exists", async () => {
const salesChannel = await scModuleService.createSalesChannels({
name: "Webshop",
})
const location = await stockLocationModule.createStockLocations({
name: "Warehouse",
})
const [product] = await productModule.createProducts([
{
title: "Test product",
status: ProductStatus.PUBLISHED,
variants: [
{
title: "Test variant",
manage_inventory: false,
},
],
},
])
const priceSet = await pricingModule.createPriceSets({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
await pricingModule.createPricePreferences({
attribute: "currency_code",
value: "usd",
has_tax_inclusive: true,
})
await remoteLink.create([
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.PRICING]: {
price_set_id: priceSet.id,
},
},
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
])
// create cart
const cart = await cartModuleService.createCarts({
currency_code: "usd",
sales_channel_id: salesChannel.id,
})
await addToCartWorkflow(appContainer).run({
input: {
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
requires_shipping: false,
},
],
cart_id: cart.id,
},
})
await createPaymentCollectionForCartWorkflow(appContainer).run({
input: {
cart_id: cart.id,
},
})
const [payCol] = await remoteQuery(
remoteQueryObjectFromString({
entryPoint: "cart_payment_collection",
variables: { filters: { cart_id: cart.id } },
fields: ["payment_collection_id"],
})
)
await createPaymentSessionsWorkflow(appContainer).run({
input: {
payment_collection_id: payCol.payment_collection_id,
provider_id: "pp_system_default",
context: {},
data: {},
},
})
const {
result: { id: orderId },
} = await completeCartWorkflow(appContainer).run({
input: {
id: cart.id,
},
})
await beginOrderEditOrderWorkflow(appContainer).run({
input: {
order_id: orderId,
},
})
await orderEditAddNewItemWorkflow(appContainer).run({
input: {
order_id: orderId,
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
},
],
},
})
await confirmOrderEditRequestWorkflow(appContainer).run({
input: { order_id: orderId },
})
const orderPaymentCollections = await remoteQuery(
remoteQueryObjectFromString({
entryPoint: "order_payment_collection",
variables: { filters: { order_id: orderId } },
fields: ["payment_collection_id"],
})
)
const [pendingPaymentCollection] = await remoteQuery(
remoteQueryObjectFromString({
entryPoint: "payment_collection",
variables: {
filters: {
id: orderPaymentCollections.map(
(orderPayCol) => orderPayCol.payment_collection_id
),
status: PaymentCollectionStatus.NOT_PAID,
},
},
fields: ["id"],
})
)
const { result: paymentSession } =
await createPaymentSessionsWorkflow(appContainer).run({
input: {
payment_collection_id: pendingPaymentCollection.id,
provider_id: "pp_system_default",
context: {},
data: {},
},
})
let completeCartCalled = false
const workflow = processPaymentWorkflow(appContainer)
workflow.addAction("track-complete-cart-step", {
invoke: async function trackStep({ invoke }) {
completeCartCalled = !!invoke["complete-cart-after-payment-step"]
},
})
await workflow.run({
input: {
action: "captured",
data: {
session_id: paymentSession.id,
amount: 3000,
},
},
})
expect(completeCartCalled).toBe(false)
})
})
})
},