UNPKG

@medusajs/core-flows

Version:

Set of workflow definitions for Medusa

301 lines • 13.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.completeCartWorkflow = exports.completeCartWorkflowId = void 0; const utils_1 = require("@medusajs/framework/utils"); const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk"); const common_1 = require("../../common"); const acquire_lock_1 = require("../../locking/acquire-lock"); const release_lock_1 = require("../../locking/release-lock"); const add_order_transaction_1 = require("../../order/steps/add-order-transaction"); const create_orders_1 = require("../../order/steps/create-orders"); const authorize_payment_session_1 = require("../../payment/steps/authorize-payment-session"); const register_usage_1 = require("../../promotion/steps/register-usage"); const steps_1 = require("../steps"); const compensate_payment_if_needed_1 = require("../steps/compensate-payment-if-needed"); const reserve_inventory_1 = require("../steps/reserve-inventory"); const fields_1 = require("../utils/fields"); const prepare_confirm_inventory_input_1 = require("../utils/prepare-confirm-inventory-input"); const prepare_line_item_data_1 = require("../utils/prepare-line-item-data"); const THREE_DAYS = 60 * 60 * 24 * 3; const THIRTY_SECONDS = 30; const TWO_MINUTES = 60 * 2; exports.completeCartWorkflowId = "complete-cart"; /** * This workflow completes a cart and places an order for the customer. It's executed by the * [Complete Cart Store API Route](https://docs.medusajs.com/api/store#carts_postcartsidcomplete). * * You can use this workflow within your own customizations or custom workflows, allowing you to wrap custom logic around completing a cart. * For example, in the [Subscriptions recipe](https://docs.medusajs.com/resources/recipes/subscriptions/examples/standard#create-workflow), * this workflow is used within another workflow that creates a subscription order. * * @example * const { result } = await completeCartWorkflow(container) * .run({ * input: { * id: "cart_123" * } * }) * * @summary * * Complete a cart and place an order. * * @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution. */ exports.completeCartWorkflow = (0, workflows_sdk_1.createWorkflow)({ name: exports.completeCartWorkflowId, store: true, idempotent: false, retentionTime: THREE_DAYS, }, (input) => { (0, acquire_lock_1.acquireLockStep)({ key: input.id, timeout: THIRTY_SECONDS, ttl: TWO_MINUTES, }); const orderCart = (0, common_1.useQueryGraphStep)({ entity: "order_cart", fields: ["cart_id", "order_id"], filters: { cart_id: input.id }, }); const orderId = (0, workflows_sdk_1.transform)({ orderCart }, ({ orderCart }) => { return orderCart.data[0]?.order_id; }); const cart = (0, common_1.useRemoteQueryStep)({ entry_point: "cart", fields: fields_1.completeCartFields, variables: { id: input.id }, list: false, }).config({ name: "cart-query", }); // this needs to be before the validation step const paymentSessions = (0, steps_1.validateCartPaymentsStep)({ cart }); // purpose of this step is to run compensation if cart completion fails // and tries to refund the payment if captured (0, compensate_payment_if_needed_1.compensatePaymentIfNeededStep)({ payment_session_id: paymentSessions[0].id, }); const validate = (0, workflows_sdk_1.createHook)("validate", { input, cart, }); // If order ID does not exist, we are completing the cart for the first time const order = (0, workflows_sdk_1.when)("create-order", { orderId }, ({ orderId }) => { return !orderId; }).then(() => { const cartOptionIds = (0, workflows_sdk_1.transform)({ cart }, ({ cart }) => { return cart.shipping_methods?.map((sm) => sm.shipping_option_id); }); const shippingOptions = (0, common_1.useRemoteQueryStep)({ entry_point: "shipping_option", fields: ["id", "shipping_profile_id"], variables: { id: cartOptionIds }, list: true, }).config({ name: "shipping-options-query", }); (0, steps_1.validateShippingStep)({ cart, shippingOptions }); const { variants, sales_channel_id } = (0, workflows_sdk_1.transform)({ cart }, (data) => { const variantsMap = {}; const allItems = data.cart?.items?.map((item) => { variantsMap[item.variant_id] = item.variant; return { id: item.id, variant_id: item.variant_id, quantity: item.quantity, }; }); return { variants: Object.values(variantsMap), items: allItems, sales_channel_id: data.cart.sales_channel_id, }; }); const cartToOrder = (0, workflows_sdk_1.transform)({ cart }, ({ cart }) => { const allItems = (cart.items ?? []).map((item) => { const input = { item, variant: item.variant, cartId: cart.id, unitPrice: item.unit_price, isTaxInclusive: item.is_tax_inclusive, taxLines: item.tax_lines ?? [], adjustments: item.adjustments ?? [], }; return (0, prepare_line_item_data_1.prepareLineItemData)(input); }); const shippingMethods = (cart.shipping_methods ?? []).map((sm) => { return { name: sm.name, description: sm.description, amount: sm.raw_amount ?? sm.amount, is_tax_inclusive: sm.is_tax_inclusive, shipping_option_id: sm.shipping_option_id, data: sm.data, metadata: sm.metadata, tax_lines: (0, prepare_line_item_data_1.prepareTaxLinesData)(sm.tax_lines ?? []), adjustments: (0, prepare_line_item_data_1.prepareAdjustmentsData)(sm.adjustments ?? []), }; }); const creditLines = (cart.credit_lines ?? []).map((creditLine) => { return { amount: creditLine.amount, raw_amount: creditLine.raw_amount, reference: creditLine.reference, reference_id: creditLine.reference_id, metadata: creditLine.metadata, }; }); const itemAdjustments = allItems .map((item) => item.adjustments ?? []) .flat(1); const shippingAdjustments = shippingMethods .map((sm) => sm.adjustments ?? []) .flat(1); const promoCodes = [...itemAdjustments, ...shippingAdjustments] .map((adjustment) => adjustment.code) .filter(Boolean); const shippingAddress = cart.shipping_address ? { ...cart.shipping_address } : null; const billingAddress = cart.billing_address ? { ...cart.billing_address } : null; if (shippingAddress) { delete shippingAddress.id; } if (billingAddress) { delete billingAddress.id; } return { region_id: cart.region?.id, customer_id: cart.customer?.id, sales_channel_id: cart.sales_channel_id, status: utils_1.OrderStatus.PENDING, email: cart.email, currency_code: cart.currency_code, shipping_address: shippingAddress, billing_address: billingAddress, no_notification: false, items: allItems, shipping_methods: shippingMethods, metadata: cart.metadata, promo_codes: promoCodes, credit_lines: creditLines, }; }); const createdOrders = (0, create_orders_1.createOrdersStep)([cartToOrder]); const createdOrder = (0, workflows_sdk_1.transform)({ createdOrders }, ({ createdOrders }) => { return createdOrders?.[0] ?? undefined; }); const reservationItemsData = (0, workflows_sdk_1.transform)({ createdOrder }, ({ createdOrder }) => createdOrder.items.map((i) => ({ variant_id: i.variant_id, quantity: i.quantity, id: i.id, }))); const formatedInventoryItems = (0, workflows_sdk_1.transform)({ input: { sales_channel_id, variants, items: reservationItemsData, }, }, prepare_confirm_inventory_input_1.prepareConfirmInventoryInput); const updateCompletedAt = (0, workflows_sdk_1.transform)({ cart }, ({ cart }) => { return { id: cart.id, completed_at: new Date(), }; }); const promotionUsage = (0, workflows_sdk_1.transform)({ cart }, ({ cart }) => { const promotionUsage = []; const itemAdjustments = (cart.items ?? []) .map((item) => item.adjustments ?? []) .flat(1); const shippingAdjustments = (cart.shipping_methods ?? []) .map((item) => item.adjustments ?? []) .flat(1); for (const adjustment of itemAdjustments) { promotionUsage.push({ amount: adjustment.amount, code: adjustment.code, }); } for (const adjustment of shippingAdjustments) { promotionUsage.push({ amount: adjustment.amount, code: adjustment.code, }); } return promotionUsage; }); const linksToCreate = (0, workflows_sdk_1.transform)({ cart, createdOrder }, ({ cart, createdOrder }) => { const links = [ { [utils_1.Modules.ORDER]: { order_id: createdOrder.id }, [utils_1.Modules.CART]: { cart_id: cart.id }, }, ]; if ((0, utils_1.isDefined)(cart.payment_collection?.id)) { links.push({ [utils_1.Modules.ORDER]: { order_id: createdOrder.id }, [utils_1.Modules.PAYMENT]: { payment_collection_id: cart.payment_collection.id, }, }); } return links; }); (0, workflows_sdk_1.parallelize)((0, common_1.createRemoteLinkStep)(linksToCreate), (0, steps_1.updateCartsStep)([updateCompletedAt]), (0, reserve_inventory_1.reserveInventoryStep)(formatedInventoryItems), (0, register_usage_1.registerUsageStep)(promotionUsage), (0, common_1.emitEventStep)({ eventName: utils_1.OrderWorkflowEvents.PLACED, data: { id: createdOrder.id }, })); /** * @ignore */ (0, workflows_sdk_1.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 = (0, authorize_payment_session_1.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 = (0, workflows_sdk_1.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; }); (0, add_order_transaction_1.addOrderTransactionStep)(orderTransactions); /** * @ignore */ (0, workflows_sdk_1.createHook)("orderCreated", { order_id: createdOrder.id, cart_id: cart.id, }); return createdOrder; }); (0, release_lock_1.releaseLockStep)({ key: input.id, }); const result = (0, workflows_sdk_1.transform)({ order, orderId }, ({ order, orderId }) => { return { id: order?.id ?? orderId }; }); return new workflows_sdk_1.WorkflowResponse(result, { hooks: [validate], }); }); //# sourceMappingURL=complete-cart.js.map