chore: Minor restructure of cart completion (#12353)

* chore: Minor revamp of cart completion

* wip

* wip

* temp. remove import

* continue on error

* chore: Use workflow

* fix import

* Create beige-actors-tie.md

* fix import
This commit is contained in:
Oli Juhl authored and GitHub committed 2025-05-08 13:37:21 +02:00
1 parent 41f46b7723
commit 469ecef3c5
7 files changed
+103 -83

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
---
"@medusajs/medusa": patch
"@medusajs/core-flows": patch
---
chore: Minor revamp of cart completion
@@ -1,11 +1,10 @@
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" import { IPaymentModuleService, Logger } from "@medusajs/framework/types"
import { import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
Modules,
PaymentSessionStatus, PaymentSessionStatus,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
import { Modules } from "@medusajs/framework/utils" import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { Logger } from "@medusajs/framework/types"
import { IPaymentModuleService } from "@medusajs/framework/types"
/** /**
* The payment session's details for compensation. * The payment session's details for compensation.
@@ -24,6 +24,7 @@ import {
useQueryGraphStep, useQueryGraphStep,
useRemoteQueryStep, useRemoteQueryStep,
} from "../../common" } from "../../common"
import { addOrderTransactionStep } from "../../order/steps/add-order-transaction"
import { createOrdersStep } from "../../order/steps/create-orders" import { createOrdersStep } from "../../order/steps/create-orders"
import { authorizePaymentSessionStep } from "../../payment/steps/authorize-payment-session" import { authorizePaymentSessionStep } from "../../payment/steps/authorize-payment-session"
import { registerUsageStep } from "../../promotion/steps/register-usage" import { registerUsageStep } from "../../promotion/steps/register-usage"
@@ -32,6 +33,7 @@ import {
validateCartPaymentsStep, validateCartPaymentsStep,
validateShippingStep, validateShippingStep,
} from "../steps" } from "../steps"
import { compensatePaymentIfNeededStep } from "../steps/compensate-payment-if-needed"
import { reserveInventoryStep } from "../steps/reserve-inventory" import { reserveInventoryStep } from "../steps/reserve-inventory"
import { completeCartFields } from "../utils/fields" import { completeCartFields } from "../utils/fields"
import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input" import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input"
@@ -41,7 +43,6 @@ import {
PrepareLineItemDataInput, PrepareLineItemDataInput,
prepareTaxLinesData, prepareTaxLinesData,
} from "../utils/prepare-line-item-data" } from "../utils/prepare-line-item-data"
import { compensatePaymentIfNeededStep } from "../steps/compensate-payment-if-needed"
/** /**
* The data to complete a cart and place an order. * The data to complete a cart and place an order.
*/ */
@@ -88,7 +89,7 @@ export const completeCartWorkflow = createWorkflow(
{ {
name: completeCartWorkflowId, name: completeCartWorkflowId,
store: true, store: true,
idempotent: true, idempotent: false,
retentionTime: THREE_DAYS, retentionTime: THREE_DAYS,
}, },
(input: WorkflowData<CompleteCartWorkflowInput>) => { (input: WorkflowData<CompleteCartWorkflowInput>) => {
@@ -152,16 +153,6 @@ export const completeCartWorkflow = createWorkflow(
validateShippingStep({ cart, shippingOptions }) validateShippingStep({ cart, shippingOptions })
createHook("beforePaymentAuthorization", {
input,
})
const payment = authorizePaymentSessionStep({
// We choose the first payment session, as there will only be one active payment session
// This might change in the future.
id: paymentSessions![0].id,
})
const { variants, sales_channel_id } = transform({ cart }, (data) => { const { variants, sales_channel_id } = transform({ cart }, (data) => {
const variantsMap: Record<string, any> = {} const variantsMap: Record<string, any> = {}
const allItems = data.cart?.items?.map((item) => { const allItems = data.cart?.items?.map((item) => {
@@ -181,19 +172,7 @@ export const completeCartWorkflow = createWorkflow(
} }
}) })
const cartToOrder = transform({ cart, payment }, ({ cart, payment }) => { const cartToOrder = transform({ cart }, ({ cart }) => {
const transactions =
(payment &&
payment?.captures?.map((capture) => {
return {
amount: capture.raw_amount ?? capture.amount,
currency_code: payment.currency_code,
reference: "capture",
reference_id: capture.id,
}
})) ??
[]
const allItems = (cart.items ?? []).map((item) => { const allItems = (cart.items ?? []).map((item) => {
const input: PrepareLineItemDataInput = { const input: PrepareLineItemDataInput = {
item, item,
@@ -259,7 +238,6 @@ export const completeCartWorkflow = createWorkflow(
shipping_methods: shippingMethods, shipping_methods: shippingMethods,
metadata: cart.metadata, metadata: cart.metadata,
promo_codes: promoCodes, promo_codes: promoCodes,
transactions,
credit_lines: creditLines, credit_lines: creditLines,
} }
}) })
@@ -298,39 +276,6 @@ export const completeCartWorkflow = createWorkflow(
} }
}) })
const linksToCreate = transform(
{ cart, createdOrder },
({ cart, createdOrder }) => {
const links: Record<string, any>[] = [
{
[Modules.ORDER]: { order_id: createdOrder.id },
[Modules.CART]: { cart_id: cart.id },
},
]
if (isDefined(cart.payment_collection?.id)) {
links.push({
[Modules.ORDER]: { order_id: createdOrder.id },
[Modules.PAYMENT]: {
payment_collection_id: cart.payment_collection.id,
},
})
}
return links
}
)
parallelize(
createRemoteLinkStep(linksToCreate),
updateCartsStep([updateCompletedAt]),
reserveInventoryStep(formatedInventoryItems),
emitEventStep({
eventName: OrderWorkflowEvents.PLACED,
data: { id: createdOrder.id },
})
)
const promotionUsage = transform( const promotionUsage = transform(
{ cart }, { cart },
({ cart }: { cart: CartWorkflowDTO }) => { ({ cart }: { cart: CartWorkflowDTO }) => {
@@ -362,7 +307,74 @@ export const completeCartWorkflow = createWorkflow(
} }
) )
registerUsageStep(promotionUsage) const linksToCreate = transform(
{ cart, createdOrder },
({ cart, createdOrder }) => {
const links: Record<string, any>[] = [
{
[Modules.ORDER]: { order_id: createdOrder.id },
[Modules.CART]: { cart_id: cart.id },
},
]
if (isDefined(cart.payment_collection?.id)) {
links.push({
[Modules.ORDER]: { order_id: createdOrder.id },
[Modules.PAYMENT]: {
payment_collection_id: cart.payment_collection.id,
},
})
}
return links
}
)
parallelize(
createRemoteLinkStep(linksToCreate),
updateCartsStep([updateCompletedAt]),
reserveInventoryStep(formatedInventoryItems),
registerUsageStep(promotionUsage),
emitEventStep({
eventName: OrderWorkflowEvents.PLACED,
data: { id: createdOrder.id },
})
)
createHook("beforePaymentAuthorization", {
input,
})
// We authorize payment sessions at the very end of the workflow to minimize the risk of
// canceling the payment in the compensation flow. The only operations that can trigger it
// is creating the transactions, the workflow hook, and the linking.
const payment = authorizePaymentSessionStep({
// We choose the first payment session, as there will only be one active payment session
// This might change in the future.
id: paymentSessions![0].id,
})
const orderTransactions = transform(
{ payment, createdOrder },
({ payment, createdOrder }) => {
const transactions =
(payment &&
payment?.captures?.map((capture) => {
return {
order_id: createdOrder.id,
amount: capture.raw_amount ?? capture.amount,
currency_code: payment.currency_code,
reference: "capture",
reference_id: capture.id,
}
})) ??
[]
return transactions
}
)
addOrderTransactionStep(orderTransactions)
createHook("orderCreated", { createHook("orderCreated", {
order_id: createdOrder.id, order_id: createdOrder.id,
@@ -5,12 +5,16 @@ import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
/** /**
* The transaction(s) to add to the order. * The transaction(s) to add to the order.
*/ */
export type AddOrderTransactionStepInput = CreateOrderTransactionDTO | CreateOrderTransactionDTO[] export type AddOrderTransactionStepInput =
| CreateOrderTransactionDTO
| CreateOrderTransactionDTO[]
/** /**
* The added order transaction(s). * The added order transaction(s).
*/ */
export type AddOrderTransactionStepOutput = CreateOrderTransactionDTO | CreateOrderTransactionDTO[] export type AddOrderTransactionStepOutput =
| CreateOrderTransactionDTO
| CreateOrderTransactionDTO[]
export const addOrderTransactionStepId = "add-order-transaction" export const addOrderTransactionStepId = "add-order-transaction"
/** /**
@@ -18,14 +22,15 @@ export const addOrderTransactionStepId = "add-order-transaction"
*/ */
export const addOrderTransactionStep = createStep( export const addOrderTransactionStep = createStep(
addOrderTransactionStepId, addOrderTransactionStepId,
async ( async (data: AddOrderTransactionStepInput, { container }) => {
data: AddOrderTransactionStepInput,
{ container }
) => {
const service = container.resolve(Modules.ORDER) const service = container.resolve(Modules.ORDER)
const trxsData = Array.isArray(data) ? data : [data] const trxsData = Array.isArray(data) ? data : [data]
if (!trxsData.length) {
return new StepResponse(null)
}
for (const trx of trxsData) { for (const trx of trxsData) {
const existing = await service.listOrderTransactions( const existing = await service.listOrderTransactions(
{ {
@@ -46,7 +51,9 @@ export const addOrderTransactionStep = createStep(
const created = await service.addOrderTransactions(trxsData) const created = await service.addOrderTransactions(trxsData)
return new StepResponse( return new StepResponse(
(Array.isArray(data) ? created : created[0]) as AddOrderTransactionStepOutput, (Array.isArray(data)
? created
: created[0]) as AddOrderTransactionStepOutput,
created.map((c) => c.id) created.map((c) => c.id)
) )
}, },
@@ -2,3 +2,4 @@ export * from "./capture-payment"
export * from "./process-payment" export * from "./process-payment"
export * from "./refund-payment" export * from "./refund-payment"
export * from "./refund-payments" export * from "./refund-payments"
@@ -102,9 +102,7 @@ export const processPaymentWorkflow = createWorkflow(
}).then(() => { }).then(() => {
completeCartWorkflow completeCartWorkflow
.runAsStep({ .runAsStep({
input: { input: { id: cartPaymentCollection.data[0].cart_id },
id: cartPaymentCollection.data[0].cart_id,
},
}) })
.config({ .config({
continueOnPermanentFailure: true, // Continue payment processing even if cart completion fails continueOnPermanentFailure: true, // Continue payment processing even if cart completion fails
@@ -35,10 +35,13 @@ export default async function paymentWebhookhandler({
const processedEvent = await paymentService.getWebhookActionAndData(input) const processedEvent = await paymentService.getWebhookActionAndData(input)
if (!processedEvent.data) {
return
}
if ( if (
processedEvent?.action === PaymentActions.NOT_SUPPORTED || processedEvent?.action === PaymentActions.NOT_SUPPORTED ||
// Currently none of these are handled by the processPaymentWorkflow, so we ignore them. // We currently don't handle these payment statuses in the processPayment function.
// Remove once the processPaymentWorkflow is handling them.
processedEvent?.action === PaymentActions.CANCELED || processedEvent?.action === PaymentActions.CANCELED ||
processedEvent?.action === PaymentActions.FAILED || processedEvent?.action === PaymentActions.FAILED ||
processedEvent?.action === PaymentActions.REQUIRES_MORE processedEvent?.action === PaymentActions.REQUIRES_MORE
@@ -46,13 +49,7 @@ export default async function paymentWebhookhandler({
return return
} }
if (!processedEvent.data) { await processPaymentWorkflow(container).run({ input: processedEvent })
return
}
await processPaymentWorkflow(container).run({
input: processedEvent,
})
} }
export const config: SubscriberConfig = { export const config: SubscriberConfig = {