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 12:01:09 +00:00
committed by GitHub
parent 6055f4c9cf
commit f7472a6fa6
14 changed files with 809 additions and 525 deletions
@@ -1,11 +1,13 @@
import { completeCartWorkflow } from "@medusajs/core-flows"
import { MedusaError } from "@medusajs/framework/utils"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { prepareRetrieveQuery } from "@medusajs/framework"
import { refetchOrder } from "../../../orders/helpers"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { HttpTypes } from "@medusajs/framework/types"
import {
ContainerRegistrationKeys,
MedusaError,
} from "@medusajs/framework/utils"
import { refetchCart } from "../../helpers"
import { defaultStoreCartFields } from "../../query-config"
import { HttpTypes } from "@medusajs/framework/types"
export const POST = async (
req: MedusaRequest,
@@ -19,6 +21,8 @@ export const POST = async (
throwOnError: false,
})
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
// When an error occurs on the workflow, its potentially got to with cart validations, payments
// or inventory checks. Return the cart here along with errors for the consumer to take more action
// and fix them
@@ -58,14 +62,14 @@ export const POST = async (
})
}
const order = await refetchOrder(
result.id,
req.scope,
req.remoteQueryConfig.fields
)
const { data } = await query.graph({
entity: "order",
fields: req.remoteQueryConfig.fields,
filters: { id: result.id },
})
res.status(200).json({
type: "order",
order,
order: data[0],
})
}
@@ -1,8 +1,16 @@
import {
onPaymentProcessedWorkflow,
processPaymentWorkflow,
} from "@medusajs/core-flows"
import {
IPaymentModuleService,
ProviderWebhookPayload,
} from "@medusajs/framework/types"
import { Modules, PaymentWebhookEvents } from "@medusajs/framework/utils"
import {
Modules,
PaymentActions,
PaymentWebhookEvents,
} from "@medusajs/framework/utils"
import { SubscriberArgs, SubscriberConfig } from "../types/subscribers"
type SerializedBuffer = {
@@ -27,7 +35,25 @@ export default async function paymentWebhookhandler({
(input.payload.rawData as unknown as SerializedBuffer).data
)
}
await paymentService.processEvent(input)
const processedEvent = await paymentService.getWebhookActionAndData(input)
if (processedEvent?.action === PaymentActions.NOT_SUPPORTED) {
return
}
if (!processedEvent.data) {
return
}
await processPaymentWorkflow(container).run({
input: processedEvent,
})
// We process the intended side effects of payment processing separately.
await onPaymentProcessedWorkflow(container).run({
input: processedEvent,
})
}
export const config: SubscriberConfig = {