@reactionary/provider-medusa 0.1.2 → 0.1.4

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.
package/core/client.js CHANGED
@@ -122,7 +122,7 @@ class MedusaClient {
122
122
  const productsResponse = await adminClient.admin.product.list({
123
123
  limit: 1,
124
124
  offset: 0,
125
- fields: "+categories.metadata.*",
125
+ fields: "+metadata.*,+categories.metadata.*",
126
126
  variants: {
127
127
  $or: [{ ean: sku }, { upc: sku }, { barcode: sku }]
128
128
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@reactionary/provider-medusa",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "src/index.d.ts",
7
7
  "dependencies": {
8
8
  "zod": "4.1.9",
9
- "@reactionary/core": "0.1.2",
9
+ "@reactionary/core": "0.1.4",
10
10
  "@medusajs/js-sdk": "^2.0.0",
11
11
  "debug": "^4.3.4",
12
12
  "@medusajs/types": "^2.11.0",
@@ -10,33 +10,37 @@ var __decorateClass = (decorators, target, key, kind) => {
10
10
  return result;
11
11
  };
12
12
  import {
13
- CartItemSchema,
14
- CartProvider,
15
- CartSchema,
16
13
  CartIdentifierSchema,
17
- CartQueryByIdSchema,
14
+ CartMutationApplyCouponSchema,
15
+ CartMutationChangeCurrencySchema,
16
+ CartMutationDeleteCartSchema,
18
17
  CartMutationItemAddSchema,
19
- CartMutationItemRemoveSchema,
20
18
  CartMutationItemQuantityChangeSchema,
21
- CartMutationDeleteCartSchema,
22
- CartMutationApplyCouponSchema,
19
+ CartMutationItemRemoveSchema,
23
20
  CartMutationRemoveCouponSchema,
24
- CartMutationChangeCurrencySchema,
21
+ CartProvider,
22
+ CartQueryByIdSchema,
23
+ CartSchema,
25
24
  ProductVariantIdentifierSchema,
26
25
  Reactionary
27
26
  } from "@reactionary/core";
28
27
  import createDebug from "debug";
29
- import { MedusaAdminClient } from "../core/client.js";
30
28
  import {
31
- MedusaCartIdentifierSchema,
32
- MedusaOrderIdentifierSchema,
33
- MedusaSessionSchema
29
+ MedusaCartIdentifierSchema
34
30
  } from "../schema/medusa.schema.js";
31
+ import { handleProviderError, parseMedusaCostBreakdown, parseMedusaItemPrice } from "../utils/medusa-helpers.js";
35
32
  const debug = createDebug("reactionary:medusa:cart");
36
33
  class MedusaCartProvider extends CartProvider {
37
34
  constructor(config, cache, context, client) {
38
35
  super(cache, context);
39
36
  this.client = client;
37
+ /**
38
+ * This controls which fields are always included when fetching a cart
39
+ * You can override this in a subclass to add more fields as needed.
40
+ *
41
+ * example: this.includedFields = [includedFields, '+discounts.*'].join(',');
42
+ */
43
+ this.includedFields = ["+items.*"].join(",");
40
44
  this.config = config;
41
45
  }
42
46
  async getById(payload) {
@@ -78,7 +82,6 @@ class MedusaCartProvider extends CartProvider {
78
82
  );
79
83
  }
80
84
  const variantId = await this.client.resolveVariantId(payload.variant.sku);
81
- const cc = this.context.languageContext;
82
85
  const response = await client.store.cart.createLineItem(
83
86
  medusaId.key,
84
87
  {
@@ -86,7 +89,7 @@ class MedusaCartProvider extends CartProvider {
86
89
  quantity: payload.quantity
87
90
  },
88
91
  {
89
- fields: "+items.*"
92
+ fields: this.includedFields
90
93
  }
91
94
  );
92
95
  if (debug.enabled) {
@@ -97,10 +100,7 @@ class MedusaCartProvider extends CartProvider {
97
100
  }
98
101
  throw new Error("Failed to add item to cart");
99
102
  } catch (error) {
100
- debug("Failed to add item to cart:", error);
101
- throw new Error(
102
- `Failed to add item to cart: ${error instanceof Error ? error.message : "Unknown error"}`
103
- );
103
+ handleProviderError("add item to cart", error);
104
104
  }
105
105
  }
106
106
  async remove(payload) {
@@ -111,7 +111,7 @@ class MedusaCartProvider extends CartProvider {
111
111
  medusaId.key,
112
112
  payload.item.key,
113
113
  {
114
- fields: "+items.*"
114
+ fields: this.includedFields
115
115
  }
116
116
  );
117
117
  if (response.parent) {
@@ -119,10 +119,7 @@ class MedusaCartProvider extends CartProvider {
119
119
  }
120
120
  throw new Error("Failed to remove item from cart");
121
121
  } catch (error) {
122
- debug("Failed to remove item from cart:", error);
123
- throw new Error(
124
- `Failed to remove item from cart: ${error instanceof Error ? error.message : "Unknown error"}`
125
- );
122
+ handleProviderError("remove item from cart", error);
126
123
  }
127
124
  }
128
125
  async changeQuantity(payload) {
@@ -141,7 +138,7 @@ class MedusaCartProvider extends CartProvider {
141
138
  quantity: payload.quantity
142
139
  },
143
140
  {
144
- fields: "+items.*"
141
+ fields: this.includedFields
145
142
  }
146
143
  );
147
144
  if (response.cart) {
@@ -149,10 +146,7 @@ class MedusaCartProvider extends CartProvider {
149
146
  }
150
147
  throw new Error("Failed to change item quantity");
151
148
  } catch (error) {
152
- debug("Failed to change item quantity:", error);
153
- throw new Error(
154
- `Failed to change item quantity: ${error instanceof Error ? error.message : "Unknown error"}`
155
- );
149
+ handleProviderError("change item quantity", error);
156
150
  }
157
151
  }
158
152
  async getActiveCartId() {
@@ -217,7 +211,7 @@ class MedusaCartProvider extends CartProvider {
217
211
  promo_codes: [payload.couponCode]
218
212
  },
219
213
  {
220
- fields: "+items.*"
214
+ fields: this.includedFields
221
215
  }
222
216
  );
223
217
  if (response.cart) {
@@ -225,10 +219,7 @@ class MedusaCartProvider extends CartProvider {
225
219
  }
226
220
  throw new Error("Failed to apply coupon code");
227
221
  } catch (error) {
228
- debug("Failed to apply coupon code:", error);
229
- throw new Error(
230
- `Failed to apply coupon code: ${error instanceof Error ? error.message : "Unknown error"}`
231
- );
222
+ handleProviderError("apply coupon code", error);
232
223
  }
233
224
  }
234
225
  async removeCouponCode(payload) {
@@ -243,6 +234,8 @@ class MedusaCartProvider extends CartProvider {
243
234
  const remainingCodes = manualDiscounts.filter((x) => x.code !== payload.couponCode).map((promotion) => promotion.code) || [];
244
235
  const response = await client.store.cart.update(medusaId.key, {
245
236
  promo_codes: remainingCodes || []
237
+ }, {
238
+ fields: this.includedFields
246
239
  });
247
240
  if (response.cart) {
248
241
  return this.parseSingle(response.cart);
@@ -250,10 +243,7 @@ class MedusaCartProvider extends CartProvider {
250
243
  }
251
244
  throw new Error("Failed to remove coupon code");
252
245
  } catch (error) {
253
- debug("Failed to remove coupon code:", error);
254
- throw new Error(
255
- `Failed to remove coupon code: ${error instanceof Error ? error.message : "Unknown error"}`
256
- );
246
+ handleProviderError("remove coupon code", error);
257
247
  }
258
248
  }
259
249
  async changeCurrency(payload) {
@@ -268,7 +258,7 @@ class MedusaCartProvider extends CartProvider {
268
258
  region_id: (await this.client.getActiveRegion()).id
269
259
  },
270
260
  {
271
- fields: "+items.*"
261
+ fields: this.includedFields
272
262
  }
273
263
  );
274
264
  if (!currentCartResponse.cart) {
@@ -285,10 +275,7 @@ class MedusaCartProvider extends CartProvider {
285
275
  }
286
276
  throw new Error("Failed to change currency");
287
277
  } catch (error) {
288
- debug("Failed to change currency:", error);
289
- throw new Error(
290
- `Failed to change currency: ${error instanceof Error ? error.message : "Unknown error"}`
291
- );
278
+ handleProviderError("change currency", error);
292
279
  }
293
280
  }
294
281
  async createCart(currency) {
@@ -299,7 +286,7 @@ class MedusaCartProvider extends CartProvider {
299
286
  currency_code: (currency || this.context.languageContext.currencyCode || "").toLowerCase()
300
287
  },
301
288
  {
302
- fields: "+items.*"
289
+ fields: this.includedFields
303
290
  }
304
291
  );
305
292
  if (response.cart) {
@@ -314,19 +301,36 @@ class MedusaCartProvider extends CartProvider {
314
301
  }
315
302
  throw new Error("Failed to create cart");
316
303
  } catch (error) {
317
- debug("Failed to create cart:", error);
318
- throw new Error(
319
- `Failed to create cart: ${error instanceof Error ? error.message : "Unknown error"}`
320
- );
304
+ handleProviderError("create cart", error);
321
305
  }
322
306
  }
323
307
  async getClient() {
324
308
  return this.client.getClient();
325
309
  }
310
+ /**
311
+ * Extension point to control the parsing of a single cart item price
312
+ * @param remoteItem
313
+ * @param currency
314
+ * @returns
315
+ */
316
+ parseItemPrice(remoteItem, currency) {
317
+ return parseMedusaItemPrice(remoteItem, currency);
318
+ }
319
+ /**
320
+ * Extension point to control the parsing of the cost breakdown of a cart
321
+ * @param remote
322
+ * @returns
323
+ */
324
+ parseCostBreakdown(remote) {
325
+ return parseMedusaCostBreakdown(remote);
326
+ }
327
+ /**
328
+ * Extension point to control the parsing of a single cart item
329
+ * @param remoteItem
330
+ * @param currency
331
+ * @returns
332
+ */
326
333
  parseCartItem(remoteItem, currency) {
327
- const unitPrice = remoteItem.unit_price || 0;
328
- const totalPrice = unitPrice * remoteItem.quantity || 0;
329
- const discountTotal = remoteItem.discount_total || 0;
330
334
  const item = {
331
335
  identifier: {
332
336
  key: remoteItem.id
@@ -338,27 +342,15 @@ class MedusaCartProvider extends CartProvider {
338
342
  sku: remoteItem.variant_sku || ""
339
343
  }),
340
344
  quantity: remoteItem.quantity || 1,
341
- price: {
342
- unitPrice: {
343
- value: unitPrice,
344
- currency
345
- },
346
- unitDiscount: {
347
- value: discountTotal / remoteItem.quantity,
348
- currency
349
- },
350
- totalPrice: {
351
- value: totalPrice,
352
- currency
353
- },
354
- totalDiscount: {
355
- value: discountTotal,
356
- currency
357
- }
358
- }
345
+ price: parseMedusaItemPrice(remoteItem, currency)
359
346
  };
360
347
  return item;
361
348
  }
349
+ /**
350
+ * Extension point to control the parsing of a single cart
351
+ * @param remote
352
+ * @returns
353
+ */
362
354
  parseSingle(remote) {
363
355
  const identifier = MedusaCartIdentifierSchema.parse({
364
356
  key: remote.id,
@@ -366,43 +358,12 @@ class MedusaCartProvider extends CartProvider {
366
358
  });
367
359
  const name = "" + (remote.metadata?.["name"] || "");
368
360
  const description = "" + (remote.metadata?.["description"] || "");
369
- const grandTotal = remote.total || 0;
370
- const shippingTotal = remote.shipping_total || 0;
371
- const taxTotal = remote.tax_total || 0;
372
- const discountTotal = remote.discount_total || 0;
373
- const subtotal = remote.subtotal || 0;
374
- const currency = (remote.currency_code || "EUR").toUpperCase();
375
- const price = {
376
- totalTax: {
377
- value: taxTotal,
378
- currency
379
- },
380
- totalDiscount: {
381
- value: discountTotal,
382
- currency
383
- },
384
- totalSurcharge: {
385
- value: 0,
386
- currency
387
- },
388
- totalShipping: {
389
- value: shippingTotal,
390
- currency
391
- },
392
- totalProductPrice: {
393
- value: subtotal,
394
- currency
395
- },
396
- grandTotal: {
397
- value: grandTotal,
398
- currency
399
- }
400
- };
361
+ const price = this.parseCostBreakdown(remote);
401
362
  const items = new Array();
402
363
  const allItems = remote.items || [];
403
364
  allItems.sort((a, b) => a.created_at && b.created_at ? new Date(a.created_at).getTime() - new Date(b.created_at).getTime() : 0);
404
365
  for (const remoteItem of allItems) {
405
- items.push(this.parseCartItem(remoteItem, remote.currency_code.toUpperCase()));
366
+ items.push(this.parseCartItem(remoteItem, price.grandTotal.currency));
406
367
  }
407
368
  const meta = {
408
369
  cache: {
@@ -30,6 +30,7 @@ import {
30
30
  } from "@reactionary/core";
31
31
  import createDebug from "debug";
32
32
  import z from "zod";
33
+ import { handleProviderError, parseMedusaCostBreakdown, parseMedusaItemPrice } from "../utils/medusa-helpers.js";
33
34
  import {
34
35
  } from "../schema/medusa.schema.js";
35
36
  const debug = createDebug("reactionary:medusa:checkout");
@@ -46,7 +47,13 @@ class MedusaCheckoutProvider extends CheckoutProvider {
46
47
  constructor(config, cache, context, client) {
47
48
  super(cache, context);
48
49
  this.client = client;
49
- this.returnedCheckoutFields = "+items.*";
50
+ /**
51
+ * This controls which fields are always included when fetching a cart
52
+ * You can override this in a subclass to add more fields as needed.
53
+ *
54
+ * example: this.includedFields = [includedFields, '+discounts.*'].join(',');
55
+ */
56
+ this.includedFields = ["+items.*"].join(",");
50
57
  this.config = config;
51
58
  }
52
59
  async initiateCheckoutForCart(payload) {
@@ -67,7 +74,7 @@ class MedusaCheckoutProvider extends CheckoutProvider {
67
74
  }
68
75
  },
69
76
  {
70
- fields: this.returnedCheckoutFields
77
+ fields: this.includedFields
71
78
  }
72
79
  );
73
80
  return this.parseSingle(response.cart);
@@ -75,7 +82,7 @@ class MedusaCheckoutProvider extends CheckoutProvider {
75
82
  async getById(payload) {
76
83
  const client = await this.client.getClient();
77
84
  const response = await client.store.cart.retrieve(payload.identifier.key, {
78
- fields: this.returnedCheckoutFields
85
+ fields: this.includedFields
79
86
  });
80
87
  return this.parseSingle(response.cart);
81
88
  }
@@ -89,7 +96,7 @@ class MedusaCheckoutProvider extends CheckoutProvider {
89
96
  )
90
97
  },
91
98
  {
92
- fields: this.returnedCheckoutFields
99
+ fields: this.includedFields
93
100
  }
94
101
  );
95
102
  return this.parseSingle(response.cart);
@@ -189,7 +196,7 @@ class MedusaCheckoutProvider extends CheckoutProvider {
189
196
  const updatedCartResponse = await client.store.cart.retrieve(
190
197
  payload.checkout.key,
191
198
  {
192
- fields: this.returnedCheckoutFields
199
+ fields: this.includedFields
193
200
  }
194
201
  );
195
202
  return this.parseSingle(updatedCartResponse.cart);
@@ -224,17 +231,14 @@ class MedusaCheckoutProvider extends CheckoutProvider {
224
231
  }
225
232
  });
226
233
  const response = await client.store.cart.retrieve(medusaId.key, {
227
- fields: this.returnedCheckoutFields
234
+ fields: this.includedFields
228
235
  });
229
236
  if (response.cart) {
230
237
  return this.parseSingle(response.cart);
231
238
  }
232
- throw new Error("Failed to set shipping info");
239
+ throw new Error("Failed to set shipping method");
233
240
  } catch (error) {
234
- debug("Failed to set shipping info:", error);
235
- throw new Error(
236
- `Failed to set shipping info: ${error instanceof Error ? error.message : "Unknown error"}`
237
- );
241
+ handleProviderError("set shipping method", error);
238
242
  }
239
243
  }
240
244
  async finalizeCheckout(payload) {
@@ -258,6 +262,11 @@ class MedusaCheckoutProvider extends CheckoutProvider {
258
262
  }
259
263
  throw new Error("Something failed during order creation");
260
264
  }
265
+ /**
266
+ * Extension point to map an Address to a Store Address
267
+ * @param address
268
+ * @returns
269
+ */
261
270
  mapAddressToStoreAddress(address) {
262
271
  return {
263
272
  first_name: address.firstName,
@@ -269,6 +278,11 @@ class MedusaCheckoutProvider extends CheckoutProvider {
269
278
  country_code: address.countryCode?.toLowerCase()
270
279
  };
271
280
  }
281
+ /**
282
+ * Extension point to control the parsing of an address from a store cart address
283
+ * @param storeAddress
284
+ * @returns
285
+ */
272
286
  composeAddressFromStoreAddress(storeAddress) {
273
287
  return {
274
288
  identifier: AddressIdentifierSchema.parse({
@@ -291,6 +305,29 @@ class MedusaCheckoutProvider extends CheckoutProvider {
291
305
  region: ""
292
306
  };
293
307
  }
308
+ /**
309
+ * Extension point to control the parsing of a single cart item price
310
+ * @param remoteItem
311
+ * @param currency
312
+ * @returns
313
+ */
314
+ parseItemPrice(remoteItem, currency) {
315
+ return parseMedusaItemPrice(remoteItem, currency);
316
+ }
317
+ /**
318
+ * Extension point to control the parsing of the cost breakdown of a cart
319
+ * @param remote
320
+ * @returns
321
+ */
322
+ parseCostBreakdown(remote) {
323
+ return parseMedusaCostBreakdown(remote);
324
+ }
325
+ /**
326
+ * Extension point to control the parsing of a single checkout item
327
+ * @param remoteItem
328
+ * @param currency
329
+ * @returns
330
+ */
294
331
  parseCheckoutItem(remoteItem, currency) {
295
332
  const unitPrice = remoteItem.unit_price || 0;
296
333
  const totalPrice = unitPrice * remoteItem.quantity || 0;
@@ -303,27 +340,15 @@ class MedusaCheckoutProvider extends CheckoutProvider {
303
340
  sku: remoteItem.variant_sku || ""
304
341
  },
305
342
  quantity: remoteItem.quantity || 1,
306
- price: {
307
- unitPrice: {
308
- value: unitPrice,
309
- currency
310
- },
311
- unitDiscount: {
312
- value: discountTotal / remoteItem.quantity,
313
- currency
314
- },
315
- totalPrice: {
316
- value: totalPrice,
317
- currency
318
- },
319
- totalDiscount: {
320
- value: discountTotal,
321
- currency
322
- }
323
- }
343
+ price: this.parseItemPrice(remoteItem, currency)
324
344
  };
325
345
  return item;
326
346
  }
347
+ /**
348
+ * Extension point to control the parsing of the Checkout object
349
+ * @param remote
350
+ * @returns
351
+ */
327
352
  parseSingle(remote) {
328
353
  const identifier = {
329
354
  key: remote.id
@@ -331,41 +356,10 @@ class MedusaCheckoutProvider extends CheckoutProvider {
331
356
  };
332
357
  const name = "" + (remote.metadata?.["name"] || "");
333
358
  const description = "" + (remote.metadata?.["description"] || "");
334
- const grandTotal = remote.total || 0;
335
- const shippingTotal = remote.shipping_total || 0;
336
- const taxTotal = remote.tax_total || 0;
337
- const discountTotal = remote.discount_total || 0;
338
- const subtotal = remote.subtotal || 0;
339
- const currency = (remote.currency_code || "EUR").toUpperCase();
340
- const price = {
341
- totalTax: {
342
- value: taxTotal,
343
- currency
344
- },
345
- totalDiscount: {
346
- value: discountTotal,
347
- currency
348
- },
349
- totalSurcharge: {
350
- value: 0,
351
- currency
352
- },
353
- totalShipping: {
354
- value: shippingTotal,
355
- currency
356
- },
357
- totalProductPrice: {
358
- value: subtotal,
359
- currency
360
- },
361
- grandTotal: {
362
- value: grandTotal,
363
- currency
364
- }
365
- };
359
+ const price = this.parseCostBreakdown(remote);
366
360
  const items = new Array();
367
361
  for (const remoteItem of remote.items || []) {
368
- items.push(this.parseCheckoutItem(remoteItem, currency));
362
+ items.push(this.parseCheckoutItem(remoteItem, price.grandTotal.currency));
369
363
  }
370
364
  const meta = {
371
365
  cache: {
@@ -34,7 +34,9 @@ class MedusaProductProvider extends ProductProvider {
34
34
  }
35
35
  let response;
36
36
  try {
37
- response = await client.store.product.retrieve(payload.identifier.key);
37
+ response = await client.store.product.retrieve(payload.identifier.key, {
38
+ fields: "+metadata,+categories.metadata.*"
39
+ });
38
40
  } catch (error) {
39
41
  if (debug.enabled) {
40
42
  debug(`Product with ID: ${payload.identifier.key} not found, returning empty product. Error %O `, error);
@@ -51,7 +53,8 @@ class MedusaProductProvider extends ProductProvider {
51
53
  const response = await client.store.product.list({
52
54
  handle: payload.slug,
53
55
  limit: 1,
54
- offset: 0
56
+ offset: 0,
57
+ fields: "+metadata.*"
55
58
  });
56
59
  if (debug.enabled) {
57
60
  debug(`Found ${response.count} products for slug: ${payload.slug}`);
@@ -183,6 +186,11 @@ class MedusaProductProvider extends ProductProvider {
183
186
  if (_body.material) {
184
187
  sharedAttributes.push(this.createSynthAttribute("material", "Material", _body.material));
185
188
  }
189
+ if (_body.metadata) {
190
+ for (const [key, value] of Object.entries(_body.metadata)) {
191
+ sharedAttributes.push(this.createSynthAttribute(key, key, String(value)));
192
+ }
193
+ }
186
194
  return sharedAttributes;
187
195
  }
188
196
  }
@@ -1,4 +1,4 @@
1
- import type { Cache, Cart, CartIdentifier, CartMutationApplyCoupon, CartMutationChangeCurrency, CartMutationDeleteCart, CartMutationItemAdd, CartMutationItemQuantityChange, CartMutationItemRemove, CartMutationRemoveCoupon, CartQueryById, Currency, RequestContext, CartItem } from '@reactionary/core';
1
+ import type { Cache, Cart, CartIdentifier, CartItem, CartMutationApplyCoupon, CartMutationChangeCurrency, CartMutationDeleteCart, CartMutationItemAdd, CartMutationItemQuantityChange, CartMutationItemRemove, CartMutationRemoveCoupon, CartQueryById, CostBreakDown, Currency, ItemCostBreakdown, RequestContext } from '@reactionary/core';
2
2
  import { CartProvider } from '@reactionary/core';
3
3
  import type { MedusaClient } from '../core/client.js';
4
4
  import type { MedusaConfiguration } from '../schema/configuration.schema.js';
@@ -6,6 +6,13 @@ import type MedusaTypes = require('@medusajs/types');
6
6
  export declare class MedusaCartProvider extends CartProvider {
7
7
  client: MedusaClient;
8
8
  protected config: MedusaConfiguration;
9
+ /**
10
+ * This controls which fields are always included when fetching a cart
11
+ * You can override this in a subclass to add more fields as needed.
12
+ *
13
+ * example: this.includedFields = [includedFields, '+discounts.*'].join(',');
14
+ */
15
+ protected includedFields: string;
9
16
  constructor(config: MedusaConfiguration, cache: Cache, context: RequestContext, client: MedusaClient);
10
17
  getById(payload: CartQueryById): Promise<Cart>;
11
18
  add(payload: CartMutationItemAdd): Promise<Cart>;
@@ -23,6 +30,30 @@ export declare class MedusaCartProvider extends CartProvider {
23
30
  store: import("@medusajs/js-sdk").Store;
24
31
  auth: import("@medusajs/js-sdk").Auth;
25
32
  }>;
33
+ /**
34
+ * Extension point to control the parsing of a single cart item price
35
+ * @param remoteItem
36
+ * @param currency
37
+ * @returns
38
+ */
39
+ protected parseItemPrice(remoteItem: MedusaTypes.StoreCartLineItem, currency: Currency): ItemCostBreakdown;
40
+ /**
41
+ * Extension point to control the parsing of the cost breakdown of a cart
42
+ * @param remote
43
+ * @returns
44
+ */
45
+ protected parseCostBreakdown(remote: MedusaTypes.StoreCart): CostBreakDown;
46
+ /**
47
+ * Extension point to control the parsing of a single cart item
48
+ * @param remoteItem
49
+ * @param currency
50
+ * @returns
51
+ */
26
52
  protected parseCartItem(remoteItem: MedusaTypes.StoreCartLineItem, currency: Currency): CartItem;
53
+ /**
54
+ * Extension point to control the parsing of a single cart
55
+ * @param remote
56
+ * @returns
57
+ */
27
58
  protected parseSingle(remote: MedusaTypes.StoreCart): Cart;
28
59
  }
@@ -1,5 +1,5 @@
1
- import type { StoreCart } from '@medusajs/types';
2
- import type { Address, Cache, Checkout, CheckoutIdentifier, CheckoutItem, CheckoutMutationAddPaymentInstruction, CheckoutMutationFinalizeCheckout, CheckoutMutationInitiateCheckout, CheckoutMutationRemovePaymentInstruction, CheckoutMutationSetShippingAddress, CheckoutMutationSetShippingInstruction, CheckoutQueryById, CheckoutQueryForAvailablePaymentMethods, CheckoutQueryForAvailableShippingMethods, Currency, PaymentMethod, RequestContext, ShippingMethod } from '@reactionary/core';
1
+ import type { StoreCart, StoreCartAddress, StoreCartLineItem } from '@medusajs/types';
2
+ import type { Address, Cache, Checkout, CheckoutIdentifier, CheckoutItem, CheckoutMutationAddPaymentInstruction, CheckoutMutationFinalizeCheckout, CheckoutMutationInitiateCheckout, CheckoutMutationRemovePaymentInstruction, CheckoutMutationSetShippingAddress, CheckoutMutationSetShippingInstruction, CheckoutQueryById, CheckoutQueryForAvailablePaymentMethods, CheckoutQueryForAvailableShippingMethods, CostBreakDown, Currency, ItemCostBreakdown, PaymentMethod, RequestContext, ShippingMethod } from '@reactionary/core';
3
3
  import { CheckoutProvider } from '@reactionary/core';
4
4
  import type { MedusaClient } from '../core/client.js';
5
5
  import type { MedusaConfiguration } from '../schema/configuration.schema.js';
@@ -10,7 +10,13 @@ export declare class CheckoutNotReadyForFinalizationError extends Error {
10
10
  export declare class MedusaCheckoutProvider extends CheckoutProvider {
11
11
  client: MedusaClient;
12
12
  protected config: MedusaConfiguration;
13
- protected returnedCheckoutFields: string;
13
+ /**
14
+ * This controls which fields are always included when fetching a cart
15
+ * You can override this in a subclass to add more fields as needed.
16
+ *
17
+ * example: this.includedFields = [includedFields, '+discounts.*'].join(',');
18
+ */
19
+ protected includedFields: string;
14
20
  constructor(config: MedusaConfiguration, cache: Cache, context: RequestContext, client: MedusaClient);
15
21
  initiateCheckoutForCart(payload: CheckoutMutationInitiateCheckout): Promise<Checkout>;
16
22
  getById(payload: CheckoutQueryById): Promise<Checkout | null>;
@@ -21,6 +27,11 @@ export declare class MedusaCheckoutProvider extends CheckoutProvider {
21
27
  removePaymentInstruction(payload: CheckoutMutationRemovePaymentInstruction): Promise<Checkout>;
22
28
  setShippingInstruction(payload: CheckoutMutationSetShippingInstruction): Promise<Checkout>;
23
29
  finalizeCheckout(payload: CheckoutMutationFinalizeCheckout): Promise<Checkout>;
30
+ /**
31
+ * Extension point to map an Address to a Store Address
32
+ * @param address
33
+ * @returns
34
+ */
24
35
  protected mapAddressToStoreAddress(address: Partial<Address>): {
25
36
  first_name: string | undefined;
26
37
  last_name: string | undefined;
@@ -30,7 +41,36 @@ export declare class MedusaCheckoutProvider extends CheckoutProvider {
30
41
  postal_code: string | undefined;
31
42
  country_code: string | undefined;
32
43
  };
33
- protected composeAddressFromStoreAddress(storeAddress: any): Address;
34
- protected parseCheckoutItem(remoteItem: any, currency: Currency): CheckoutItem;
44
+ /**
45
+ * Extension point to control the parsing of an address from a store cart address
46
+ * @param storeAddress
47
+ * @returns
48
+ */
49
+ protected composeAddressFromStoreAddress(storeAddress: StoreCartAddress): Address;
50
+ /**
51
+ * Extension point to control the parsing of a single cart item price
52
+ * @param remoteItem
53
+ * @param currency
54
+ * @returns
55
+ */
56
+ protected parseItemPrice(remoteItem: StoreCartLineItem, currency: Currency): ItemCostBreakdown;
57
+ /**
58
+ * Extension point to control the parsing of the cost breakdown of a cart
59
+ * @param remote
60
+ * @returns
61
+ */
62
+ protected parseCostBreakdown(remote: StoreCart): CostBreakDown;
63
+ /**
64
+ * Extension point to control the parsing of a single checkout item
65
+ * @param remoteItem
66
+ * @param currency
67
+ * @returns
68
+ */
69
+ protected parseCheckoutItem(remoteItem: StoreCartLineItem, currency: Currency): CheckoutItem;
70
+ /**
71
+ * Extension point to control the parsing of the Checkout object
72
+ * @param remote
73
+ * @returns
74
+ */
35
75
  protected parseSingle(remote: StoreCart): Checkout;
36
76
  }
@@ -0,0 +1,35 @@
1
+ import type { StoreCart } from '@medusajs/types';
2
+ import type { CostBreakDown, Currency } from '@reactionary/core';
3
+ /**
4
+ * Parses cost breakdown from Medusa StoreCart
5
+ */
6
+ export declare function parseMedusaCostBreakdown(remote: StoreCart): CostBreakDown;
7
+ /**
8
+ * Parses item price structure from Medusa line item
9
+ */
10
+ export declare function parseMedusaItemPrice(remoteItem: {
11
+ unit_price?: number;
12
+ quantity: number;
13
+ discount_total?: number;
14
+ }, currency: Currency): {
15
+ unitPrice: {
16
+ value: number;
17
+ currency: Currency;
18
+ };
19
+ unitDiscount: {
20
+ value: number;
21
+ currency: Currency;
22
+ };
23
+ totalPrice: {
24
+ value: number;
25
+ currency: Currency;
26
+ };
27
+ totalDiscount: {
28
+ value: number;
29
+ currency: Currency;
30
+ };
31
+ };
32
+ /**
33
+ * Handles provider errors with consistent formatting
34
+ */
35
+ export declare function handleProviderError(action: string, error: unknown): never;
@@ -1,8 +1,8 @@
1
- import { CartSchema, NoOpCache, createInitialRequestContext } from "@reactionary/core";
2
- import { beforeAll, beforeEach, describe, expect, it } from "vitest";
1
+ import { NoOpCache, createInitialRequestContext } from "@reactionary/core";
2
+ import { beforeEach, describe, expect, it } from "vitest";
3
+ import { MedusaClient } from "../core/client.js";
3
4
  import { MedusaCartProvider } from "../providers/cart.provider.js";
4
5
  import { getMedusaTestConfiguration } from "./test-utils.js";
5
- import { MedusaClient } from "../core/client.js";
6
6
  const testData = {
7
7
  skuWithoutTiers: "4047443491480",
8
8
  skuWithTiers: "0819927012825"
@@ -31,6 +31,9 @@ describe("Medusa Product Provider", () => {
31
31
  expect(result.mainVariant).toBeDefined();
32
32
  expect(result.mainVariant.identifier.sku).toBe(testData.product.sku);
33
33
  expect(result.mainVariant.images[0].sourceUrl).toBe(testData.product.image);
34
+ expect(result.sharedAttributes.length).toBeGreaterThan(1);
35
+ expect(result.sharedAttributes[1].values.length).toBeGreaterThan(0);
36
+ expect(result.sharedAttributes[1].values[0].value).toBeTruthy();
34
37
  });
35
38
  it("should be able to get a product by slug", async () => {
36
39
  const result = await provider.getBySlug({ slug: testData.product.slug });
@@ -58,12 +61,22 @@ describe("Medusa Product Provider", () => {
58
61
  expect(result.mainVariant).toBeDefined();
59
62
  expect(result.mainVariant.identifier.sku).toBe(testData.product.sku);
60
63
  expect(result.mainVariant.images[0].sourceUrl).toBe(testData.product.image);
64
+ expect(result.sharedAttributes.length).toBeGreaterThan(1);
65
+ expect(result.sharedAttributes[1].values.length).toBeGreaterThan(0);
66
+ expect(result.sharedAttributes[1].values[0].value).toBeTruthy();
61
67
  }
62
68
  });
63
69
  it("should return null for unknown slug", async () => {
64
70
  const result = await provider.getBySlug({ slug: "unknown-slug" });
65
71
  expect(result).toBeNull();
66
72
  });
73
+ it("should contain both product level and variant level attributes", async () => {
74
+ const result = await provider.getBySlug({ slug: testData.product.slug });
75
+ expect(result).toBeTruthy();
76
+ expect(result.sharedAttributes.length).toBeGreaterThan(1);
77
+ expect(result.sharedAttributes[1].values.length).toBeGreaterThan(0);
78
+ expect(result.sharedAttributes[1].values[0].value).toBeTruthy();
79
+ });
67
80
  it("should return a placeholder product for unknown id", async () => {
68
81
  const result = await provider.getById({ identifier: { key: "unknown-id" } });
69
82
  expect(result).toBeTruthy();
@@ -0,0 +1,72 @@
1
+ import createDebug from "debug";
2
+ const debug = createDebug("reactionary:medusa:helpers");
3
+ function parseMedusaCostBreakdown(remote) {
4
+ const grandTotal = remote.total || 0;
5
+ const shippingTotal = remote.shipping_total || 0;
6
+ const taxTotal = remote.tax_total || 0;
7
+ const discountTotal = remote.discount_total || 0;
8
+ const subtotal = remote.subtotal || 0;
9
+ const currency = (remote.currency_code || "EUR").toUpperCase();
10
+ return {
11
+ totalTax: {
12
+ value: taxTotal,
13
+ currency
14
+ },
15
+ totalDiscount: {
16
+ value: discountTotal,
17
+ currency
18
+ },
19
+ totalSurcharge: {
20
+ value: 0,
21
+ currency
22
+ },
23
+ totalShipping: {
24
+ value: shippingTotal,
25
+ currency
26
+ },
27
+ totalProductPrice: {
28
+ value: subtotal,
29
+ currency
30
+ },
31
+ grandTotal: {
32
+ value: grandTotal,
33
+ currency
34
+ }
35
+ };
36
+ }
37
+ function parseMedusaItemPrice(remoteItem, currency) {
38
+ const unitPrice = remoteItem.unit_price || 0;
39
+ const totalPrice = unitPrice * remoteItem.quantity || 0;
40
+ const discountTotal = remoteItem.discount_total || 0;
41
+ return {
42
+ unitPrice: {
43
+ value: unitPrice,
44
+ currency
45
+ },
46
+ unitDiscount: {
47
+ value: discountTotal / remoteItem.quantity,
48
+ currency
49
+ },
50
+ totalPrice: {
51
+ value: totalPrice,
52
+ currency
53
+ },
54
+ totalDiscount: {
55
+ value: discountTotal,
56
+ currency
57
+ }
58
+ };
59
+ }
60
+ function handleProviderError(action, error) {
61
+ if (debug.enabled) {
62
+ debug(`Failed to ${action}:`, error);
63
+ }
64
+ throw new Error(
65
+ `Failed to ${action}: ${error instanceof Error ? error.message : "Unknown error"}`
66
+ );
67
+ }
68
+ export {
69
+ handleProviderError,
70
+ parseMedusaCostBreakdown,
71
+ parseMedusaItemPrice
72
+ };