@medusajs/core-flows
Version:
Set of workflow definitions for Medusa
301 lines • 13.4 kB
JavaScript
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
;