@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 +1 -1
- package/package.json +2 -2
- package/providers/cart.provider.js +62 -101
- package/providers/checkout.provider.js +56 -62
- package/providers/product.provider.js +10 -2
- package/src/providers/cart.provider.d.ts +32 -1
- package/src/providers/checkout.provider.d.ts +45 -5
- package/src/utils/medusa-helpers.d.ts +35 -0
- package/test/cart.provider.spec.js +3 -3
- package/test/product.provider.spec.js +13 -0
- package/utils/medusa-helpers.js +72 -0
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.
|
|
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.
|
|
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
|
-
|
|
14
|
+
CartMutationApplyCouponSchema,
|
|
15
|
+
CartMutationChangeCurrencySchema,
|
|
16
|
+
CartMutationDeleteCartSchema,
|
|
18
17
|
CartMutationItemAddSchema,
|
|
19
|
-
CartMutationItemRemoveSchema,
|
|
20
18
|
CartMutationItemQuantityChangeSchema,
|
|
21
|
-
|
|
22
|
-
CartMutationApplyCouponSchema,
|
|
19
|
+
CartMutationItemRemoveSchema,
|
|
23
20
|
CartMutationRemoveCouponSchema,
|
|
24
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
239
|
+
throw new Error("Failed to set shipping method");
|
|
233
240
|
} catch (error) {
|
|
234
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
+
};
|