From bbf9b8703ee4f8ba5e090bf2d1a52a597ef0418e Mon Sep 17 00:00:00 2001 From: Tomasz Dworniczak Date: Wed, 26 Nov 2025 09:54:22 +0100 Subject: [PATCH 1/2] fix: remove redundant functions and checks, use extended fields array to fetch cart relations and pass enriched cart object to updateCartPromotionWorkflow, add default target rule with seller id for newly created promotions --- .../api/store/carts/[id]/promotions/route.ts | 32 ++++--------- .../src/api/store/carts/middlewares.ts | 13 ++++- .../b2c-core/src/modules/seller/utils.ts | 31 ------------ .../utils/cart-fields-for-promo-apply.ts | 47 +++++++++++++++++++ .../workflows/create-vendor-promotion.ts | 19 +++++++- ...pdate-cart-promotions-in-seller-context.ts | 27 +++++++++++ 6 files changed, 111 insertions(+), 58 deletions(-) create mode 100644 packages/modules/b2c-core/src/workflows/promotions/utils/cart-fields-for-promo-apply.ts create mode 100644 packages/modules/b2c-core/src/workflows/promotions/workflows/update-cart-promotions-in-seller-context.ts diff --git a/packages/modules/b2c-core/src/api/store/carts/[id]/promotions/route.ts b/packages/modules/b2c-core/src/api/store/carts/[id]/promotions/route.ts index 594e247c7..65e93eaa5 100644 --- a/packages/modules/b2c-core/src/api/store/carts/[id]/promotions/route.ts +++ b/packages/modules/b2c-core/src/api/store/carts/[id]/promotions/route.ts @@ -1,37 +1,21 @@ -import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework" -import { ContainerRegistrationKeys, MedusaError, PromotionActions } from "@medusajs/framework/utils" +import { MedusaRequest, MedusaResponse } from "@medusajs/framework" +import { ContainerRegistrationKeys, PromotionActions } from "@medusajs/framework/utils" import { StoreAddCartPromotionsType } from "@medusajs/medusa/api/store/carts/validators" -import { updateCartPromotionsWorkflow } from "@medusajs/medusa/core-flows" -import { fetchSellerByAuthActorId } from "@mercurjs/framework" -import { validateSellerPromotions } from "../../../../../modules/seller" +import { updateCartPromotionsInSellerContextWorkflow } from "../../../../../workflows/promotions/workflows/update-cart-promotions-in-seller-context" export const POST = async ( - req: AuthenticatedMedusaRequest, + req: MedusaRequest, res: MedusaResponse ) => { const query = req.scope.resolve(ContainerRegistrationKeys.QUERY) + const { promo_codes } = req.validatedBody as StoreAddCartPromotionsType; - const seller = await fetchSellerByAuthActorId( - req.auth_context?.actor_id, - req.scope - ) - - const validatePromotions = await validateSellerPromotions( - req.validatedBody.promo_codes, - req.scope, - seller.id - ) - - if (!validatePromotions.valid) { - throw new MedusaError(MedusaError.Types.INVALID_DATA, 'Some of the promotion codes are invalid') - } - - await updateCartPromotionsWorkflow.run({ + await updateCartPromotionsInSellerContextWorkflow(req.scope).run({ input: { cart_id: req.params.id, - promo_codes: req.validatedBody.promo_codes, + promo_codes: promo_codes, action: - req.validatedBody.promo_codes.length > 0 + promo_codes.length > 0 ? PromotionActions.ADD : PromotionActions.REPLACE, }, diff --git a/packages/modules/b2c-core/src/api/store/carts/middlewares.ts b/packages/modules/b2c-core/src/api/store/carts/middlewares.ts index 682415433..99b80a84c 100644 --- a/packages/modules/b2c-core/src/api/store/carts/middlewares.ts +++ b/packages/modules/b2c-core/src/api/store/carts/middlewares.ts @@ -4,7 +4,7 @@ import { validateAndTransformQuery } from '@medusajs/framework' import * as QueryConfig from '@medusajs/medusa/api/store/carts/query-config' -import { StoreGetCartsCart } from '@medusajs/medusa/api/store/carts/validators' +import { StoreAddCartPromotions, StoreGetCartsCart } from '@medusajs/medusa/api/store/carts/validators' import { StoreAddCartShippingMethods } from '@medusajs/medusa/api/store/carts/validators' import { StoreDeleteCartShippingMethods } from './validators' @@ -31,5 +31,16 @@ export const storeCartsMiddlewares: MiddlewareRoute[] = [ QueryConfig.retrieveTransformQueryConfig ) ] + }, + { + method: ['POST'], + matcher: '/store/carts/:id/promotions', + middlewares: [ + validateAndTransformBody(StoreAddCartPromotions), + validateAndTransformQuery( + StoreGetCartsCart, + QueryConfig.retrieveTransformQueryConfig + ) + ] } ] diff --git a/packages/modules/b2c-core/src/modules/seller/utils.ts b/packages/modules/b2c-core/src/modules/seller/utils.ts index 8488bae36..6b03f4e2f 100644 --- a/packages/modules/b2c-core/src/modules/seller/utils.ts +++ b/packages/modules/b2c-core/src/modules/seller/utils.ts @@ -126,34 +126,3 @@ export async function selectCustomersChartData( return result as unknown as { date: Date; count: string }[] } - -export async function validateSellerPromotions( - promo_codes: string[], - container: MedusaContainer, - seller_id: string -): Promise<{ valid: boolean; invalidCodes: string[] }> { - const knex = container.resolve(ContainerRegistrationKeys.PG_CONNECTION) - - const sellerPromotionCodes = await knex - .select('promotion.code') - .from('seller_seller_promotion_promotion') - .leftJoin( - 'promotion', - 'seller_seller_promotion_promotion.promotion_id', - 'promotion.id' - ) - .where('seller_seller_promotion_promotion.seller_id', seller_id) - .whereIn('promotion.code', promo_codes) - .whereNull('promotion.deleted_at') - - const foundCodes = sellerPromotionCodes.map((row) => row.code) - - const invalidCodes = promo_codes.filter( - (code) => !foundCodes.includes(code) - ) - - return { - valid: invalidCodes.length === 0, - invalidCodes - } -} diff --git a/packages/modules/b2c-core/src/workflows/promotions/utils/cart-fields-for-promo-apply.ts b/packages/modules/b2c-core/src/workflows/promotions/utils/cart-fields-for-promo-apply.ts new file mode 100644 index 000000000..d929949bc --- /dev/null +++ b/packages/modules/b2c-core/src/workflows/promotions/utils/cart-fields-for-promo-apply.ts @@ -0,0 +1,47 @@ +export const cartFieldsForPromoApply = [ + "id", + "email", + "currency_code", + "quantity", + "subtotal", + "item_total", + "total", + "item_subtotal", + "shipping_subtotal", + "region_id", + "metadata", + "completed_at", + "sales_channel_id", + "region.*", + "items.*", + "items.product.id", + "items.product.is_giftcard", + "items.product.collection_id", + "items.product.categories.id", + "items.product.tags.id", + "items.product.type_id", + "items.product.seller.id", + "items.variant.id", + "items.variant.product.id", + "items.variant.weight", + "items.variant.length", + "items.variant.height", + "items.variant.width", + "items.variant.material", + "items.adjustments.*", + "items.tax_lines.*", + "shipping_address.*", + "shipping_methods.*", + "shipping_methods.adjustments.*", + "shipping_methods.tax_lines.*", + "shipping_methods.shipping_option.shipping_option_type_id", + "customer.*", + "customer.groups.*", + "promotions.id", + "promotions.code", + "payment_collection.id", + "payment_collection.raw_amount", + "payment_collection.amount", + "payment_collection.currency_code", + "payment_collection.payment_sessions.id", + ] \ No newline at end of file diff --git a/packages/modules/b2c-core/src/workflows/promotions/workflows/create-vendor-promotion.ts b/packages/modules/b2c-core/src/workflows/promotions/workflows/create-vendor-promotion.ts index f6a8a5ca0..7333173e7 100644 --- a/packages/modules/b2c-core/src/workflows/promotions/workflows/create-vendor-promotion.ts +++ b/packages/modules/b2c-core/src/workflows/promotions/workflows/create-vendor-promotion.ts @@ -1,4 +1,4 @@ -import { CreatePromotionDTO, LinkDefinition } from "@medusajs/framework/types"; +import { CreatePromotionDTO, CreatePromotionRuleDTO, LinkDefinition } from "@medusajs/framework/types"; import { Modules } from "@medusajs/framework/utils"; import { createPromotionsWorkflow, @@ -30,9 +30,24 @@ export const createVendorPromotionWorkflow = createWorkflow( })) ); + const promotionWithVendorRule = transform(input, ({ promotion, seller_id }) => ({ + ...promotion, + application_method: { + ...promotion.application_method, + target_rules: [ + ...(promotion.application_method?.target_rules || []), + { + attribute: "items.product.seller.id", + operator: "eq", + values: [seller_id], + } as CreatePromotionRuleDTO, + ], + }, + })); + const promotions = createPromotionsWorkflow.runAsStep({ input: { - promotionsData: [input.promotion], + promotionsData: [promotionWithVendorRule], }, }); diff --git a/packages/modules/b2c-core/src/workflows/promotions/workflows/update-cart-promotions-in-seller-context.ts b/packages/modules/b2c-core/src/workflows/promotions/workflows/update-cart-promotions-in-seller-context.ts new file mode 100644 index 000000000..5a8a35047 --- /dev/null +++ b/packages/modules/b2c-core/src/workflows/promotions/workflows/update-cart-promotions-in-seller-context.ts @@ -0,0 +1,27 @@ +import { createWorkflow, WorkflowData, transform } from "@medusajs/framework/workflows-sdk" +import { UpdateCartPromotionsWorkflowInput, useQueryGraphStep, updateCartPromotionsWorkflow } from "@medusajs/medusa/core-flows" +import { cartFieldsForPromoApply } from "../utils/cart-fields-for-promo-apply" + +export const updateCartPromotionsInSellerContextWorkflow = createWorkflow( + { + name: "update-cart-promotions-in-seller-context", + idempotent: false, + }, + (input: WorkflowData) => { + const { data: fetchedCart } = useQueryGraphStep({ + entity: "cart", + fields: cartFieldsForPromoApply, + filters: { id: input.cart_id }, + options: { isList: false }, + }).config({ name: "fetch-cart-with-seller" }) + + const enrichedInput = transform({ input, fetchedCart }, ({ input, fetchedCart }) => ({ + ...input, + cart: fetchedCart, + })) + + return updateCartPromotionsWorkflow.runAsStep({ + input: enrichedInput, + }) + } + ) \ No newline at end of file From 487fad1da719c6c60530bf4bea4d39dcf20bb9eb Mon Sep 17 00:00:00 2001 From: Tomasz Dworniczak Date: Wed, 26 Nov 2025 10:49:05 +0100 Subject: [PATCH 2/2] fix: add type to req param in route, remove as type casting from validatedBody --- .../b2c-core/src/api/store/carts/[id]/promotions/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/b2c-core/src/api/store/carts/[id]/promotions/route.ts b/packages/modules/b2c-core/src/api/store/carts/[id]/promotions/route.ts index 65e93eaa5..234c06f00 100644 --- a/packages/modules/b2c-core/src/api/store/carts/[id]/promotions/route.ts +++ b/packages/modules/b2c-core/src/api/store/carts/[id]/promotions/route.ts @@ -4,11 +4,11 @@ import { StoreAddCartPromotionsType } from "@medusajs/medusa/api/store/carts/val import { updateCartPromotionsInSellerContextWorkflow } from "../../../../../workflows/promotions/workflows/update-cart-promotions-in-seller-context" export const POST = async ( - req: MedusaRequest, + req: MedusaRequest, res: MedusaResponse ) => { const query = req.scope.resolve(ContainerRegistrationKeys.QUERY) - const { promo_codes } = req.validatedBody as StoreAddCartPromotionsType; + const { promo_codes } = req.validatedBody; await updateCartPromotionsInSellerContextWorkflow(req.scope).run({ input: {