Skip to content

Commit 8c4228f

Browse files
authored
fix(link-modules,core-flows): Carry over cart promotions to order promotions (#12920)
what: - Carry over cart promotions to order promotions
1 parent 541e791 commit 8c4228f

16 files changed

+200
-203
lines changed

.changeset/odd-apricots-marry.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@medusajs/link-modules": patch
3+
"@medusajs/core-flows": patch
4+
---
5+
6+
fix(link-modules,core-flows): Carry over cart promotions to order promotions

integration-tests/http/__tests__/cart/store/cart.spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,60 @@ medusaIntegrationTestRunner({
12521252
)
12531253
})
12541254

1255+
it("should successfully complete cart with promotions", async () => {
1256+
const oldCart = (
1257+
await api.get(`/store/carts/${cart.id}`, storeHeaders)
1258+
).data.cart
1259+
1260+
createCartCreditLinesWorkflow.run({
1261+
input: [
1262+
{
1263+
cart_id: oldCart.id,
1264+
amount: oldCart.total,
1265+
currency_code: "usd",
1266+
reference: "test",
1267+
reference_id: "test",
1268+
},
1269+
],
1270+
container: appContainer,
1271+
})
1272+
1273+
const cartResponse = await api.post(
1274+
`/store/carts/${cart.id}/complete`,
1275+
{},
1276+
storeHeaders
1277+
)
1278+
1279+
const orderResponse = await api.get(
1280+
`/store/orders/${cartResponse.data.order.id}?fields=+promotions.*`,
1281+
storeHeaders
1282+
)
1283+
1284+
expect(cartResponse.status).toEqual(200)
1285+
expect(orderResponse.data.order).toEqual(
1286+
expect.objectContaining({
1287+
promotions: [
1288+
expect.objectContaining({
1289+
code: promotion.code,
1290+
}),
1291+
],
1292+
items: expect.arrayContaining([
1293+
expect.objectContaining({
1294+
unit_price: 1500,
1295+
compare_at_unit_price: null,
1296+
quantity: 1,
1297+
adjustments: expect.arrayContaining([
1298+
expect.objectContaining({
1299+
amount: 100,
1300+
code: promotion.code,
1301+
}),
1302+
]),
1303+
}),
1304+
]),
1305+
})
1306+
)
1307+
})
1308+
12551309
it("should successfully complete cart without shipping for digital products", async () => {
12561310
/**
12571311
* Product has a shipping profile so cart item should not require shipping

packages/core/core-flows/src/cart/utils/fields.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const cartFieldsForRefreshSteps = [
3636
"shipping_methods.tax_lines.*",
3737
"customer.*",
3838
"customer.groups.*",
39+
"promotions.id",
3940
"promotions.code",
4041
"payment_collection.id",
4142
"payment_collection.raw_amount",
@@ -105,6 +106,7 @@ export const completeCartFields = [
105106
"credit_lines.*",
106107
"payment_collection.*",
107108
"payment_collection.payment_sessions.*",
109+
"promotions.id",
108110
"items.variant.id",
109111
"items.variant.product.id",
110112
"items.variant.product.is_giftcard",

packages/core/core-flows/src/cart/workflows/complete-cart.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {
22
CartCreditLineDTO,
33
CartWorkflowDTO,
4+
LinkDefinition,
5+
PromotionDTO,
46
UsageComputedActions,
57
} from "@medusajs/framework/types"
68
import {
@@ -326,13 +328,22 @@ export const completeCartWorkflow = createWorkflow(
326328
const linksToCreate = transform(
327329
{ cart, createdOrder },
328330
({ cart, createdOrder }) => {
329-
const links: Record<string, any>[] = [
331+
const links: LinkDefinition[] = [
330332
{
331333
[Modules.ORDER]: { order_id: createdOrder.id },
332334
[Modules.CART]: { cart_id: cart.id },
333335
},
334336
]
335337

338+
if (cart.promotions?.length) {
339+
cart.promotions.forEach((promotion: PromotionDTO) => {
340+
links.push({
341+
[Modules.ORDER]: { order_id: createdOrder.id },
342+
[Modules.PROMOTION]: { promotion_id: promotion.id },
343+
})
344+
})
345+
}
346+
336347
if (isDefined(cart.payment_collection?.id)) {
337348
links.push({
338349
[Modules.ORDER]: { order_id: createdOrder.id },

packages/core/core-flows/src/draft-order/utils/fields.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,8 @@ export const draftOrderFieldsForRefreshSteps = [
2727
"shipping_methods.tax_lines.*",
2828
"customer.*",
2929
"customer.groups.*",
30-
"promotion_link.*",
31-
"promotion_link.promotion",
32-
"promotion_link.promotion.id",
33-
"promotion_link.promotion.code",
30+
"promotions.id",
31+
"promotions.code",
3432
"subtotal",
3533
"item_total",
3634
"total",

packages/core/core-flows/src/draft-order/workflows/add-draft-order-items.ts

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,38 +27,39 @@ export const addDraftOrderItemsWorkflowId = "add-draft-order-items"
2727
/**
2828
* This workflow adds items to a draft order. It's used by the
2929
* [Add Item to Draft Order Admin API Route](https://docs.medusajs.com/api/admin#draft-orders_postdraftordersidedititems).
30-
*
30+
*
3131
* You can use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around adding items to
3232
* a draft order.
33-
*
33+
*
3434
* @example
3535
* const { result } = await addDraftOrderItemsWorkflow(container)
3636
* .run({
3737
* input: {
3838
* order_id: "order_123",
39-
* items: [{
40-
* variant_id: "variant_123",
41-
* quantity: 1
39+
* items: [{
40+
* variant_id: "variant_123",
41+
* quantity: 1
4242
* }]
4343
* }
4444
* })
45-
*
45+
*
4646
* @summary
47-
*
47+
*
4848
* Add items to a draft order.
4949
*/
5050
export const addDraftOrderItemsWorkflow = createWorkflow(
5151
addDraftOrderItemsWorkflowId,
5252
function (
5353
input: WorkflowData<OrderWorkflow.OrderEditAddNewItemWorkflowInput>
5454
) {
55-
const order: OrderDTO = useRemoteQueryStep({
56-
entry_point: "orders",
57-
fields: draftOrderFieldsForRefreshSteps,
58-
variables: { id: input.order_id },
59-
list: false,
60-
throw_if_key_not_found: true,
61-
}).config({ name: "order-query" })
55+
const order: OrderDTO & { promotions: { code: string }[] } =
56+
useRemoteQueryStep({
57+
entry_point: "orders",
58+
fields: draftOrderFieldsForRefreshSteps,
59+
variables: { id: input.order_id },
60+
list: false,
61+
throw_if_key_not_found: true,
62+
}).config({ name: "order-query" })
6263

6364
const orderChange: OrderChangeDTO = useRemoteQueryStep({
6465
entry_point: "order_change",
@@ -92,19 +93,10 @@ export const addDraftOrderItemsWorkflow = createWorkflow(
9293
},
9394
})
9495

95-
const appliedPromoCodes: string[] = transform(order, (order) => {
96-
const promotionLink = (order as any).promotion_link
97-
98-
if (!promotionLink) {
99-
return []
100-
}
101-
102-
if (Array.isArray(promotionLink)) {
103-
return promotionLink.map((promo) => promo.promotion.code)
104-
}
105-
106-
return [promotionLink.promotion.code]
107-
})
96+
const appliedPromoCodes: string[] = transform(
97+
order,
98+
(order) => order.promotions?.map((promotion) => promotion.code) ?? []
99+
)
108100

109101
// If any the order has any promo codes, then we need to refresh the adjustments.
110102
when(

packages/core/core-flows/src/draft-order/workflows/add-draft-order-shipping-methods.ts

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ export interface AddDraftOrderShippingMethodsWorkflowInput {
4848
/**
4949
* This workflow adds shipping methods to a draft order. It's used by the
5050
* [Add Shipping Method to Draft Order Admin API Route](https://docs.medusajs.com/api/admin#draft-orders_postdraftordersideditshippingmethods).
51-
*
51+
*
5252
* You can use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around adding shipping methods to
5353
* a draft order.
54-
*
54+
*
5555
* @example
5656
* const { result } = await addDraftOrderShippingMethodsWorkflow(container)
5757
* .run({
@@ -61,15 +61,19 @@ export interface AddDraftOrderShippingMethodsWorkflowInput {
6161
* custom_amount: 10
6262
* }
6363
* })
64-
*
64+
*
6565
* @summary
66-
*
66+
*
6767
* Add shipping methods to a draft order.
6868
*/
6969
export const addDraftOrderShippingMethodsWorkflow = createWorkflow(
7070
addDraftOrderShippingMethodsWorkflowId,
7171
function (input: WorkflowData<AddDraftOrderShippingMethodsWorkflowInput>) {
72-
const order: OrderDTO = useRemoteQueryStep({
72+
const order: OrderDTO & {
73+
promotions: {
74+
code: string
75+
}[]
76+
} = useRemoteQueryStep({
7377
entry_point: "orders",
7478
fields: draftOrderFieldsForRefreshSteps,
7579
variables: { id: input.order_id },
@@ -133,19 +137,10 @@ export const addDraftOrderShippingMethodsWorkflow = createWorkflow(
133137
},
134138
})
135139

136-
const appliedPromoCodes = transform(order, (order) => {
137-
const promotionLink = (order as any).promotion_link
138-
139-
if (!promotionLink) {
140-
return []
141-
}
142-
143-
if (Array.isArray(promotionLink)) {
144-
return promotionLink.map((promo) => promo.promotion.code)
145-
}
146-
147-
return [promotionLink.promotion.code]
148-
})
140+
const appliedPromoCodes: string[] = transform(
141+
order,
142+
(order) => order.promotions?.map((promotion) => promotion.code) ?? []
143+
)
149144

150145
// If any the order has any promo codes, then we need to refresh the adjustments.
151146
when(

packages/core/core-flows/src/draft-order/workflows/cancel-draft-order-edit.ts

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,30 @@ export interface CancelDraftOrderEditWorkflowInput {
3333
/**
3434
* This workflow cancels a draft order edit. It's used by the
3535
* [Cancel Draft Order Edit Admin API Route](https://docs.medusajs.com/api/admin#draft-orders_deletedraftordersidedit).
36-
*
36+
*
3737
* You can use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around
3838
* cancelling a draft order edit.
39-
*
39+
*
4040
* @example
4141
* const { result } = await cancelDraftOrderEditWorkflow(container)
4242
* .run({
4343
* input: {
4444
* order_id: "order_123",
4545
* }
4646
* })
47-
*
47+
*
4848
* @summary
49-
*
49+
*
5050
* Cancel a draft order edit.
5151
*/
5252
export const cancelDraftOrderEditWorkflow = createWorkflow(
5353
cancelDraftOrderEditWorkflowId,
5454
function (input: WorkflowData<CancelDraftOrderEditWorkflowInput>) {
55-
const order: OrderDTO = useRemoteQueryStep({
55+
const order: OrderDTO & {
56+
promotions: {
57+
code: string
58+
}[]
59+
} = useRemoteQueryStep({
5660
entry_point: "orders",
5761
fields: ["version", ...draftOrderFieldsForRefreshSteps],
5862
variables: { id: input.order_id },
@@ -125,18 +129,12 @@ export const cancelDraftOrderEditWorkflow = createWorkflow(
125129
const promotionsToRefresh = transform(
126130
{ order, promotionsToRemove, promotionsToRestore },
127131
({ order, promotionsToRemove, promotionsToRestore }) => {
128-
const promotionLink = (order as any).promotion_link
132+
const orderPromotions = order.promotions
129133
const codes: Set<string> = new Set()
130134

131-
if (promotionLink) {
132-
if (Array.isArray(promotionLink)) {
133-
promotionLink.forEach((promo) => {
134-
codes.add(promo.promotion.code)
135-
})
136-
} else {
137-
codes.add(promotionLink.promotion.code)
138-
}
139-
}
135+
orderPromotions?.forEach((promo) => {
136+
codes.add(promo.code)
137+
})
140138

141139
for (const code of promotionsToRemove) {
142140
codes.delete(code)
@@ -163,9 +161,7 @@ export const cancelDraftOrderEditWorkflow = createWorkflow(
163161
},
164162
})
165163

166-
when(shippingToRestore, (methods) => {
167-
return !!methods?.length
168-
}).then(() => {
164+
when(shippingToRestore, (methods) => !!methods?.length).then(() => {
169165
restoreDraftOrderShippingMethodsStep({
170166
shippingMethods: shippingToRestore as any,
171167
})

packages/core/core-flows/src/draft-order/workflows/remove-draft-order-action-item.ts

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ export const removeDraftOrderActionItemWorkflowId =
2828
/**
2929
* This workflow removes an item that was added or updated in a draft order edit. It's used by the
3030
* [Remove Item from Draft Order Edit Admin API Route](https://docs.medusajs.com/api/admin#draft-orders_deletedraftordersidedititemsaction_id).
31-
*
31+
*
3232
* You can use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around
3333
* removing an item from a draft order edit.
34-
*
34+
*
3535
* @example
3636
* const { result } = await removeDraftOrderActionItemWorkflow(container)
3737
* .run({
@@ -40,17 +40,21 @@ export const removeDraftOrderActionItemWorkflowId =
4040
* action_id: "action_123",
4141
* }
4242
* })
43-
*
43+
*
4444
* @summary
45-
*
45+
*
4646
* Remove an item from a draft order edit.
4747
*/
4848
export const removeDraftOrderActionItemWorkflow = createWorkflow(
4949
removeDraftOrderActionItemWorkflowId,
5050
function (
5151
input: WorkflowData<OrderWorkflow.DeleteOrderEditItemActionWorkflowInput>
5252
): WorkflowResponse<OrderPreviewDTO> {
53-
const order: OrderDTO = useRemoteQueryStep({
53+
const order: OrderDTO & {
54+
promotions: {
55+
code: string
56+
}[]
57+
} = useRemoteQueryStep({
5458
entry_point: "orders",
5559
fields: ["id", "status", "is_draft_order", "canceled_at", "items.*"],
5660
variables: { id: input.order_id },
@@ -89,19 +93,8 @@ export const removeDraftOrderActionItemWorkflow = createWorkflow(
8993

9094
const appliedPromoCodes: string[] = transform(
9195
refetchedOrder,
92-
(refetchedOrder) => {
93-
const promotionLink = (refetchedOrder as any).promotion_link
94-
95-
if (!promotionLink) {
96-
return []
97-
}
98-
99-
if (Array.isArray(promotionLink)) {
100-
return promotionLink.map((promo) => promo.promotion.code)
101-
}
102-
103-
return [promotionLink.promotion.code]
104-
}
96+
(refetchedOrder) =>
97+
refetchedOrder.promotions?.map((promotion) => promotion.code) ?? []
10598
)
10699

107100
// If any the order has any promo codes, then we need to refresh the adjustments.

0 commit comments

Comments
 (0)