@reactionary/source 0.3.16 → 0.3.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +2 -2
  2. package/core/src/initialization.ts +2 -2
  3. package/core/src/providers/price.provider.ts +2 -1
  4. package/core/src/schemas/models/cart.model.ts +7 -2
  5. package/core/src/schemas/models/identifiers.model.ts +12 -8
  6. package/core/src/schemas/models/price.model.ts +12 -0
  7. package/examples/node/package.json +7 -7
  8. package/examples/node/src/capabilities/cart.spec.ts +97 -15
  9. package/examples/node/src/capabilities/category.spec.ts +27 -32
  10. package/examples/node/src/capabilities/checkout.spec.ts +5 -5
  11. package/examples/node/src/capabilities/identity.spec.ts +6 -2
  12. package/examples/node/src/capabilities/inventory.spec.ts +1 -1
  13. package/examples/node/src/capabilities/price.spec.ts +7 -7
  14. package/examples/node/src/utils.ts +4 -1
  15. package/package.json +3 -3
  16. package/providers/algolia/src/providers/product-search.provider.ts +19 -14
  17. package/providers/commercetools/src/core/client.ts +112 -9
  18. package/providers/commercetools/src/core/token-cache.ts +4 -5
  19. package/providers/commercetools/src/providers/cart.provider.ts +76 -11
  20. package/providers/commercetools/src/providers/inventory.provider.ts +5 -7
  21. package/providers/commercetools/src/providers/price.provider.ts +17 -30
  22. package/providers/commercetools/src/schema/configuration.schema.ts +4 -0
  23. package/providers/commercetools/src/schema/session.schema.ts +3 -1
  24. package/providers/fake/src/providers/cart.provider.ts +1 -0
  25. package/providers/fake/src/providers/price.provider.ts +54 -95
  26. package/providers/medusa/src/providers/cart.provider.ts +159 -70
  27. package/providers/medusa/src/providers/category.provider.ts +35 -23
  28. package/providers/medusa/src/providers/checkout.provider.ts +78 -41
  29. package/providers/medusa/src/providers/order-search.provider.ts +21 -10
  30. package/providers/medusa/src/providers/price.provider.ts +18 -9
  31. package/providers/medusa/src/providers/product-recommendations.provider.ts +10 -6
  32. package/providers/medusa/src/providers/product-search.provider.ts +19 -10
  33. package/providers/medusa/src/providers/product.provider.ts +20 -12
  34. package/providers/medusa/src/providers/profile.provider.ts +38 -13
  35. package/providers/meilisearch/src/providers/order-search.provider.ts +17 -12
  36. package/providers/meilisearch/src/providers/product-recommendations.provider.ts +10 -11
  37. package/providers/meilisearch/src/providers/product-search.provider.ts +23 -18
