diff --git a/.changeset/beige-actors-tie.md b/.changeset/beige-actors-tie.md new file mode 100644 index 0000000000..9c6cf09861 --- /dev/null +++ b/.changeset/beige-actors-tie.md @@ -0,0 +1,6 @@ +--- +"@medusajs/medusa": patch +"@medusajs/core-flows": patch +--- + +chore: Minor revamp of cart completion diff --git a/packages/core/core-flows/src/cart/steps/compensate-payment-if-needed.ts b/packages/core/core-flows/src/cart/steps/compensate-payment-if-needed.ts index 1c5edc85c2..a6b90fc1e1 100644 --- a/packages/core/core-flows/src/cart/steps/compensate-payment-if-needed.ts +++ b/packages/core/core-flows/src/cart/steps/compensate-payment-if-needed.ts @@ -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. diff --git a/packages/core/core-flows/src/cart/workflows/complete-cart.ts b/packages/core/core-flows/src/cart/workflows/complete-cart.ts index bc1bc1beb6..ef74c77458 100644 --- a/packages/core/core-flows/src/cart/workflows/complete-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/complete-cart.ts @@ -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) => { @@ -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 = {} 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[] = [ - { - [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[] = [ + { + [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, diff --git a/packages/core/core-flows/src/order/steps/add-order-transaction.ts b/packages/core/core-flows/src/order/steps/add-order-transaction.ts index 550ad4f496..6856580c5c 100644 --- a/packages/core/core-flows/src/order/steps/add-order-transaction.ts +++ b/packages/core/core-flows/src/order/steps/add-order-transaction.ts @@ -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) ) }, diff --git a/packages/core/core-flows/src/payment/workflows/index.ts b/packages/core/core-flows/src/payment/workflows/index.ts index 8fe11981ce..0df7faa21b 100644 --- a/packages/core/core-flows/src/payment/workflows/index.ts +++ b/packages/core/core-flows/src/payment/workflows/index.ts @@ -2,3 +2,4 @@ export * from "./capture-payment" export * from "./process-payment" export * from "./refund-payment" export * from "./refund-payments" + diff --git a/packages/core/core-flows/src/payment/workflows/process-payment.ts b/packages/core/core-flows/src/payment/workflows/process-payment.ts index e357021eff..bf89a29160 100644 --- a/packages/core/core-flows/src/payment/workflows/process-payment.ts +++ b/packages/core/core-flows/src/payment/workflows/process-payment.ts @@ -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 diff --git a/packages/medusa/src/subscribers/payment-webhook.ts b/packages/medusa/src/subscribers/payment-webhook.ts index bd709f46d6..0b1cc8b149 100644 --- a/packages/medusa/src/subscribers/payment-webhook.ts +++ b/packages/medusa/src/subscribers/payment-webhook.ts @@ -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 = {