@reactionary/source 0.3.17 → 0.6.1
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/README.md +22 -11
- package/core/src/schemas/models/cart.model.ts +7 -2
- package/core/src/schemas/models/identifiers.model.ts +12 -8
- package/core/src/schemas/models/price.model.ts +9 -0
- package/examples/node/package.json +7 -7
- package/examples/node/src/capabilities/cart.spec.ts +97 -15
- package/examples/node/src/capabilities/category.spec.ts +27 -32
- package/examples/node/src/capabilities/checkout.spec.ts +5 -5
- package/examples/node/src/capabilities/identity.spec.ts +5 -1
- package/examples/node/src/capabilities/inventory.spec.ts +1 -1
- package/package.json +3 -3
- package/providers/algolia/src/providers/product-search.provider.ts +19 -14
- package/providers/commercetools/src/providers/cart.provider.ts +76 -11
- package/providers/fake/src/providers/cart.provider.ts +1 -0
- package/providers/medusa/src/providers/cart.provider.ts +159 -70
- package/providers/medusa/src/providers/category.provider.ts +35 -23
- package/providers/medusa/src/providers/checkout.provider.ts +78 -41
- package/providers/medusa/src/providers/order-search.provider.ts +21 -10
- package/providers/medusa/src/providers/product-recommendations.provider.ts +10 -6
- package/providers/medusa/src/providers/product-search.provider.ts +19 -10
- package/providers/medusa/src/providers/product.provider.ts +20 -12
- package/providers/medusa/src/providers/profile.provider.ts +38 -13
- package/providers/meilisearch/src/providers/order-search.provider.ts +17 -12
- package/providers/meilisearch/src/providers/product-recommendations.provider.ts +10 -11
- package/providers/meilisearch/src/providers/product-search.provider.ts +23 -18
|
@@ -37,14 +37,7 @@ export class AlgoliaProductSearchProvider extends ProductSearchProvider {
|
|
|
37
37
|
this.config = config;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
inputSchema: ProductSearchQueryByTermSchema,
|
|
42
|
-
outputSchema: ProductSearchResultSchema
|
|
43
|
-
})
|
|
44
|
-
public override async queryByTerm(
|
|
45
|
-
payload: ProductSearchQueryByTerm
|
|
46
|
-
): Promise<Result<ProductSearchResult>> {
|
|
47
|
-
const client = algoliasearch(this.config.appId, this.config.apiKey);
|
|
40
|
+
protected queryByTermPayload(payload: ProductSearchQueryByTerm) {
|
|
48
41
|
|
|
49
42
|
const facetsThatAreNotCategory = payload.search.facets.filter(x => x.facet.key !== 'categories');
|
|
50
43
|
const categoryFacet = payload.search.facets.find(x => x.facet.key === 'categories') || payload.search.categoryFilter;
|
|
@@ -60,11 +53,7 @@ export class AlgoliaProductSearchProvider extends ProductSearchProvider {
|
|
|
60
53
|
if (categoryFacet) {
|
|
61
54
|
finalFilters.push(`categories:"${categoryFacet.key}"`);
|
|
62
55
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const remote = await client.search<AlgoliaNativeRecord>({
|
|
66
|
-
requests: [
|
|
67
|
-
{
|
|
56
|
+
return {
|
|
68
57
|
indexName: this.config.indexName,
|
|
69
58
|
query: payload.search.term,
|
|
70
59
|
page: payload.search.paginationOptions.pageNumber - 1,
|
|
@@ -75,7 +64,23 @@ export class AlgoliaProductSearchProvider extends ProductSearchProvider {
|
|
|
75
64
|
facetFilters: finalFacetFilters,
|
|
76
65
|
filters: (finalFilters || [])
|
|
77
66
|
.join(' AND '),
|
|
78
|
-
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Reactionary({
|
|
71
|
+
inputSchema: ProductSearchQueryByTermSchema,
|
|
72
|
+
outputSchema: ProductSearchResultSchema
|
|
73
|
+
})
|
|
74
|
+
public override async queryByTerm(
|
|
75
|
+
payload: ProductSearchQueryByTerm
|
|
76
|
+
): Promise<Result<ProductSearchResult>> {
|
|
77
|
+
const client = algoliasearch(this.config.appId, this.config.apiKey);
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
const remote = await client.search<AlgoliaNativeRecord>({
|
|
82
|
+
requests: [
|
|
83
|
+
this.queryByTermPayload(payload)
|
|
79
84
|
],
|
|
80
85
|
});
|
|
81
86
|
|
|
@@ -35,6 +35,7 @@ import type {
|
|
|
35
35
|
CostBreakDown,
|
|
36
36
|
Result,
|
|
37
37
|
NotFoundError,
|
|
38
|
+
Promotion,
|
|
38
39
|
} from '@reactionary/core';
|
|
39
40
|
import type { CommercetoolsConfiguration } from '../schema/configuration.schema.js';
|
|
40
41
|
import type {
|
|
@@ -49,7 +50,7 @@ import type { CommercetoolsAPI } from '../core/client.js';
|
|
|
49
50
|
export class CommercetoolsCartProvider extends CartProvider {
|
|
50
51
|
protected config: CommercetoolsConfiguration;
|
|
51
52
|
protected commercetools: CommercetoolsAPI;
|
|
52
|
-
|
|
53
|
+
protected expandedCartFields = ['discountCodes[*].discountCode'];
|
|
53
54
|
constructor(
|
|
54
55
|
config: CommercetoolsConfiguration,
|
|
55
56
|
cache: Cache,
|
|
@@ -71,7 +72,12 @@ export class CommercetoolsCartProvider extends CartProvider {
|
|
|
71
72
|
const ctId = payload.cart as CommercetoolsCartIdentifier;
|
|
72
73
|
|
|
73
74
|
try {
|
|
74
|
-
const remote = await client.carts.withId({ ID: ctId.key }).get(
|
|
75
|
+
const remote = await client.carts.withId({ ID: ctId.key }).get(
|
|
76
|
+
{
|
|
77
|
+
queryArgs: {
|
|
78
|
+
expand: this.expandedCartFields
|
|
79
|
+
}
|
|
80
|
+
}).execute();
|
|
75
81
|
|
|
76
82
|
return success(this.parseSingle(remote.body));
|
|
77
83
|
} catch(err) {
|
|
@@ -92,6 +98,8 @@ export class CommercetoolsCartProvider extends CartProvider {
|
|
|
92
98
|
cartIdentifier = await this.createCart();
|
|
93
99
|
}
|
|
94
100
|
|
|
101
|
+
const channelId = await this.commercetools.resolveChannelIdByRole('Primary');
|
|
102
|
+
|
|
95
103
|
const result = await this.applyActions(cartIdentifier, [
|
|
96
104
|
{
|
|
97
105
|
action: 'addLineItem',
|
|
@@ -100,7 +108,7 @@ export class CommercetoolsCartProvider extends CartProvider {
|
|
|
100
108
|
// FIXME: This should be dynamic, probably as part of the context...
|
|
101
109
|
distributionChannel: {
|
|
102
110
|
typeId: 'channel',
|
|
103
|
-
|
|
111
|
+
id: channelId,
|
|
104
112
|
},
|
|
105
113
|
},
|
|
106
114
|
{
|
|
@@ -184,7 +192,6 @@ export class CommercetoolsCartProvider extends CartProvider {
|
|
|
184
192
|
|
|
185
193
|
@Reactionary({
|
|
186
194
|
inputSchema: CartMutationDeleteCartSchema,
|
|
187
|
-
outputSchema: CartSchema,
|
|
188
195
|
})
|
|
189
196
|
public override async deleteCart(
|
|
190
197
|
payload: CartMutationDeleteCart
|
|
@@ -234,11 +241,28 @@ export class CommercetoolsCartProvider extends CartProvider {
|
|
|
234
241
|
public override async removeCouponCode(
|
|
235
242
|
payload: CartMutationRemoveCoupon
|
|
236
243
|
): Promise<Result<Cart>> {
|
|
244
|
+
|
|
245
|
+
const client = await this.getClient();
|
|
246
|
+
const currentCart = await client.carts
|
|
247
|
+
.withId({ ID: payload.cart.key })
|
|
248
|
+
.get({
|
|
249
|
+
queryArgs: {
|
|
250
|
+
expand: this.expandedCartFields
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
.execute();
|
|
254
|
+
|
|
255
|
+
const discountCodeReference = currentCart.body.discountCodes?.find(dc => dc.discountCode.obj?.code === payload.couponCode)?.discountCode;
|
|
256
|
+
|
|
257
|
+
if (!discountCodeReference) {
|
|
258
|
+
// Coupon code is not applied to the cart, so we can just return the cart as is.
|
|
259
|
+
return success(this.parseSingle(currentCart.body));
|
|
260
|
+
}
|
|
237
261
|
const result = await this.applyActions(payload.cart, [
|
|
238
262
|
{
|
|
239
263
|
action: 'removeDiscountCode',
|
|
240
264
|
discountCode: {
|
|
241
|
-
id:
|
|
265
|
+
id: discountCodeReference.id,
|
|
242
266
|
typeId: 'discount-code',
|
|
243
267
|
},
|
|
244
268
|
},
|
|
@@ -319,6 +343,9 @@ export class CommercetoolsCartProvider extends CartProvider {
|
|
|
319
343
|
country: this.context.taxJurisdiction.countryCode || 'US',
|
|
320
344
|
locale: this.context.languageContext.locale,
|
|
321
345
|
},
|
|
346
|
+
queryArgs: {
|
|
347
|
+
expand: this.expandedCartFields
|
|
348
|
+
}
|
|
322
349
|
})
|
|
323
350
|
.execute();
|
|
324
351
|
|
|
@@ -343,6 +370,9 @@ export class CommercetoolsCartProvider extends CartProvider {
|
|
|
343
370
|
version: ctId.version,
|
|
344
371
|
actions,
|
|
345
372
|
},
|
|
373
|
+
queryArgs: {
|
|
374
|
+
expand: this.expandedCartFields
|
|
375
|
+
}
|
|
346
376
|
})
|
|
347
377
|
.execute();
|
|
348
378
|
|
|
@@ -378,7 +408,19 @@ export class CommercetoolsCartProvider extends CartProvider {
|
|
|
378
408
|
protected parseCartItem(remoteItem: LineItem): CartItem {
|
|
379
409
|
const unitPrice = remoteItem.price.value.centAmount;
|
|
380
410
|
const totalPrice = remoteItem.totalPrice.centAmount || 0;
|
|
381
|
-
|
|
411
|
+
let itemDiscount = 0;
|
|
412
|
+
|
|
413
|
+
// look, discounts are weird in commercetools.... i think the .price.discount only applies for embedded prices maybe?
|
|
414
|
+
|
|
415
|
+
if (remoteItem.discountedPricePerQuantity && remoteItem.discountedPricePerQuantity.length > 0) {
|
|
416
|
+
itemDiscount = remoteItem.discountedPricePerQuantity.reduce((sum, discPrQty) => {
|
|
417
|
+
return sum + discPrQty.quantity * discPrQty.discountedPrice?.includedDiscounts?.
|
|
418
|
+
reduce((sum, discount) => sum + discount.discountedAmount.centAmount, 0) || 0
|
|
419
|
+
}, 0);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const totalDiscount = (remoteItem.price.discounted?.value.centAmount || 0) + itemDiscount;
|
|
423
|
+
|
|
382
424
|
const unitDiscount = totalDiscount / remoteItem.quantity;
|
|
383
425
|
const currency =
|
|
384
426
|
remoteItem.price.value.currencyCode.toUpperCase() as Currency;
|
|
@@ -423,12 +465,23 @@ export class CommercetoolsCartProvider extends CartProvider {
|
|
|
423
465
|
version: remote.version || 0,
|
|
424
466
|
} satisfies CommercetoolsCartIdentifier;
|
|
425
467
|
|
|
468
|
+
|
|
469
|
+
const items = new Array<CartItem>();
|
|
470
|
+
for (const remoteItem of remote.lineItems) {
|
|
471
|
+
const item = this.parseCartItem(remoteItem);
|
|
472
|
+
|
|
473
|
+
items.push(item);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
|
|
426
477
|
const grandTotal = remote.totalPrice.centAmount || 0;
|
|
427
478
|
const shippingTotal = remote.shippingInfo?.price.centAmount || 0;
|
|
428
479
|
const productTotal = grandTotal - shippingTotal;
|
|
429
480
|
const taxTotal = remote.taxedPrice?.totalTax?.centAmount || 0;
|
|
481
|
+
|
|
482
|
+
// i think this is missing some elements still?
|
|
430
483
|
const discountTotal =
|
|
431
|
-
remote.discountOnTotalPrice?.discountedAmount.centAmount || 0;
|
|
484
|
+
(remote.discountOnTotalPrice?.discountedAmount.centAmount || 0) + items.reduce((sum, item) => sum + (item.price.totalDiscount.value * 100 || 0), 0);
|
|
432
485
|
const surchargeTotal = 0;
|
|
433
486
|
const currency = remote.totalPrice.currencyCode as Currency;
|
|
434
487
|
|
|
@@ -459,13 +512,24 @@ export class CommercetoolsCartProvider extends CartProvider {
|
|
|
459
512
|
},
|
|
460
513
|
} satisfies CostBreakDown;
|
|
461
514
|
|
|
462
|
-
const items = new Array<CartItem>();
|
|
463
|
-
for (const remoteItem of remote.lineItems) {
|
|
464
|
-
const item = this.parseCartItem(remoteItem);
|
|
465
515
|
|
|
466
|
-
|
|
516
|
+
const localeString = this.context.languageContext.locale || 'en'
|
|
517
|
+
const appliedPromotions = [];
|
|
518
|
+
if (remote.discountCodes) {
|
|
519
|
+
for (const promo of remote.discountCodes) {
|
|
520
|
+
appliedPromotions.push({
|
|
521
|
+
code: promo.discountCode.obj?.code || '',
|
|
522
|
+
isCouponCode: true,
|
|
523
|
+
name: promo.discountCode.obj?.name?.[localeString] || '',
|
|
524
|
+
description: promo.discountCode.obj?.description?.[localeString] || '',
|
|
525
|
+
} satisfies Promotion);
|
|
526
|
+
}
|
|
467
527
|
}
|
|
468
528
|
|
|
529
|
+
// if we want to include the nice name and description of the non-coupon promotions, we have to do some extra work to fetch the referenced promotions and include them here,
|
|
530
|
+
// as the max expand level is 3, and the information is at level 5.
|
|
531
|
+
// For now, we will just include the coupon codes, as that is the most common use case.
|
|
532
|
+
|
|
469
533
|
const cart = {
|
|
470
534
|
identifier,
|
|
471
535
|
userId: {
|
|
@@ -474,6 +538,7 @@ export class CommercetoolsCartProvider extends CartProvider {
|
|
|
474
538
|
name: remote.custom?.fields['name'] || '',
|
|
475
539
|
description: remote.custom?.fields['description'] || '',
|
|
476
540
|
price,
|
|
541
|
+
appliedPromotions,
|
|
477
542
|
items,
|
|
478
543
|
} satisfies Cart;
|
|
479
544
|
|
|
@@ -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
|
|
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:',
|
|
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
|
-
|
|
334
|
-
|
|
368
|
+
|
|
369
|
+
const response = await client.client.fetch<StoreCartResponse>(
|
|
370
|
+
`/store/carts/${medusaId.key}/promotions`,
|
|
335
371
|
{
|
|
336
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
387
|
-
|
|
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
|
-
|
|
408
|
-
const
|
|
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 (
|
|
484
|
+
if (updatedCartResponse.cart) {
|
|
430
485
|
// Update session to use new cart
|
|
431
486
|
this.medusaApi.setSessionData({
|
|
432
|
-
activeCartId:
|
|
487
|
+
activeCartId: updatedCartResponse.cart.id,
|
|
433
488
|
});
|
|
434
489
|
|
|
435
|
-
return success(this.parseSingle(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
},
|