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
2025-05-08 13:37:21 +02:00
committed by GitHub
parent 41f46b7723
commit 469ecef3c5
7 changed files with 103 additions and 83 deletions

View File

@@ -0,0 +1,6 @@
---
"@medusajs/medusa": patch
"@medusajs/core-flows": patch
---
chore: Minor revamp of cart completion

View File

@@ -1,11 +1,10 @@
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { IPaymentModuleService, Logger } from "@medusajs/framework/types"
import {
ContainerRegistrationKeys,
Modules,
PaymentSessionStatus,
} from "@medusajs/framework/utils"
import { Modules } from "@medusajs/framework/utils"
import { Logger } from "@medusajs/framework/types"
import { IPaymentModuleService } from "@medusajs/framework/types"
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
/**
* The payment session's details for compensation.

View File

@@ -24,6 +24,7 @@ import {
useQueryGraphStep,
useRemoteQueryStep,
} from "../../common"
import { addOrderTransactionStep } from "../../order/steps/add-order-transaction"
import { createOrdersStep } from "../../order/steps/create-orders"
import { authorizePaymentSessionStep } from "../../payment/steps/authorize-payment-session"
import { registerUsageStep } from "../../promotion/steps/register-usage"
@@ -32,6 +33,7 @@ import {
validateCartPaymentsStep,
validateShippingStep,
} from "../steps"
import { compensatePaymentIfNeededStep } from "../steps/compensate-payment-if-needed"
import { reserveInventoryStep } from "../steps/reserve-inventory"
import { completeCartFields } from "../utils/fields"
import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input"
@@ -41,7 +43,6 @@ import {
PrepareLineItemDataInput,
prepareTaxLinesData,
} from "../utils/prepare-line-item-data"
import { compensatePaymentIfNeededStep } from "../steps/compensate-payment-if-needed"
/**
* The data to complete a cart and place an order.
*/
@@ -88,7 +89,7 @@ export const completeCartWorkflow = createWorkflow(
{
name: completeCartWorkflowId,
store: true,
idempotent: true,
idempotent: false,
retentionTime: THREE_DAYS,
},
(input: WorkflowData<CompleteCartWorkflowInput>) => {
@@ -152,16 +153,6 @@ export const completeCartWorkflow = createWorkflow(
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 variantsMap: Record<string, any> = {}
const allItems = data.cart?.items?.map((item) => {
@@ -181,19 +172,7 @@ export const completeCartWorkflow = createWorkflow(
}
})
const cartToOrder = transform({ cart, payment }, ({ cart, payment }) => {
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 cartToOrder = transform({ cart }, ({ cart }) => {
const allItems = (cart.items ?? []).map((item) => {
const input: PrepareLineItemDataInput = {
item,
@@ -259,7 +238,6 @@ export const completeCartWorkflow = createWorkflow(
shipping_methods: shippingMethods,
metadata: cart.metadata,
promo_codes: promoCodes,
transactions,
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(
{ cart },
({ 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", {
order_id: createdOrder.id,

View File

@@ -5,12 +5,16 @@ import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
/**
* The transaction(s) to add to the order.
*/
export type AddOrderTransactionStepInput = CreateOrderTransactionDTO | CreateOrderTransactionDTO[]
export type AddOrderTransactionStepInput =
| CreateOrderTransactionDTO
| CreateOrderTransactionDTO[]
/**
* The added order transaction(s).
*/
export type AddOrderTransactionStepOutput = CreateOrderTransactionDTO | CreateOrderTransactionDTO[]
export type AddOrderTransactionStepOutput =
| CreateOrderTransactionDTO
| CreateOrderTransactionDTO[]
export const addOrderTransactionStepId = "add-order-transaction"
/**
@@ -18,14 +22,15 @@ export const addOrderTransactionStepId = "add-order-transaction"
*/
export const addOrderTransactionStep = createStep(
addOrderTransactionStepId,
async (
data: AddOrderTransactionStepInput,
{ container }
) => {
async (data: AddOrderTransactionStepInput, { container }) => {
const service = container.resolve(Modules.ORDER)
const trxsData = Array.isArray(data) ? data : [data]
if (!trxsData.length) {
return new StepResponse(null)
}
for (const trx of trxsData) {
const existing = await service.listOrderTransactions(
{
@@ -46,7 +51,9 @@ export const addOrderTransactionStep = createStep(
const created = await service.addOrderTransactions(trxsData)
return new StepResponse(
(Array.isArray(data) ? created : created[0]) as AddOrderTransactionStepOutput,
(Array.isArray(data)
? created
: created[0]) as AddOrderTransactionStepOutput,
created.map((c) => c.id)
)
},

View File

@@ -2,3 +2,4 @@ export * from "./capture-payment"
export * from "./process-payment"
export * from "./refund-payment"
export * from "./refund-payments"

View File

@@ -102,9 +102,7 @@ export const processPaymentWorkflow = createWorkflow(
}).then(() => {
completeCartWorkflow
.runAsStep({
input: {
id: cartPaymentCollection.data[0].cart_id,
},
input: { id: cartPaymentCollection.data[0].cart_id },
})
.config({
continueOnPermanentFailure: true, // Continue payment processing even if cart completion fails

View File

@@ -35,10 +35,13 @@ export default async function paymentWebhookhandler({
const processedEvent = await paymentService.getWebhookActionAndData(input)
if (!processedEvent.data) {
return
}
if (
processedEvent?.action === PaymentActions.NOT_SUPPORTED ||
// Currently none of these are handled by the processPaymentWorkflow, so we ignore them.
// Remove once the processPaymentWorkflow is handling them.
// We currently don't handle these payment statuses in the processPayment function.
processedEvent?.action === PaymentActions.CANCELED ||
processedEvent?.action === PaymentActions.FAILED ||
processedEvent?.action === PaymentActions.REQUIRES_MORE
@@ -46,13 +49,7 @@ export default async function paymentWebhookhandler({
return
}
if (!processedEvent.data) {
return
}
await processPaymentWorkflow(container).run({
input: processedEvent,
})
await processPaymentWorkflow(container).run({ input: processedEvent })
}
export const config: SubscriberConfig = {