fix: Idempotent cart completion (#9231)

What
- Store result of cart-completion workflow for three days by default
  - This enables the built-in idempotency mechanism to kick-in, provided the same transaction ID is used on workflow executions
- Return order from cart-completion workflow if the cart has already been completed
  - In case transaction ID is not used on workflow executions, we still only want to complete a cart once
This commit is contained in:
Oli Juhl
2024-10-04 14:01:09 +02:00
committed by GitHub
parent 6055f4c9cf
commit f7472a6fa6
14 changed files with 809 additions and 525 deletions

View File

@@ -20,7 +20,7 @@ import {
ProductStatus,
PromotionRuleOperator,
PromotionType,
RuleOperator,
RuleOperator
} from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import {
@@ -3085,6 +3085,67 @@ medusaIntegrationTestRunner({
})
})
it("should return order when cart is already completed", async () => {
const cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
email: "tony@stark-industries.com",
shipping_address: {
address_1: "test address 1",
address_2: "test address 2",
city: "ny",
country_code: "us",
province: "ny",
postal_code: "94016",
},
sales_channel_id: salesChannel.id,
items: [{ quantity: 1, variant_id: product.variants[0].id }],
},
storeHeaders
)
).data.cart
const paymentCollection = (
await api.post(
`/store/payment-collections`,
{
cart_id: cart.id,
},
storeHeaders
)
).data.payment_collection
await api.post(
`/store/payment-collections/${paymentCollection.id}/payment-sessions`,
{ provider_id: "pp_system_default" },
storeHeaders
)
await api.post(`/store/carts/${cart.id}/complete`, {}, storeHeaders)
const cartRefetch = (
await api.get(`/store/carts/${cart.id}`, storeHeaders)
).data.cart
expect(cartRefetch.completed_at).toBeTruthy()
const order = await api.post(
`/store/carts/${cart.id}/complete`,
{},
storeHeaders
)
expect(order.status).toEqual(200)
expect(order.data).toEqual({
type: "order",
order: expect.objectContaining({
id: expect.any(String),
}),
})
})
it("should return cart when payment authorization fails", async () => {
const paymentModuleService = appContainer.resolve(Modules.PAYMENT)
const authorizePaymentSessionSpy = jest.spyOn(