@@ -16,10 +16,11 @@ import {
16
16
  import type * as z from 'zod';
17
17
  import type { FakeConfiguration } from '../schema/configuration.schema.js';
18
18
  import { base, en, Faker } from '@faker-js/faker';
19
+ import { calcSeed } from '../utilities/seed.js';
19
20
 
20
21
  export class FakePriceProvider extends PriceProvider {
21
22
  protected config: FakeConfiguration;
22
-
23
+ protected faker: Faker;
23
24
  constructor(
24
25
  config: FakeConfiguration,
25
26
  cache: Cache,
@@ -28,66 +29,72 @@ export class FakePriceProvider extends PriceProvider {
28
29
  super(cache, context);
29
30
 
30
31
  this.config = config;
32
+ this.faker = new Faker({
33
+ locale: [en, base],
34
+ });
31
35
  }
32
36
 
33
- @Reactionary({
34
- inputSchema: ListPriceQuerySchema,
35
- outputSchema: PriceSchema
36
- })
37
- public override async getListPrice(payload: ListPriceQuery): Promise<Result<Price>> {
38
- if (payload.variant.sku === 'unknown-sku') {
39
- return success(this.createEmptyPriceResult(payload.variant.sku));
37
+ protected createPrice(variantSku: string, mode: 'list' | 'customer'): Price {
38
+ const seed = calcSeed(variantSku);
39
+ this.faker.seed(seed);
40
+ let price = this.faker.number.int({ min: 300, max: 100000 }) / 100;
41
+ let onSale = false;
42
+ if (mode === 'customer') {
43
+ // For customer price, randomly decide if the product is on sale
44
+ onSale = this.faker.datatype.boolean({ probability: 0.1 }); // 10% chance of being on sale
45
+
46
+ if (onSale) {
47
+ price = price * this.faker.number.float({ min: 0.5, max: 0.9 }); // Apply a random discount between 10% and 50%
48
+ }
40
49
  }
41
50
 
42
- // Generate a simple hash from the SKU key string for seeding
43
- let hash = 0;
44
- const skuString = payload.variant.sku;
45
- for (let i = 0; i < skuString.length; i++) {
46
- hash = (hash << 5) - hash + skuString.charCodeAt(i);
47
- hash = hash & hash; // Convert to 32bit integer
51
+ const tiers = [];
52
+ if (variantSku.includes('with-tiers')) {
53
+ // Add tiered pricing for SKUs that include "with-tiers"
54
+ tiers.push({
55
+ minimumQuantity: this.faker.number.int({ min: 2, max: 5 }),
56
+ price: {
57
+ value: price * 0.8, // 20% discount for tier 1
58
+ currency: this.context.languageContext.currencyCode,
59
+ },
60
+ });
61
+ tiers.push({
62
+ minimumQuantity: this.faker.number.int({ min: 6, max: 10 }),
63
+ price: {
64
+ value: price * 0.6, // 40% discount for tier 2
65
+ currency: this.context.languageContext.currencyCode,
66
+ },
67
+ });
48
68
  }
49
69
 
50
- const generator = new Faker({
51
- seed: hash || 42,
52
- locale: [en, base],
53
- });
54
70
 
55
- const model = {
71
+ return {
56
72
  identifier: {
57
- variant: payload.variant,
73
+ variant: {
74
+ sku: variantSku,
75
+ },
58
76
  },
59
77
  unitPrice: {
60
- value: generator.number.int({ min: 300, max: 100000 }) / 100,
78
+ value: price,
61
79
  currency: this.context.languageContext.currencyCode,
62
80
  },
63
- } as Price;
81
+ onSale,
82
+ tieredPrices: tiers,
83
+ }
84
+ }
64
85
 
65
- if (skuString.includes('with-tiers')) {
66
- const unitPrice = model.unitPrice?.value || 0;
67
- // Ensure tiered prices are less than the unit price
68
- const tier1Price = unitPrice * 0.8;
69
- const tier2Price = tier1Price * 0.8;
70
- model.tieredPrices = [
71
- {
72
- minimumQuantity: generator.number.int({ min: 2, max: 5 }),
73
- price: {
74
- value: tier1Price,
75
- currency: this.context.languageContext.currencyCode,
76
- },
77
- },
78
- {
79
- minimumQuantity: generator.number.int({ min: 6, max: 10 }),
80
- price: {
81
- value: tier2Price,
82
- currency: this.context.languageContext.currencyCode,
83
- },
84
- },
85
- ];
86
- } else {
87
- model.tieredPrices = [];
86
+
87
+
88
+ @Reactionary({
89
+ inputSchema: ListPriceQuerySchema,
90
+ outputSchema: PriceSchema
91
+ })
92
+ public override async getListPrice(payload: ListPriceQuery): Promise<Result<Price>> {
93
+ if (payload.variant.sku === 'unknown-sku') {
94
+ return success(this.createEmptyPriceResult(payload.variant.sku));
88
95
  }
89
96
 
90
- return success(model);
97
+ return success(this.createPrice(payload.variant.sku, 'list'));
91
98
  }
92
99
 
93
100
  @Reactionary({
@@ -99,54 +106,6 @@ export class FakePriceProvider extends PriceProvider {
99
106
  return success(this.createEmptyPriceResult(payload.variant.sku));
100
107
  }
101
108
 
102
- // Generate a simple hash from the SKU key string for seeding
103
- let hash = 0;
104
- const skuString = payload.variant.sku;
105
- for (let i = 0; i < skuString.length; i++) {
106
- hash = (hash << 5) - hash + skuString.charCodeAt(i);
107
- hash = hash & hash; // Convert to 32bit integer
108
- }
109
-
110
- const generator = new Faker({
111
- seed: hash || 42,
112
- locale: [en, base],
113
- });
114
-
115
- const model = {
116
- identifier: {
117
- variant: payload.variant,
118
- },
119
- unitPrice: {
120
- value: generator.number.int({ min: 300, max: 100000 }) / 100,
121
- currency: this.context.languageContext.currencyCode,
122
- },
123
- } as Price;
124
-
125
- if (skuString.includes('with-tiers')) {
126
- const unitPrice = model.unitPrice?.value || 0;
127
- // Ensure tiered prices are less than the unit price
128
- const tier1Price = unitPrice * 0.8;
129
- const tier2Price = tier1Price * 0.8;
130
- model.tieredPrices = [
131
- {
132
- minimumQuantity: generator.number.int({ min: 2, max: 5 }),
133
- price: {
134
- value: tier1Price,
135
- currency: this.context.languageContext.currencyCode,
136
- },
137
- },
138
- {
139
- minimumQuantity: generator.number.int({ min: 6, max: 10 }),
140
- price: {
141
- value: tier2Price,
142
- currency: this.context.languageContext.currencyCode,
143
- },
144
- },
145
- ];
146
- } else {
147
- model.tieredPrices = [];
148
- }
149
-
150
- return success(model);
109
+ return success(this.createPrice(payload.variant.sku, 'customer'));
151
110
  }
152
111
  }
@@ -16,6 +16,7 @@ import type {
16
16
  ItemCostBreakdown,
17
17
  NotFoundError,
18
18
  ProductVariantIdentifier,
19
+ Promotion,
19
20
  RequestContext,
20
21
  Result,
21
22
  } from '@reactionary/core';
@@ -31,10 +32,10 @@ import {
31
32
  CartProvider,
32
33
  CartQueryByIdSchema,
33
34
  CartSchema,
35
+ error,
34
36
  ProductVariantIdentifierSchema,
35
37
  Reactionary,
36
38
  success,
37
- error,
38
39
  } from '@reactionary/core';
39
40
 
40
41
  import createDebug from 'debug';
@@ -47,8 +48,7 @@ import {
47
48
  parseMedusaCostBreakdown,
48
49
  parseMedusaItemPrice,
49
50
  } from '../utils/medusa-helpers.js';
50
- import type MedusaTypes = require('@medusajs/types');
51
- import type StoreCartPromotion = require('@medusajs/types');
51
+ import type { StoreAddCartLineItem, StoreCart, StoreCartAddPromotion, StoreCartLineItem, StoreCartRemovePromotion, StoreCartResponse, StoreCreateCart, StoreProduct, StoreUpdateCart, StoreUpdateCartLineItem } from '@medusajs/types';
52
52
 
53
53
  const debug = createDebug('reactionary:medusa:cart');
54
54
 
@@ -87,7 +87,7 @@ export class MedusaCartProvider extends CartProvider {
87
87
  debug('Fetching cart by ID:', medusaId.key);
88
88
  }
89
89
 
90
- const cartResponse = await client.store.cart.retrieve(medusaId.key);
90
+ const cartResponse = await client.store.cart.retrieve(medusaId.key, { fields: this.includedFields });
91
91
 
92
92
  if (debug.enabled) {
93
93
  debug('Received cart response:', cartResponse);
@@ -111,6 +111,21 @@ export class MedusaCartProvider extends CartProvider {
111
111
  }
112
112
  }
113
113
 
114
+
115
+ /**
116
+ * Extension point for the `add` operation to control the payload sent to Medusa when adding an item to the cart. By default, it only includes the variant ID and quantity, but you can override it to include more fields as needed.
117
+ *
118
+ * @param payload
119
+ * @param variantId
120
+ * @returns
121
+ */
122
+ protected addPayload(payload: CartMutationItemAdd, variantId: string): StoreAddCartLineItem {
123
+ return {
124
+ variant_id: variantId,
125
+ quantity: payload.quantity,
126
+ };
127
+ }
128
+
114
129
  @Reactionary({
115
130
  inputSchema: CartMutationItemAddSchema,
116
131
  outputSchema: CartSchema,
@@ -148,13 +163,11 @@ export class MedusaCartProvider extends CartProvider {
148
163
 
149
164
  const response = await client.store.cart.createLineItem(
150
165
  medusaId.key,
151
- {
152
- variant_id: variantId,
153
- quantity: payload.quantity,
154
- },
166
+ this.addPayload(payload, variantId),
155
167
  {
156
168
  fields: this.includedFields,
157
169
  }
170
+
158
171
  );
159
172
 
160
173
  if (debug.enabled) {
@@ -200,6 +213,18 @@ export class MedusaCartProvider extends CartProvider {
200
213
  }
201
214
  }
202
215
 
216
+ /**
217
+ * Extension point for the `changeQuantity` operation to control the payload sent to Medusa when changing the quantity of an item in the cart. By default, it only includes the new quantity, but you can override it to include more fields as needed.
218
+ * @param payload
219
+ * @returns
220
+ */
221
+ protected changeQuantityPayload(payload: CartMutationItemQuantityChange): StoreUpdateCartLineItem {
222
+ return {
223
+ quantity: payload.quantity,
224
+ };
225
+ }
226
+
227
+
203
228
  @Reactionary({
204
229
  inputSchema: CartMutationItemQuantityChangeSchema,
205
230
  outputSchema: CartSchema,
@@ -222,9 +247,7 @@ export class MedusaCartProvider extends CartProvider {
222
247
  const response = await client.store.cart.updateLineItem(
223
248
  medusaId.key,
224
249
  payload.item.key,
225
- {
226
- quantity: payload.quantity,
227
- },
250
+ this.changeQuantityPayload(payload),
228
251
  {
229
252
  fields: this.includedFields,
230
253
  }
@@ -272,7 +295,7 @@ export class MedusaCartProvider extends CartProvider {
272
295
  identifier: undefined,
273
296
  });
274
297
  } catch (err) {
275
- debug('Failed to get active cart ID:', error);
298
+ debug('Failed to get active cart ID:', err);
276
299
 
277
300
  return error<NotFoundError>({
278
301
  type: 'NotFound',
@@ -282,8 +305,8 @@ export class MedusaCartProvider extends CartProvider {
282
305
  }
283
306
 
284
307
  @Reactionary({
308
+ cache: false,
285
309
  inputSchema: CartMutationDeleteCartSchema,
286
- outputSchema: CartSchema,
287
310
  })
288
311
  public override async deleteCart(
289
312
  payload: CartMutationDeleteCart
@@ -319,6 +342,18 @@ export class MedusaCartProvider extends CartProvider {
319
342
  }
320
343
  }
321
344
 
345
+
346
+ /**
347
+ * Extension point to apply a coupon code to the cart. By default, it only includes the coupon code, but you can override it to include more fields as needed.
348
+ * @param payload
349
+ * @returns
350
+ */
351
+ protected applyCouponCodePayload(payload: CartMutationApplyCoupon): StoreCartAddPromotion {
352
+ return {
353
+ promo_codes: [payload.couponCode],
354
+ };
355
+ }
356
+
322
357
  @Reactionary({
323
358
  inputSchema: CartMutationApplyCouponSchema,
324
359
  outputSchema: CartSchema,
@@ -330,16 +365,27 @@ export class MedusaCartProvider extends CartProvider {
330
365
  const client = await this.getClient();
331
366
  const medusaId = payload.cart as MedusaCartIdentifier;
332
367
 
333
- const response = await client.store.cart.update(
334
- medusaId.key,
368
+
369
+ const response = await client.client.fetch<StoreCartResponse>(
370
+ `/store/carts/${medusaId.key}/promotions`,
335
371
  {
336
- promo_codes: [payload.couponCode],
337
- },
372
+ method: "POST",
373
+ body: this.applyCouponCodePayload(payload),
374
+ query: {
375
+ fields: this.includedFields,
376
+ }
377
+ }
378
+ );
379
+
380
+ /** When PR: https://github.com/medusajs/medusa/pull/14850 gets merged, revert to the below
381
+ const response = await client.store.cart.addPromotionCodes(
382
+ medusaId.key,
383
+ this.applyCouponCodePayload(payload),
338
384
  {
339
385
  fields: this.includedFields,
340
386
  }
341
387
  );
342
-
388
+ */
343
389
  if (response.cart) {
344
390
  return success(this.parseSingle(response.cart));
345
391
  }
@@ -350,6 +396,18 @@ export class MedusaCartProvider extends CartProvider {
350
396
  }
351
397
  }
352
398
 
399
+ /**
400
+ * Extension point to remove a coupon code from the cart. By default, it only includes the coupon code to be removed, but you can override it to include more fields as needed.
401
+ * @param payload
402
+ * @returns
403
+ */
404
+ protected removeCouponCodePayload(payload: CartMutationRemoveCoupon): StoreCartRemovePromotion {
405
+ return {
406
+ promo_codes: [payload.couponCode],
407
+
408
+ };
409
+ }
410
+
353
411
  @Reactionary({
354
412
  inputSchema: CartMutationRemoveCouponSchema,
355
413
  outputSchema: CartSchema,
@@ -361,39 +419,49 @@ export class MedusaCartProvider extends CartProvider {
361
419
  const client = await this.getClient();
362
420
  const medusaId = payload.cart as MedusaCartIdentifier;
363
421
 
364
- // Get current cart to find the discount to remove
365
- const cartResponse = await client.store.cart.retrieve(medusaId.key);
366
-
367
- if (cartResponse.cart?.promotions) {
368
- const manualDiscounts = cartResponse.cart.promotions.filter(
369
- (x) => !x.is_automatic && x.code
370
- );
371
-
372
- const remainingCodes = (manualDiscounts
373
- .filter((x) => x.code !== payload.couponCode)
374
- .map((promotion: MedusaTypes.StoreCartPromotion) => promotion.code) ||
375
- []) as string[];
376
- const response = await client.store.cart.update(
377
- medusaId.key,
378
- {
379
- promo_codes: remainingCodes || [],
380
- },
381
- {
422
+ const response = await client.client.fetch<StoreCartResponse>(
423
+ `/store/carts/${medusaId.key}/promotions`,
424
+ {
425
+ method: "DELETE",
426
+ body: this.removeCouponCodePayload(payload),
427
+ query: {
382
428
  fields: this.includedFields,
383
429
  }
384
- );
430
+ }
431
+ );
432
+
433
+ /*
385
434
 
386
- if (response.cart) {
387
- return success(this.parseSingle(response.cart));
435
+ const response = await client.store.cart.removePromotionCodes(
436
+ medusaId.key,
437
+ this.removeCouponCodePayload(payload),
438
+ {
439
+ fields: this.includedFields,
388
440
  }
389
- }
441
+ );
442
+ */
390
443
 
444
+ if (response.cart) {
445
+ return success(this.parseSingle(response.cart));
446
+ }
391
447
  throw new Error('Failed to remove coupon code');
392
448
  } catch (error) {
393
449
  handleProviderError('remove coupon code', error);
394
450
  }
395
451
  }
396
452
 
453
+ /**
454
+ * Extension point to control the payload sent to Medusa when changing the currency of the cart. By default, it only includes the new region ID, but you can override it to include more fields as needed.
455
+ * @param payload
456
+ * @param newRegionId
457
+ * @returns
458
+ */
459
+ protected changeCurrencyPayload(payload: CartMutationChangeCurrency, newRegionId: string): StoreUpdateCart {
460
+ return {
461
+ region_id: newRegionId,
462
+ };
463
+ }
464
+
397
465
  @Reactionary({
398
466
  inputSchema: CartMutationChangeCurrencySchema,
399
467
  outputSchema: CartSchema,
@@ -404,35 +472,22 @@ export class MedusaCartProvider extends CartProvider {
404
472
  try {
405
473
  const client = await this.getClient();
406
474
 
407
- // Get current cart
408
- const currentCartResponse = await client.store.cart.retrieve(
409
- payload.cart.key
410
- );
411
- client.store.cart.update(
475
+ const newRegionId = (await this.medusaApi.getActiveRegion()).id;
476
+ const updatedCartResponse = await client.store.cart.update(
412
477
  payload.cart.key,
413
- {
414
- region_id: (await this.medusaApi.getActiveRegion()).id,
415
- },
478
+ this.changeCurrencyPayload(payload, newRegionId),
416
479
  {
417
480
  fields: this.includedFields,
418
481
  }
419
482
  );
420
- if (!currentCartResponse.cart) {
421
- throw new Error('Cart not found');
422
- }
423
-
424
- // Get the new cart
425
- const newCartResponse = await client.store.cart.retrieve(
426
- payload.cart.key
427
- );
428
483
 
429
- if (newCartResponse.cart) {
484
+ if (updatedCartResponse.cart) {
430
485
  // Update session to use new cart
431
486
  this.medusaApi.setSessionData({
432
- activeCartId: newCartResponse.cart.id,
487
+ activeCartId: updatedCartResponse.cart.id,
433
488
  });
434
489
 
435
- return success(this.parseSingle(newCartResponse.cart));
490
+ return success(this.parseSingle(updatedCartResponse.cart));
436
491
  }
437
492
 
438
493
  throw new Error('Failed to change currency');
@@ -441,18 +496,28 @@ export class MedusaCartProvider extends CartProvider {
441
496
  }
442
497
  }
443
498
 
499
+ /**
500
+ * Extension point to control the payload sent to Medusa when creating a cart. By default, it only includes the currency code, but you can override it to include more fields as needed.
501
+ * @param currency
502
+ * @returns
503
+ */
504
+ protected createCartPayload(currency?: string): StoreCreateCart {
505
+ return {
506
+ currency_code: (
507
+ currency ||
508
+ this.context.languageContext.currencyCode ||
509
+ ''
510
+ ).toLowerCase(),
511
+ };
512
+ }
513
+
514
+
444
515
  protected async createCart(currency?: string): Promise<CartIdentifier> {
445
516
  try {
446
517
  const client = await this.getClient();
447
518
 
448
519
  const response = await client.store.cart.create(
449
- {
450
- currency_code: (
451
- currency ||
452
- this.context.languageContext.currencyCode ||
453
- ''
454
- ).toLowerCase(),
455
- },
520
+ this.createCartPayload(currency),
456
521
  {
457
522
  fields: this.includedFields,
458
523
  }
@@ -489,7 +554,7 @@ export class MedusaCartProvider extends CartProvider {
489
554
  * @returns
490
555
  */
491
556
  protected parseItemPrice(
492
- remoteItem: MedusaTypes.StoreCartLineItem,
557
+ remoteItem: StoreCartLineItem,
493
558
  currency: Currency
494
559
  ): ItemCostBreakdown {
495
560
  return parseMedusaItemPrice(remoteItem, currency);
@@ -500,7 +565,7 @@ export class MedusaCartProvider extends CartProvider {
500
565
  * @param remote
501
566
  * @returns
502
567
  */
503
- protected parseCostBreakdown(remote: MedusaTypes.StoreCart): CostBreakDown {
568
+ protected parseCostBreakdown(remote: StoreCart): CostBreakDown {
504
569
  return parseMedusaCostBreakdown(remote);
505
570
  }
506
571
 
@@ -511,9 +576,10 @@ export class MedusaCartProvider extends CartProvider {
511
576
  * @returns
512
577
  */
513
578
  protected parseCartItem(
514
- remoteItem: MedusaTypes.StoreCartLineItem,
579
+ remoteItem: StoreCartLineItem,
515
580
  currency: Currency
516
581
  ): CartItem {
582
+
517
583
  const item: CartItem = {
518
584
  identifier: {
519
585
  key: remoteItem.id,
@@ -535,7 +601,7 @@ export class MedusaCartProvider extends CartProvider {
535
601
  * @param remote
536
602
  * @returns
537
603
  */
538
- protected parseSingle(remote: MedusaTypes.StoreCart): Cart {
604
+ protected parseSingle(remote: StoreCart): Cart {
539
605
  const identifier = MedusaCartIdentifierSchema.parse({
540
606
  key: remote.id,
541
607
  region_id: remote.region_id,
@@ -559,12 +625,35 @@ export class MedusaCartProvider extends CartProvider {
559
625
  items.push(this.parseCartItem(remoteItem, price.grandTotal.currency));
560
626
  }
561
627
 
628
+
629
+ const appliedPromotions = [];
630
+ if (remote.promotions) {
631
+ for (const promo of remote.promotions) {
632
+
633
+ const promotionName = promo.code;
634
+ let promoDescription = '';
635
+ if (promo.application_method?.type === 'percentage') {
636
+ promoDescription = `-${promo.application_method.value}%`;
637
+ }
638
+ if (promo.application_method?.type === 'fixed') {
639
+ promoDescription = `-${promo.application_method.value} ${price.grandTotal.currency}`;
640
+ }
641
+ appliedPromotions.push({
642
+ code: promo.code || '',
643
+ isCouponCode: promo.is_automatic ? false : true,
644
+ name: promotionName || promoDescription,
645
+ description: promoDescription
646
+ } satisfies Promotion);
647
+ }
648
+ }
649
+
562
650
  const result = {
563
651
  identifier,
564
652
  name,
565
653
  description,
566
654
  price,
567
655
  items,
656
+ appliedPromotions,
568
657
  userId: {
569
658
  userId: '???',
570
659
  },