@reactionary/medusa 0.10.54 → 0.10.56
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/capabilities/checkout.capability.js +16 -9
- package/factories/checkout/checkout.factory.js +103 -9
- package/factories/product/product.factory.js +3 -0
- package/package.json +2 -2
- package/src/capabilities/checkout.capability.d.ts +3 -3
- package/src/factories/checkout/checkout.factory.d.ts +49 -5
- package/utils/product-compliance.js +15 -0
|
@@ -61,17 +61,17 @@ class MedusaCheckoutCapability extends CheckoutCapability {
|
|
|
61
61
|
*
|
|
62
62
|
* example: this.includedFields = [includedFields, '+discounts.*'].join(',');
|
|
63
63
|
*/
|
|
64
|
-
includedFields = ["+items.*", "+order.id", "+order.display_id", "+order.custom_display_id"].join(",");
|
|
64
|
+
includedFields = ["+items.*", "+order.id", "+order.display_id", "+order.custom_display_id", "+shipping_methods.data"].join(",");
|
|
65
65
|
/**
|
|
66
66
|
* This controls which fields are requested for the cart's shipping options.
|
|
67
67
|
* You can override this in a subclass to add more fields as needed.
|
|
68
68
|
*
|
|
69
69
|
* The service zone's fulfillment set tells us whether an option is in-store
|
|
70
|
-
* pickup (BOPIS) or a delivery, and the option
|
|
71
|
-
*
|
|
72
|
-
* protocolData.
|
|
70
|
+
* pickup (BOPIS) or a delivery, and the option data is the fulfillment
|
|
71
|
+
* provider's payload (e.g. nShift option/carrier ids) that is mapped to the
|
|
72
|
+
* shipping method's protocolData.
|
|
73
73
|
*/
|
|
74
|
-
shippingOptionFields = ["*service_zone.fulfillment_set", "+
|
|
74
|
+
shippingOptionFields = ["*service_zone.fulfillment_set", "+data"].join(",");
|
|
75
75
|
initiateCheckoutForCartPayload(payload) {
|
|
76
76
|
return {
|
|
77
77
|
billing_address: payload.billingAddress ? this.mapAddressToStoreAddress(payload.billingAddress) : void 0,
|
|
@@ -276,12 +276,19 @@ class MedusaCheckoutCapability extends CheckoutCapability {
|
|
|
276
276
|
throw new Error("Method not implemented.");
|
|
277
277
|
}
|
|
278
278
|
setShippingInstructionPayload(payload) {
|
|
279
|
+
const data = {
|
|
280
|
+
consent_for_unattended_delivery: payload.shippingInstruction.consentForUnattendedDelivery + "",
|
|
281
|
+
instructions: payload.shippingInstruction.instructions || ""
|
|
282
|
+
};
|
|
283
|
+
for (const entry of payload.shippingInstruction.protocolData ?? []) {
|
|
284
|
+
data[entry.key] = entry.value;
|
|
285
|
+
}
|
|
286
|
+
if (payload.shippingInstruction.pickupPoint) {
|
|
287
|
+
data["pickup_point"] = payload.shippingInstruction.pickupPoint;
|
|
288
|
+
}
|
|
279
289
|
return {
|
|
280
290
|
option_id: payload.shippingInstruction.shippingMethod.key,
|
|
281
|
-
data
|
|
282
|
-
consent_for_unattended_delivery: payload.shippingInstruction.consentForUnattendedDelivery + "",
|
|
283
|
-
instructions: payload.shippingInstruction.instructions || ""
|
|
284
|
-
}
|
|
291
|
+
data
|
|
285
292
|
};
|
|
286
293
|
}
|
|
287
294
|
/**
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
import {
|
|
4
4
|
AddressIdentifierSchema,
|
|
5
5
|
MonetaryAmountSchema,
|
|
6
|
+
SHIPPING_PROTOCOL_DATA_KEYS,
|
|
6
7
|
ShippingMethodIdentifierSchema
|
|
7
8
|
} from "@reactionary/core";
|
|
8
9
|
import {
|
|
@@ -121,7 +122,7 @@ class MedusaCheckoutFactory {
|
|
|
121
122
|
instructions = sm.data["instructions"] != null ? String(sm.data["instructions"]) : "";
|
|
122
123
|
consentForUnattendedDelivery = sm.data["consent_for_unattended_delivery"] === "true";
|
|
123
124
|
pickupStore = sm.data["pickup_store"] != null && sm.data["pickup_store"] !== "" ? String(sm.data["pickup_store"]) : void 0;
|
|
124
|
-
protocolData = this.
|
|
125
|
+
protocolData = this.parseInstructionProtocolData(sm.data);
|
|
125
126
|
}
|
|
126
127
|
if (!pickupPoint) {
|
|
127
128
|
pickupPoint = backupPickupPoint || "";
|
|
@@ -209,11 +210,46 @@ class MedusaCheckoutFactory {
|
|
|
209
210
|
currency: data.calculated_price.currency_code?.toUpperCase() || _context.languageContext.currencyCode
|
|
210
211
|
}),
|
|
211
212
|
deliveryTime: "",
|
|
213
|
+
carrier: this.parseShippingMethodCarrier(_context, data),
|
|
212
214
|
fulfillmentType: this.parseFulfillmentType(_context, data),
|
|
213
|
-
protocolData: this.
|
|
215
|
+
protocolData: this.mergeShippingMethodProtocolData(
|
|
216
|
+
this.parseShippingMethodProtocolData(_context, data),
|
|
217
|
+
this.parseShippingMethodStoreReference(_context, data)
|
|
218
|
+
)
|
|
214
219
|
};
|
|
215
220
|
return this.shippingMethodSchema.parse(sm);
|
|
216
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Appends a conventional entry to the provider-derived protocol data,
|
|
224
|
+
* unless the provider data already carries the same key (explicit provider
|
|
225
|
+
* data wins).
|
|
226
|
+
*/
|
|
227
|
+
mergeShippingMethodProtocolData(protocolData, entry) {
|
|
228
|
+
if (!entry) {
|
|
229
|
+
return protocolData;
|
|
230
|
+
}
|
|
231
|
+
if ((protocolData ?? []).some((existing) => existing.key === entry.key)) {
|
|
232
|
+
return protocolData;
|
|
233
|
+
}
|
|
234
|
+
return [...protocolData ?? [], entry];
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Extension point emitting the conventional "pickup_store" protocol data
|
|
238
|
+
* entry for in-store pickup options: the stock location id of the service
|
|
239
|
+
* zone's fulfillment set, which equals the store capability's
|
|
240
|
+
* Store.identifier.key. Delivery options also carry a location (the
|
|
241
|
+
* warehouse), so the entry is emitted for pickup options only.
|
|
242
|
+
*/
|
|
243
|
+
parseShippingMethodStoreReference(_context, data) {
|
|
244
|
+
if (this.parseFulfillmentType(_context, data) !== "pickup") {
|
|
245
|
+
return void 0;
|
|
246
|
+
}
|
|
247
|
+
const locationId = data.service_zone?.fulfillment_set?.location?.id;
|
|
248
|
+
if (!locationId) {
|
|
249
|
+
return void 0;
|
|
250
|
+
}
|
|
251
|
+
return { key: SHIPPING_PROTOCOL_DATA_KEYS.pickupStore, value: locationId };
|
|
252
|
+
}
|
|
217
253
|
/**
|
|
218
254
|
* Extension point to control how a Medusa shipping option maps to the core
|
|
219
255
|
* fulfillment type. A shipping option belonging to a fulfillment set of type
|
|
@@ -223,18 +259,33 @@ class MedusaCheckoutFactory {
|
|
|
223
259
|
return data.service_zone?.fulfillment_set?.type === "pickup" ? "pickup" : "delivery";
|
|
224
260
|
}
|
|
225
261
|
/**
|
|
226
|
-
* Extension point mapping a Medusa shipping option's
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* client. String and JSON-object values are kept as-is, other
|
|
230
|
-
* are stringified, and arrays/null entries are skipped.
|
|
262
|
+
* Extension point mapping a Medusa shipping option's `data` - the payload
|
|
263
|
+
* the fulfillment provider works with (e.g. nShift's option/carrier ids) -
|
|
264
|
+
* to the shipping method's protocolData, so provider-protocol data reaches
|
|
265
|
+
* the client. String and JSON-object values are kept as-is, other
|
|
266
|
+
* primitives are stringified, and arrays/null entries are skipped.
|
|
267
|
+
*
|
|
268
|
+
* Option `metadata` is deliberately NOT mapped: it is merchant free-form
|
|
269
|
+
* annotation, not protocol data. Override this hook to surface it.
|
|
231
270
|
*/
|
|
232
271
|
parseShippingMethodProtocolData(_context, data) {
|
|
233
|
-
if (!data.
|
|
272
|
+
if (!data.data) {
|
|
234
273
|
return void 0;
|
|
235
274
|
}
|
|
275
|
+
const entries = this.convertToProtocolDataEntries(data.data);
|
|
276
|
+
return entries.length > 0 ? entries : void 0;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Converts a Medusa key-value record into protocol data entries: string and
|
|
280
|
+
* JSON-object values are kept as-is, other primitives are stringified, and
|
|
281
|
+
* arrays/null entries are skipped. Keys listed in skipKeys are left out.
|
|
282
|
+
*/
|
|
283
|
+
convertToProtocolDataEntries(record, skipKeys = []) {
|
|
236
284
|
const entries = [];
|
|
237
|
-
for (const [key, value] of Object.entries(
|
|
285
|
+
for (const [key, value] of Object.entries(record)) {
|
|
286
|
+
if (skipKeys.includes(key)) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
238
289
|
if (typeof value === "string") {
|
|
239
290
|
entries.push({ key, value });
|
|
240
291
|
} else if (typeof value === "number" || typeof value === "boolean") {
|
|
@@ -243,6 +294,49 @@ class MedusaCheckoutFactory {
|
|
|
243
294
|
entries.push({ key, value });
|
|
244
295
|
}
|
|
245
296
|
}
|
|
297
|
+
return entries;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Extension point resolving the carrier of a shipping option from its
|
|
301
|
+
* provider data (e.g. nShift's carrier_id), when present.
|
|
302
|
+
*/
|
|
303
|
+
parseShippingMethodCarrier(_context, data) {
|
|
304
|
+
const carrierId = data.data?.["carrier_id"];
|
|
305
|
+
return typeof carrierId === "string" && carrierId.length > 0 ? carrierId : void 0;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* The persisted shipping method data keys that are NOT surfaced as
|
|
309
|
+
* instruction protocol data: keys already represented as first-class
|
|
310
|
+
* instruction fields, the legacy JSON blob, and provider-side values that
|
|
311
|
+
* would mislead (nShift's data.price is not the charged amount).
|
|
312
|
+
*/
|
|
313
|
+
instructionProtocolDataSkipKeys = [
|
|
314
|
+
"pickup_store",
|
|
315
|
+
"pickup_point",
|
|
316
|
+
"instructions",
|
|
317
|
+
"consent_for_unattended_delivery",
|
|
318
|
+
"shipping_protocol_data",
|
|
319
|
+
"price",
|
|
320
|
+
"currency_code"
|
|
321
|
+
];
|
|
322
|
+
/**
|
|
323
|
+
* Builds the shipping instruction's protocolData from the persisted
|
|
324
|
+
* shipping method data: the provider payload enriched by the backend (e.g.
|
|
325
|
+
* nShift's session_id, carrier ids and optional delivery_time) merged with
|
|
326
|
+
* whatever the client sent when setting the instruction. First-class and
|
|
327
|
+
* misleading keys are skipped; the legacy shipping_protocol_data JSON blob
|
|
328
|
+
* still contributes entries whose keys are not already present.
|
|
329
|
+
*/
|
|
330
|
+
parseInstructionProtocolData(smData) {
|
|
331
|
+
const entries = this.convertToProtocolDataEntries(
|
|
332
|
+
smData,
|
|
333
|
+
this.instructionProtocolDataSkipKeys
|
|
334
|
+
);
|
|
335
|
+
for (const legacyEntry of this.parseShippingProtocolData(smData["shipping_protocol_data"]) ?? []) {
|
|
336
|
+
if (!entries.some((entry) => entry.key === legacyEntry.key)) {
|
|
337
|
+
entries.push(legacyEntry);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
246
340
|
return entries.length > 0 ? entries : void 0;
|
|
247
341
|
}
|
|
248
342
|
/**
|
|
@@ -134,7 +134,10 @@ class MedusaProductFactory {
|
|
|
134
134
|
ce_marking: shared.ce_marking,
|
|
135
135
|
weee_symbol: shared.weee_symbol,
|
|
136
136
|
energy_class: shared.energy_class,
|
|
137
|
+
energy_class_range: shared.energy_class_range,
|
|
137
138
|
garan_duration: shared.garan_duration,
|
|
139
|
+
eprel_registration_number: shared.eprel_registration_number,
|
|
140
|
+
eprel_product_group: shared.eprel_product_group,
|
|
138
141
|
safety_warnings: safeStringConvert(body.metadata?.["compliance_data_safety_warnings"]),
|
|
139
142
|
composition: safeStringConvert(body.metadata?.["compliance_data_composition"]),
|
|
140
143
|
additional_disclosures: safeStringConvert(body.metadata?.["compliance_data_additional_disclosures"]),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/medusa",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.56",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@medusajs/js-sdk": "^2.13.3",
|
|
13
|
-
"@reactionary/core": "0.10.
|
|
13
|
+
"@reactionary/core": "0.10.56",
|
|
14
14
|
"debug": "^4.4.3",
|
|
15
15
|
"@medusajs/types": "^2.13.3",
|
|
16
16
|
"zod": "4.1.9"
|
|
@@ -25,9 +25,9 @@ export declare class MedusaCheckoutCapability<TFactory extends CheckoutFactory =
|
|
|
25
25
|
* You can override this in a subclass to add more fields as needed.
|
|
26
26
|
*
|
|
27
27
|
* The service zone's fulfillment set tells us whether an option is in-store
|
|
28
|
-
* pickup (BOPIS) or a delivery, and the option
|
|
29
|
-
*
|
|
30
|
-
* protocolData.
|
|
28
|
+
* pickup (BOPIS) or a delivery, and the option data is the fulfillment
|
|
29
|
+
* provider's payload (e.g. nShift option/carrier ids) that is mapped to the
|
|
30
|
+
* shipping method's protocolData.
|
|
31
31
|
*/
|
|
32
32
|
protected shippingOptionFields: string;
|
|
33
33
|
constructor(config: MedusaConfiguration, cache: Cache, context: RequestContext, medusaApi: MedusaAPI, factory: CheckoutFactoryWithOutput<TFactory>);
|
|
@@ -45,6 +45,20 @@ export declare class MedusaCheckoutFactory<TCheckoutSchema extends AnyCheckoutSc
|
|
|
45
45
|
protected isCart(data: StoreCart): data is StoreCart;
|
|
46
46
|
parseCheckout(context: RequestContext, data: StoreCart): z.output<TCheckoutSchema>;
|
|
47
47
|
parseShippingMethod(_context: RequestContext, data: MedusaShippingOptionWithMetadata): z.output<TShippingMethodSchema>;
|
|
48
|
+
/**
|
|
49
|
+
* Appends a conventional entry to the provider-derived protocol data,
|
|
50
|
+
* unless the provider data already carries the same key (explicit provider
|
|
51
|
+
* data wins).
|
|
52
|
+
*/
|
|
53
|
+
protected mergeShippingMethodProtocolData(protocolData: ShippingProtocolData[] | undefined, entry: ShippingProtocolData | undefined): ShippingProtocolData[] | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Extension point emitting the conventional "pickup_store" protocol data
|
|
56
|
+
* entry for in-store pickup options: the stock location id of the service
|
|
57
|
+
* zone's fulfillment set, which equals the store capability's
|
|
58
|
+
* Store.identifier.key. Delivery options also carry a location (the
|
|
59
|
+
* warehouse), so the entry is emitted for pickup options only.
|
|
60
|
+
*/
|
|
61
|
+
protected parseShippingMethodStoreReference(_context: RequestContext, data: MedusaShippingOptionWithMetadata): ShippingProtocolData | undefined;
|
|
48
62
|
/**
|
|
49
63
|
* Extension point to control how a Medusa shipping option maps to the core
|
|
50
64
|
* fulfillment type. A shipping option belonging to a fulfillment set of type
|
|
@@ -52,13 +66,43 @@ export declare class MedusaCheckoutFactory<TCheckoutSchema extends AnyCheckoutSc
|
|
|
52
66
|
*/
|
|
53
67
|
protected parseFulfillmentType(_context: RequestContext, data: MedusaShippingOptionWithMetadata): ShippingFulfillmentType;
|
|
54
68
|
/**
|
|
55
|
-
* Extension point mapping a Medusa shipping option's
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* client. String and JSON-object values are kept as-is, other
|
|
59
|
-
* are stringified, and arrays/null entries are skipped.
|
|
69
|
+
* Extension point mapping a Medusa shipping option's `data` - the payload
|
|
70
|
+
* the fulfillment provider works with (e.g. nShift's option/carrier ids) -
|
|
71
|
+
* to the shipping method's protocolData, so provider-protocol data reaches
|
|
72
|
+
* the client. String and JSON-object values are kept as-is, other
|
|
73
|
+
* primitives are stringified, and arrays/null entries are skipped.
|
|
74
|
+
*
|
|
75
|
+
* Option `metadata` is deliberately NOT mapped: it is merchant free-form
|
|
76
|
+
* annotation, not protocol data. Override this hook to surface it.
|
|
60
77
|
*/
|
|
61
78
|
protected parseShippingMethodProtocolData(_context: RequestContext, data: MedusaShippingOptionWithMetadata): ShippingProtocolData[] | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Converts a Medusa key-value record into protocol data entries: string and
|
|
81
|
+
* JSON-object values are kept as-is, other primitives are stringified, and
|
|
82
|
+
* arrays/null entries are skipped. Keys listed in skipKeys are left out.
|
|
83
|
+
*/
|
|
84
|
+
protected convertToProtocolDataEntries(record: Record<string, unknown>, skipKeys?: readonly string[]): ShippingProtocolData[];
|
|
85
|
+
/**
|
|
86
|
+
* Extension point resolving the carrier of a shipping option from its
|
|
87
|
+
* provider data (e.g. nShift's carrier_id), when present.
|
|
88
|
+
*/
|
|
89
|
+
protected parseShippingMethodCarrier(_context: RequestContext, data: MedusaShippingOptionWithMetadata): string | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* The persisted shipping method data keys that are NOT surfaced as
|
|
92
|
+
* instruction protocol data: keys already represented as first-class
|
|
93
|
+
* instruction fields, the legacy JSON blob, and provider-side values that
|
|
94
|
+
* would mislead (nShift's data.price is not the charged amount).
|
|
95
|
+
*/
|
|
96
|
+
protected instructionProtocolDataSkipKeys: readonly string[];
|
|
97
|
+
/**
|
|
98
|
+
* Builds the shipping instruction's protocolData from the persisted
|
|
99
|
+
* shipping method data: the provider payload enriched by the backend (e.g.
|
|
100
|
+
* nShift's session_id, carrier ids and optional delivery_time) merged with
|
|
101
|
+
* whatever the client sent when setting the instruction. First-class and
|
|
102
|
+
* misleading keys are skipped; the legacy shipping_protocol_data JSON blob
|
|
103
|
+
* still contributes entries whose keys are not already present.
|
|
104
|
+
*/
|
|
105
|
+
protected parseInstructionProtocolData(smData: Record<string, unknown>): ShippingProtocolData[] | undefined;
|
|
62
106
|
/**
|
|
63
107
|
* Parses shipping protocol data persisted as a JSON object string (keys
|
|
64
108
|
* mapping to string or JSON-object values) back into protocol data entries.
|
|
@@ -2,6 +2,18 @@ import {
|
|
|
2
2
|
ProductSearchResultItemComplianceDataSchema
|
|
3
3
|
} from "@reactionary/core";
|
|
4
4
|
import { safeBoolConvert, safeStringConvert } from "./medusa-helpers.js";
|
|
5
|
+
function parseEnergyClassRange(value) {
|
|
6
|
+
const text = safeStringConvert(value)?.trim();
|
|
7
|
+
return text && /^[A-Za-z0-9+\-–_ ]{1,20}$/.test(text) ? text : void 0;
|
|
8
|
+
}
|
|
9
|
+
function parseEprelRegistrationNumber(value) {
|
|
10
|
+
const text = safeStringConvert(value)?.trim();
|
|
11
|
+
return text && /^\d{1,19}$/.test(text) ? text : void 0;
|
|
12
|
+
}
|
|
13
|
+
function parseEprelProductGroup(value) {
|
|
14
|
+
const text = safeStringConvert(value)?.trim().toLowerCase();
|
|
15
|
+
return text && /^[a-z0-9]{1,64}$/.test(text) ? text : void 0;
|
|
16
|
+
}
|
|
5
17
|
function parseProductSearchComplianceData(product) {
|
|
6
18
|
if (!product.metadata) {
|
|
7
19
|
return ProductSearchResultItemComplianceDataSchema.parse({});
|
|
@@ -12,7 +24,10 @@ function parseProductSearchComplianceData(product) {
|
|
|
12
24
|
ce_marking: safeBoolConvert(metadata["compliance_data_ce_marking"]),
|
|
13
25
|
weee_symbol: safeBoolConvert(metadata["compliance_data_weee_symbol"]),
|
|
14
26
|
energy_class: safeStringConvert(metadata["compliance_data_energy_class"]),
|
|
27
|
+
energy_class_range: parseEnergyClassRange(metadata["compliance_data_energy_class_range"]),
|
|
15
28
|
garan_duration: duration === void 0 || !Number.isFinite(Number(duration)) ? void 0 : Number(duration),
|
|
29
|
+
eprel_registration_number: parseEprelRegistrationNumber(metadata["compliance_data_eprel_registration_number"]),
|
|
30
|
+
eprel_product_group: parseEprelProductGroup(metadata["compliance_data_eprel_product_group"]),
|
|
16
31
|
is_organic: safeBoolConvert(metadata["compliance_data_organic_is_organic"]),
|
|
17
32
|
organic_certification_type: safeStringConvert(metadata["compliance_data_organic_certification_type"])
|
|
18
33
|
});
|