@reactionary/medusa 0.10.53 → 0.10.54

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.
@@ -30,6 +30,7 @@ import {
30
30
  } from "@reactionary/core";
31
31
  import createDebug from "debug";
32
32
  import * as z from "zod";
33
+ import { MedusaAdminAPI } from "../core/client.js";
33
34
  import {
34
35
  } from "../schema/medusa.schema.js";
35
36
  import { handleProviderError, isNotFoundFetchError } from "../utils/medusa-helpers.js";
@@ -61,6 +62,16 @@ class MedusaCheckoutCapability extends CheckoutCapability {
61
62
  * example: this.includedFields = [includedFields, '+discounts.*'].join(',');
62
63
  */
63
64
  includedFields = ["+items.*", "+order.id", "+order.display_id", "+order.custom_display_id"].join(",");
65
+ /**
66
+ * This controls which fields are requested for the cart's shipping options.
67
+ * You can override this in a subclass to add more fields as needed.
68
+ *
69
+ * The service zone's fulfillment set tells us whether an option is in-store
70
+ * pickup (BOPIS) or a delivery, and the option metadata lets medusa-side
71
+ * providers push additional data that is mapped to the shipping method's
72
+ * protocolData.
73
+ */
74
+ shippingOptionFields = ["*service_zone.fulfillment_set", "+metadata"].join(",");
64
75
  initiateCheckoutForCartPayload(payload) {
65
76
  return {
66
77
  billing_address: payload.billingAddress ? this.mapAddressToStoreAddress(payload.billingAddress) : void 0,
@@ -93,7 +104,9 @@ class MedusaCheckoutCapability extends CheckoutCapability {
93
104
  const response = await client.store.cart.retrieve(payload.identifier.key, {
94
105
  fields: this.includedFields
95
106
  });
96
- return success(this.factory.parseCheckout(this.context, response.cart));
107
+ return success(
108
+ this.hydrateResultingOrder(this.factory.parseCheckout(this.context, response.cart))
109
+ );
97
110
  } catch (err) {
98
111
  if (isNotFoundFetchError(err)) {
99
112
  return error({
@@ -122,7 +135,8 @@ class MedusaCheckoutCapability extends CheckoutCapability {
122
135
  }
123
136
  getAvailableShippingMethodsPayload(payload) {
124
137
  return {
125
- cart_id: payload.checkout.key
138
+ cart_id: payload.checkout.key,
139
+ fields: this.shippingOptionFields
126
140
  };
127
141
  }
128
142
  /**
@@ -270,6 +284,50 @@ class MedusaCheckoutCapability extends CheckoutCapability {
270
284
  }
271
285
  };
272
286
  }
287
+ /**
288
+ * Serializes the client-set shipping protocol data as a JSON object string
289
+ * for persistence in the cart metadata, or undefined when none is set.
290
+ */
291
+ serializeShippingProtocolData(protocolData) {
292
+ if (!protocolData || protocolData.length === 0) {
293
+ return void 0;
294
+ }
295
+ return JSON.stringify(
296
+ Object.fromEntries(protocolData.map((entry) => [entry.key, entry.value]))
297
+ );
298
+ }
299
+ /**
300
+ * Resolves the shipping address to use for in-store pickup (BOPIS): the
301
+ * address of the selected store, so the order ships to the store. Returns
302
+ * undefined when the store or its address is unknown, leaving the cart's
303
+ * shipping address untouched.
304
+ */
305
+ async resolveStorePickupAddress(store) {
306
+ try {
307
+ const adminClient = await new MedusaAdminAPI(this.config, this.context).getClient();
308
+ const response = await adminClient.admin.stockLocation.retrieve(store.key, {
309
+ fields: "*address"
310
+ });
311
+ const address = response.stock_location?.address;
312
+ if (!address) {
313
+ return void 0;
314
+ }
315
+ return {
316
+ firstName: "",
317
+ lastName: response.stock_location.name,
318
+ streetAddress: address.address_1 || "",
319
+ streetNumber: address.address_2 || "",
320
+ city: address.city || "",
321
+ postalCode: address.postal_code || "",
322
+ countryCode: address.country_code || ""
323
+ };
324
+ } catch (err) {
325
+ if (debug.enabled) {
326
+ debug(`Unable to resolve address for pickup store ${store.key}`, err);
327
+ }
328
+ return void 0;
329
+ }
330
+ }
273
331
  async setShippingInstruction(payload) {
274
332
  const client = await this.medusaApi.getClient();
275
333
  const medusaId = payload.checkout;
@@ -277,13 +335,22 @@ class MedusaCheckoutCapability extends CheckoutCapability {
277
335
  if (payload.shippingInstruction.shippingMethod) {
278
336
  await client.store.cart.addShippingMethod(medusaId.key, this.setShippingInstructionPayload(payload));
279
337
  }
280
- await client.store.cart.update(medusaId.key, {
338
+ const cartUpdate = {
281
339
  metadata: {
282
340
  consent_for_unattended_delivery: payload.shippingInstruction.consentForUnattendedDelivery + "",
283
341
  instructions: payload.shippingInstruction.instructions || "",
284
- pickup_point: payload.shippingInstruction.pickupPoint || ""
342
+ pickup_point: payload.shippingInstruction.pickupPoint || "",
343
+ pickup_store: payload.shippingInstruction.store?.key || "",
344
+ shipping_protocol_data: this.serializeShippingProtocolData(payload.shippingInstruction.protocolData) || ""
285
345
  }
286
- });
346
+ };
347
+ if (payload.shippingInstruction.store?.key) {
348
+ const storeAddress = await this.resolveStorePickupAddress(payload.shippingInstruction.store);
349
+ if (storeAddress) {
350
+ cartUpdate.shipping_address = this.mapAddressToStoreAddress(storeAddress);
351
+ }
352
+ }
353
+ await client.store.cart.update(medusaId.key, cartUpdate);
287
354
  const response = await client.store.cart.retrieve(medusaId.key, {
288
355
  fields: this.includedFields
289
356
  });
@@ -306,14 +373,24 @@ class MedusaCheckoutCapability extends CheckoutCapability {
306
373
  if (!checkout.value.resultingOrder) {
307
374
  const client = await this.medusaApi.getClient();
308
375
  const medusaId = payload.checkout;
376
+ await client.store.cart.update(medusaId.key, {
377
+ metadata: {
378
+ cart_id: medusaId.key
379
+ }
380
+ });
309
381
  const response = await client.store.cart.complete(medusaId.key);
310
382
  if (response.type === "order") {
383
+ const resultingOrder = this.parseCompletedOrderIdentifier(response.order);
384
+ this.rememberFinalizedOrder(medusaId.key, resultingOrder);
311
385
  checkout = await this.getById({
312
386
  identifier: payload.checkout
313
387
  });
314
388
  if (!checkout.success) {
315
389
  throw new Error(`Unable to reload checkout ${payload.checkout.key} after completion`);
316
390
  }
391
+ if (!checkout.value.resultingOrder) {
392
+ checkout = success({ ...checkout.value, resultingOrder });
393
+ }
317
394
  } else {
318
395
  throw new Error(`Failed to finalize checkout ${payload.checkout.key}. Expected order, got ${response.type}`);
319
396
  }
@@ -333,6 +410,48 @@ class MedusaCheckoutCapability extends CheckoutCapability {
333
410
  }
334
411
  return success(checkout.value);
335
412
  }
413
+ /**
414
+ * Builds the order identifier from a completed-cart response's order,
415
+ * following the same display-id precedence as the checkout factory
416
+ * (custom_display_id over display_id).
417
+ */
418
+ parseCompletedOrderIdentifier(order) {
419
+ const rawDisplayId = order.custom_display_id ?? order.display_id;
420
+ const displayId = rawDisplayId != null ? Number(`${rawDisplayId}`) : void 0;
421
+ return {
422
+ key: order.id,
423
+ display_id: displayId != null && Number.isFinite(displayId) ? displayId : void 0
424
+ };
425
+ }
426
+ /**
427
+ * Remembers which order a finalized cart produced, so later checkout reads
428
+ * in the same session can report the resulting order even when the Medusa
429
+ * store cart endpoint does not expose the cart->order relation.
430
+ */
431
+ rememberFinalizedOrder(cartKey, order) {
432
+ const sessionData = this.medusaApi.getSessionData();
433
+ this.medusaApi.setSessionData({
434
+ finalizedOrders: {
435
+ ...sessionData.finalizedOrders || {},
436
+ [cartKey]: order
437
+ }
438
+ });
439
+ }
440
+ /**
441
+ * Fills in a missing resultingOrder from the session's finalized-orders map.
442
+ * Checkouts that already carry a resulting order (older Medusa versions
443
+ * still hydrate the +order.* fields) are returned unchanged.
444
+ */
445
+ hydrateResultingOrder(checkout) {
446
+ if (checkout.resultingOrder) {
447
+ return checkout;
448
+ }
449
+ const remembered = this.medusaApi.getSessionData().finalizedOrders?.[checkout.identifier.key];
450
+ if (!remembered) {
451
+ return checkout;
452
+ }
453
+ return { ...checkout, resultingOrder: remembered };
454
+ }
336
455
  /**
337
456
  * Extension point to map an Address to a Store Address
338
457
  * @param address
@@ -0,0 +1,166 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ import {
12
+ Reactionary,
13
+ StoreCapability,
14
+ StorePaginatedResultSchema,
15
+ StoreQueryByIdSchema,
16
+ StoreQueryByProximitySchema,
17
+ StoreQueryForListSchema,
18
+ StoreSchema,
19
+ error,
20
+ success
21
+ } from "@reactionary/core";
22
+ import createDebug from "debug";
23
+ import * as z from "zod";
24
+ import { MedusaAdminAPI } from "../core/client.js";
25
+ import { haversineDistanceKm } from "../utils/geo.js";
26
+ const debug = createDebug("reactionary:medusa:store");
27
+ class MedusaStoreCapability extends StoreCapability {
28
+ constructor(config, cache, context, medusaApi, factory) {
29
+ super(cache, context);
30
+ this.medusaApi = medusaApi;
31
+ this.config = config;
32
+ this.factory = factory;
33
+ }
34
+ medusaApi;
35
+ config;
36
+ factory;
37
+ /**
38
+ * The fields requested for stock locations. Override in a subclass if a
39
+ * project needs more relations.
40
+ */
41
+ stockLocationFields = "*address,*fulfillment_sets";
42
+ /**
43
+ * Whether a stock location is a store customers can pick up orders from,
44
+ * i.e. it has a fulfillment set of type "pickup".
45
+ */
46
+ isPickupLocation(location) {
47
+ return (location.fulfillment_sets || []).some((set) => set.type === "pickup");
48
+ }
49
+ /**
50
+ * Fetches all stock locations that are pickup stores. Medusa installations
51
+ * have a small, bounded number of stock locations, so fetching them all and
52
+ * filtering client-side is acceptable.
53
+ */
54
+ async fetchPickupLocations() {
55
+ const adminClient = await new MedusaAdminAPI(this.config, this.context).getClient();
56
+ const pageSize = 200;
57
+ let offset = 0;
58
+ const locations = [];
59
+ for (; ; ) {
60
+ const response = await adminClient.admin.stockLocation.list({
61
+ fields: this.stockLocationFields,
62
+ limit: pageSize,
63
+ offset
64
+ });
65
+ locations.push(...response.stock_locations || []);
66
+ offset += pageSize;
67
+ if (offset >= response.count || (response.stock_locations || []).length === 0) {
68
+ break;
69
+ }
70
+ }
71
+ return locations.filter((location) => this.isPickupLocation(location));
72
+ }
73
+ async list(payload) {
74
+ const pickupLocations = await this.fetchPickupLocations();
75
+ if (debug.enabled) {
76
+ debug(`Found ${pickupLocations.length} pickup stock locations`);
77
+ }
78
+ const { pageNumber, pageSize } = payload.paginationOptions;
79
+ const startIndex = (pageNumber - 1) * pageSize;
80
+ const pagedLocations = pickupLocations.slice(startIndex, startIndex + pageSize);
81
+ return success({
82
+ pageNumber,
83
+ pageSize,
84
+ totalCount: pickupLocations.length,
85
+ totalPages: Math.ceil(pickupLocations.length / pageSize),
86
+ items: pagedLocations.map(
87
+ (location) => this.factory.parseStore(this.context, location)
88
+ )
89
+ });
90
+ }
91
+ async getById(payload) {
92
+ const adminClient = await new MedusaAdminAPI(this.config, this.context).getClient();
93
+ try {
94
+ const response = await adminClient.admin.stockLocation.retrieve(payload.identifier.key, {
95
+ fields: this.stockLocationFields
96
+ });
97
+ if (!response.stock_location || !this.isPickupLocation(response.stock_location)) {
98
+ return error({ type: "NotFound", identifier: payload.identifier });
99
+ }
100
+ return success(this.factory.parseStore(this.context, response.stock_location));
101
+ } catch (err) {
102
+ if (debug.enabled) {
103
+ debug(`Error fetching stock location ${payload.identifier.key}`, err);
104
+ }
105
+ return error({ type: "NotFound", identifier: payload.identifier });
106
+ }
107
+ }
108
+ /**
109
+ * Extension point resolving a stock location's geo coordinates. Medusa
110
+ * stock locations carry no first-class coordinates, so by default they are
111
+ * read from the location metadata keys `latitude` and `longitude`. Returns
112
+ * undefined when the location has no usable coordinates.
113
+ */
114
+ parseLocationCoordinates(location) {
115
+ const latitude = Number(location.metadata?.["latitude"]);
116
+ const longitude = Number(location.metadata?.["longitude"]);
117
+ if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
118
+ return void 0;
119
+ }
120
+ return { latitude, longitude };
121
+ }
122
+ async queryByProximity(payload) {
123
+ const pickupLocations = await this.fetchPickupLocations();
124
+ const withinDistance = [];
125
+ for (const location of pickupLocations) {
126
+ const coordinates = this.parseLocationCoordinates(location);
127
+ if (!coordinates) {
128
+ continue;
129
+ }
130
+ const distance = haversineDistanceKm(
131
+ payload.latitude,
132
+ payload.longitude,
133
+ coordinates.latitude,
134
+ coordinates.longitude
135
+ );
136
+ if (distance <= payload.distance) {
137
+ withinDistance.push({ location, distance });
138
+ }
139
+ }
140
+ withinDistance.sort((a, b) => a.distance - b.distance);
141
+ return success(
142
+ withinDistance.slice(0, payload.limit).map((entry) => this.factory.parseStore(this.context, entry.location))
143
+ );
144
+ }
145
+ }
146
+ __decorateClass([
147
+ Reactionary({
148
+ inputSchema: StoreQueryForListSchema,
149
+ outputSchema: StorePaginatedResultSchema
150
+ })
151
+ ], MedusaStoreCapability.prototype, "list", 1);
152
+ __decorateClass([
153
+ Reactionary({
154
+ inputSchema: StoreQueryByIdSchema,
155
+ outputSchema: StoreSchema
156
+ })
157
+ ], MedusaStoreCapability.prototype, "getById", 1);
158
+ __decorateClass([
159
+ Reactionary({
160
+ inputSchema: StoreQueryByProximitySchema,
161
+ outputSchema: z.array(StoreSchema)
162
+ })
163
+ ], MedusaStoreCapability.prototype, "queryByProximity", 1);
164
+ export {
165
+ MedusaStoreCapability
166
+ };
@@ -23,7 +23,8 @@ import {
23
23
  ProductSearchResultSchema,
24
24
  ProfileSchema,
25
25
  PersonalizationProfileSchema,
26
- ShippingMethodSchema
26
+ ShippingMethodSchema,
27
+ StoreSchema
27
28
  } from "@reactionary/core";
28
29
  import { MedusaCartCapability } from "../capabilities/cart.capability.js";
29
30
  import { MedusaCategoryCapability } from "../capabilities/category.capability.js";
@@ -43,6 +44,7 @@ import { MedusaProductCapability } from "../capabilities/product.capability.js";
43
44
  import { MedusaProductAssociationsCapability } from "../capabilities/product-associations.capability.js";
44
45
  import { MedusaProfileCapability } from "../capabilities/profile.capability.js";
45
46
  import { MedusaPersonalizationProfileCapability } from "../capabilities/personalization-profile.capability.js";
47
+ import { MedusaStoreCapability } from "../capabilities/store.capability.js";
46
48
  import {
47
49
  MedusaCapabilitiesSchema
48
50
  } from "../schema/capabilities.schema.js";
@@ -66,7 +68,8 @@ import {
66
68
  MedusaProductFactory,
67
69
  MedusaProductSearchFactory,
68
70
  MedusaProfileFactory,
69
- MedusaPersonalizationProfileFactory
71
+ MedusaPersonalizationProfileFactory,
72
+ MedusaStoreFactory
70
73
  } from "../factories/index.js";
71
74
  import {
72
75
  resolveCapabilityWithFactory,
@@ -374,6 +377,22 @@ function withMedusaCapabilities(configuration, capabilities) {
374
377
  buildCapabilityArgs
375
378
  );
376
379
  }
380
+ if (caps.store?.enabled) {
381
+ client.store = resolveCapabilityWithFactory(
382
+ capabilities.store,
383
+ {
384
+ factory: new MedusaStoreFactory(StoreSchema),
385
+ capability: (args) => new MedusaStoreCapability(
386
+ args.config,
387
+ args.cache,
388
+ args.context,
389
+ args.medusaApi,
390
+ args.factory
391
+ )
392
+ },
393
+ buildCapabilityArgs
394
+ );
395
+ }
377
396
  if (caps.personalizationProfile?.enabled) {
378
397
  client.personalizationProfile = resolveCapabilityWithFactory(
379
398
  capabilities.personalizationProfile,
@@ -105,15 +105,23 @@ class MedusaCheckoutFactory {
105
105
  const backupUnattendedDelivery = data.metadata?.["consent_for_unattended_delivery"] !== void 0 ? data.metadata?.["consent_for_unattended_delivery"] === "true" : void 0;
106
106
  const backupInstructions = data.metadata?.["instructions"] != null ? String(data.metadata["instructions"]) : void 0;
107
107
  const backupPickupPoint = data.metadata?.["pickup_point"] != null ? String(data.metadata["pickup_point"]) : void 0;
108
+ const backupPickupStore = data.metadata?.["pickup_store"] != null && data.metadata["pickup_store"] !== "" ? String(data.metadata["pickup_store"]) : void 0;
109
+ const backupShippingProtocolData = this.parseShippingProtocolData(
110
+ data.metadata?.["shipping_protocol_data"]
111
+ );
108
112
  let shippingInstruction;
109
113
  data.shipping_methods?.forEach((sm) => {
110
114
  let pickupPoint = "";
111
115
  let instructions = "";
112
116
  let consentForUnattendedDelivery = false;
117
+ let pickupStore;
118
+ let protocolData;
113
119
  if (sm.data) {
114
120
  pickupPoint = sm.data["pickup_point"] != null ? String(sm.data["pickup_point"]) : "";
115
121
  instructions = sm.data["instructions"] != null ? String(sm.data["instructions"]) : "";
116
122
  consentForUnattendedDelivery = sm.data["consent_for_unattended_delivery"] === "true";
123
+ pickupStore = sm.data["pickup_store"] != null && sm.data["pickup_store"] !== "" ? String(sm.data["pickup_store"]) : void 0;
124
+ protocolData = this.parseShippingProtocolData(sm.data["shipping_protocol_data"]);
117
125
  }
118
126
  if (!pickupPoint) {
119
127
  pickupPoint = backupPickupPoint || "";
@@ -124,11 +132,19 @@ class MedusaCheckoutFactory {
124
132
  if (!consentForUnattendedDelivery) {
125
133
  consentForUnattendedDelivery = backupUnattendedDelivery || false;
126
134
  }
135
+ if (!pickupStore) {
136
+ pickupStore = backupPickupStore;
137
+ }
138
+ if (!protocolData) {
139
+ protocolData = backupShippingProtocolData;
140
+ }
127
141
  shippingInstruction = {
128
- shippingMethod: { key: sm.shipping_option_id },
142
+ shippingMethod: { key: sm.shipping_option_id || "" },
129
143
  consentForUnattendedDelivery,
130
144
  instructions,
131
- pickupPoint
145
+ pickupPoint,
146
+ store: pickupStore ? { key: pickupStore } : void 0,
147
+ protocolData
132
148
  };
133
149
  });
134
150
  const paymentInstructions = new Array();
@@ -192,10 +208,71 @@ class MedusaCheckoutFactory {
192
208
  value: data.calculated_price.calculated_amount || 0,
193
209
  currency: data.calculated_price.currency_code?.toUpperCase() || _context.languageContext.currencyCode
194
210
  }),
195
- deliveryTime: ""
211
+ deliveryTime: "",
212
+ fulfillmentType: this.parseFulfillmentType(_context, data),
213
+ protocolData: this.parseShippingMethodProtocolData(_context, data)
196
214
  };
197
215
  return this.shippingMethodSchema.parse(sm);
198
216
  }
217
+ /**
218
+ * Extension point to control how a Medusa shipping option maps to the core
219
+ * fulfillment type. A shipping option belonging to a fulfillment set of type
220
+ * "pickup" is in-store pickup (BOPIS); everything else is a delivery.
221
+ */
222
+ parseFulfillmentType(_context, data) {
223
+ return data.service_zone?.fulfillment_set?.type === "pickup" ? "pickup" : "delivery";
224
+ }
225
+ /**
226
+ * Extension point mapping a Medusa shipping option's metadata to the
227
+ * shipping method's protocolData, so medusa-side providers can push
228
+ * additional data (e.g. available pickup time slots) that reaches the
229
+ * client. String and JSON-object values are kept as-is, other primitives
230
+ * are stringified, and arrays/null entries are skipped.
231
+ */
232
+ parseShippingMethodProtocolData(_context, data) {
233
+ if (!data.metadata) {
234
+ return void 0;
235
+ }
236
+ const entries = [];
237
+ for (const [key, value] of Object.entries(data.metadata)) {
238
+ if (typeof value === "string") {
239
+ entries.push({ key, value });
240
+ } else if (typeof value === "number" || typeof value === "boolean") {
241
+ entries.push({ key, value: String(value) });
242
+ } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
243
+ entries.push({ key, value });
244
+ }
245
+ }
246
+ return entries.length > 0 ? entries : void 0;
247
+ }
248
+ /**
249
+ * Parses shipping protocol data persisted as a JSON object string (keys
250
+ * mapping to string or JSON-object values) back into protocol data entries.
251
+ * Returns undefined when nothing (valid) is stored, keeping the field absent
252
+ * for carts that never set it.
253
+ */
254
+ parseShippingProtocolData(raw) {
255
+ if (typeof raw !== "string" || raw.length === 0) {
256
+ return void 0;
257
+ }
258
+ try {
259
+ const parsed = JSON.parse(raw);
260
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
261
+ return void 0;
262
+ }
263
+ const entries = [];
264
+ for (const [key, value] of Object.entries(parsed)) {
265
+ if (typeof value === "string") {
266
+ entries.push({ key, value });
267
+ } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
268
+ entries.push({ key, value });
269
+ }
270
+ }
271
+ return entries.length > 0 ? entries : void 0;
272
+ } catch {
273
+ return void 0;
274
+ }
275
+ }
199
276
  parsePaymentInstruction(_context, remotePayment, checkout) {
200
277
  const mainSession = (remotePayment.payment_sessions || []).find(
201
278
  (session) => session.status !== "canceled" && session.status !== "error"
@@ -15,3 +15,4 @@ export * from "./product-associations/product-associations.factory.js";
15
15
  export * from "./product-search/product-search.factory.js";
16
16
  export * from "./profile/profile.factory.js";
17
17
  export * from "./personalization-profile/personalization-profile.factory.js";
18
+ export * from "./store/store.factory.js";
@@ -0,0 +1,49 @@
1
+ import { AddressIdentifierSchema } from "@reactionary/core";
2
+ class MedusaStoreFactory {
3
+ storeSchema;
4
+ constructor(storeSchema) {
5
+ this.storeSchema = storeSchema;
6
+ }
7
+ /**
8
+ * Extension point to control the parsing of the stock location address
9
+ * into the store's address.
10
+ */
11
+ parseAddress(_context, data) {
12
+ if (!data.address) {
13
+ return void 0;
14
+ }
15
+ return {
16
+ identifier: AddressIdentifierSchema.parse({
17
+ nickName: data.address.id
18
+ }),
19
+ firstName: "",
20
+ lastName: data.address.company || data.name,
21
+ streetAddress: data.address.address_1 || "",
22
+ streetNumber: data.address.address_2 || "",
23
+ city: data.address.city || "",
24
+ region: data.address.province || "",
25
+ postalCode: data.address.postal_code || "",
26
+ countryCode: data.address.country_code || ""
27
+ };
28
+ }
29
+ parseStore(context, data) {
30
+ const identifier = {
31
+ key: data.id
32
+ };
33
+ const fulfillmentCenter = {
34
+ key: data.name
35
+ };
36
+ const openingHours = data.metadata?.["opening_hours"] != null ? String(data.metadata["opening_hours"]) : void 0;
37
+ const result = {
38
+ identifier,
39
+ name: data.name,
40
+ fulfillmentCenter,
41
+ address: this.parseAddress(context, data),
42
+ openingHours
43
+ };
44
+ return this.storeSchema.parse(result);
45
+ }
46
+ }
47
+ export {
48
+ MedusaStoreFactory
49
+ };
package/index.js CHANGED
@@ -23,3 +23,4 @@ export * from "./capabilities/category.capability.js";
23
23
  export * from "./capabilities/checkout.capability.js";
24
24
  export * from "./capabilities/product.capability.js";
25
25
  export * from "./capabilities/personalization-profile.capability.js";
26
+ export * from "./capabilities/store.capability.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reactionary/medusa",
3
- "version": "0.10.53",
3
+ "version": "0.10.54",
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.53",
13
+ "@reactionary/core": "0.10.54",
14
14
  "debug": "^4.4.3",
15
15
  "@medusajs/types": "^2.13.3",
16
16
  "zod": "4.1.9"
@@ -24,7 +24,8 @@ const MedusaCapabilitiesSchema = CapabilitiesSchema.pick({
24
24
  profile: true,
25
25
  productAssociations: true,
26
26
  personalizationProfile: true,
27
- company: true
27
+ company: true,
28
+ store: true
28
29
  }).extend({
29
30
  product: OverridableCapabilitySchema.optional(),
30
31
  productSearch: OverridableCapabilitySchema.optional(),
@@ -43,7 +44,8 @@ const MedusaCapabilitiesSchema = CapabilitiesSchema.pick({
43
44
  profile: OverridableCapabilitySchema.optional(),
44
45
  productAssociations: OverridableCapabilitySchema.optional(),
45
46
  productRecommendations: DirectCapabilitySchema.optional(),
46
- personalizationProfile: OverridableCapabilitySchema.optional()
47
+ personalizationProfile: OverridableCapabilitySchema.optional(),
48
+ store: OverridableCapabilitySchema.optional()
47
49
  }).partial();
48
50
  export {
49
51
  MedusaCapabilitiesSchema
@@ -20,7 +20,8 @@ const MedusaSessionSchema = z.looseObject({
20
20
  token: z.string().optional(),
21
21
  expires: z.string().optional(),
22
22
  allRegions: z.array(MedusaRegionSchema).optional(),
23
- selectedRegion: MedusaRegionSchema.optional()
23
+ selectedRegion: MedusaRegionSchema.optional(),
24
+ finalizedOrders: z.record(z.string(), MedusaOrderIdentifierSchema).optional().meta({ description: "Orders created by finalizing a checkout in this session, keyed by the originating cart id. Lets checkout reads report the resulting order when the Medusa store cart endpoint does not expose the cart-to-order relation." })
24
25
  });
25
26
  export {
26
27
  MedusaCachedPluginListSchema,
@@ -1,9 +1,10 @@
1
- import type { StoreAddCartShippingMethods, StoreCalculatedPrice, StoreInitializePaymentSession, StoreUpdateCart } from '@medusajs/types';
2
- import type { Address, Cache, CheckoutFactory, CheckoutFactoryCheckoutOutput, CheckoutFactoryPaymentMethodOutput, CheckoutFactoryShippingMethodOutput, CheckoutFactoryWithOutput, CheckoutIdentifier, CheckoutMutationAddPaymentInstruction, CheckoutMutationFinalizeCheckout, CheckoutMutationInitiateCheckout, CheckoutMutationRemovePaymentInstruction, CheckoutMutationSetShippingAddress, CheckoutMutationSetShippingInstruction, CheckoutQueryById, CheckoutQueryForAvailablePaymentMethods, CheckoutQueryForAvailableShippingMethods, NotFoundError, RequestContext, Result } from '@reactionary/core';
1
+ import type { StoreAddCartShippingMethods, StoreCalculatedPrice, StoreInitializePaymentSession, StoreOrder, StoreUpdateCart } from '@medusajs/types';
2
+ import type { Address, Cache, CheckoutFactory, CheckoutFactoryCheckoutOutput, CheckoutFactoryPaymentMethodOutput, CheckoutFactoryShippingMethodOutput, CheckoutFactoryWithOutput, CheckoutIdentifier, CheckoutMutationAddPaymentInstruction, CheckoutMutationFinalizeCheckout, CheckoutMutationInitiateCheckout, CheckoutMutationRemovePaymentInstruction, CheckoutMutationSetShippingAddress, CheckoutMutationSetShippingInstruction, CheckoutQueryById, CheckoutQueryForAvailablePaymentMethods, CheckoutQueryForAvailableShippingMethods, NotFoundError, RequestContext, Result, ShippingProtocolData, StoreIdentifier } from '@reactionary/core';
3
3
  import { CheckoutCapability } from '@reactionary/core';
4
- import type { MedusaAPI } from '../core/client.js';
4
+ import { type MedusaAPI } from '../core/client.js';
5
5
  import type { MedusaCheckoutFactory } from '../factories/checkout/checkout.factory.js';
6
6
  import type { MedusaConfiguration } from '../schema/configuration.schema.js';
7
+ import { type MedusaOrderIdentifier } from '../schema/medusa.schema.js';
7
8
  export declare class CheckoutNotReadyForFinalizationError extends Error {
8
9
  checkoutIdentifier: CheckoutIdentifier;
9
10
  constructor(checkoutIdentifier: CheckoutIdentifier);
@@ -19,6 +20,16 @@ export declare class MedusaCheckoutCapability<TFactory extends CheckoutFactory =
19
20
  * example: this.includedFields = [includedFields, '+discounts.*'].join(',');
20
21
  */
21
22
  protected includedFields: string;
23
+ /**
24
+ * This controls which fields are requested for the cart's shipping options.
25
+ * You can override this in a subclass to add more fields as needed.
26
+ *
27
+ * The service zone's fulfillment set tells us whether an option is in-store
28
+ * pickup (BOPIS) or a delivery, and the option metadata lets medusa-side
29
+ * providers push additional data that is mapped to the shipping method's
30
+ * protocolData.
31
+ */
32
+ protected shippingOptionFields: string;
22
33
  constructor(config: MedusaConfiguration, cache: Cache, context: RequestContext, medusaApi: MedusaAPI, factory: CheckoutFactoryWithOutput<TFactory>);
23
34
  protected initiateCheckoutForCartPayload(payload: CheckoutMutationInitiateCheckout): StoreUpdateCart;
24
35
  initiateCheckoutForCart(payload: CheckoutMutationInitiateCheckout): Promise<Result<CheckoutFactoryCheckoutOutput<TFactory>>>;
@@ -27,6 +38,7 @@ export declare class MedusaCheckoutCapability<TFactory extends CheckoutFactory =
27
38
  setShippingAddress(payload: CheckoutMutationSetShippingAddress): Promise<Result<CheckoutFactoryCheckoutOutput<TFactory>>>;
28
39
  protected getAvailableShippingMethodsPayload(payload: CheckoutQueryForAvailableShippingMethods): {
29
40
  cart_id: string;
41
+ fields: string;
30
42
  };
31
43
  /**
32
44
  * Whether a Medusa shipping option's calculated price is usable, i.e. has an
@@ -48,8 +60,40 @@ export declare class MedusaCheckoutCapability<TFactory extends CheckoutFactory =
48
60
  addPaymentInstruction(payload: CheckoutMutationAddPaymentInstruction): Promise<Result<CheckoutFactoryCheckoutOutput<TFactory>>>;
49
61
  removePaymentInstruction(payload: CheckoutMutationRemovePaymentInstruction): Promise<Result<CheckoutFactoryCheckoutOutput<TFactory>>>;
50
62
  protected setShippingInstructionPayload(payload: CheckoutMutationSetShippingInstruction): StoreAddCartShippingMethods;
63
+ /**
64
+ * Serializes the client-set shipping protocol data as a JSON object string
65
+ * for persistence in the cart metadata, or undefined when none is set.
66
+ */
67
+ protected serializeShippingProtocolData(protocolData: ShippingProtocolData[] | undefined): string | undefined;
68
+ /**
69
+ * Resolves the shipping address to use for in-store pickup (BOPIS): the
70
+ * address of the selected store, so the order ships to the store. Returns
71
+ * undefined when the store or its address is unknown, leaving the cart's
72
+ * shipping address untouched.
73
+ */
74
+ protected resolveStorePickupAddress(store: StoreIdentifier): Promise<Partial<Address> | undefined>;
51
75
  setShippingInstruction(payload: CheckoutMutationSetShippingInstruction): Promise<Result<CheckoutFactoryCheckoutOutput<TFactory>>>;
52
76
  finalizeCheckout(payload: CheckoutMutationFinalizeCheckout): Promise<Result<CheckoutFactoryCheckoutOutput<TFactory>>>;
77
+ /**
78
+ * Builds the order identifier from a completed-cart response's order,
79
+ * following the same display-id precedence as the checkout factory
80
+ * (custom_display_id over display_id).
81
+ */
82
+ protected parseCompletedOrderIdentifier(order: StoreOrder & {
83
+ custom_display_id?: string | number;
84
+ }): MedusaOrderIdentifier;
85
+ /**
86
+ * Remembers which order a finalized cart produced, so later checkout reads
87
+ * in the same session can report the resulting order even when the Medusa
88
+ * store cart endpoint does not expose the cart->order relation.
89
+ */
90
+ protected rememberFinalizedOrder(cartKey: string, order: MedusaOrderIdentifier): void;
91
+ /**
92
+ * Fills in a missing resultingOrder from the session's finalized-orders map.
93
+ * Checkouts that already carry a resulting order (older Medusa versions
94
+ * still hydrate the +order.* fields) are returned unchanged.
95
+ */
96
+ protected hydrateResultingOrder(checkout: CheckoutFactoryCheckoutOutput<TFactory>): CheckoutFactoryCheckoutOutput<TFactory>;
53
97
  /**
54
98
  * Extension point to map an Address to a Store Address
55
99
  * @param address
@@ -0,0 +1,45 @@
1
+ import type { AdminStockLocation } from '@medusajs/types';
2
+ import type { Cache, NotFoundError, RequestContext, Result, StoreFactory, StoreFactoryOutput, StoreFactoryWithOutput, StorePaginatedResult, StoreQueryById, StoreQueryByProximity, StoreQueryForList } from '@reactionary/core';
3
+ import { StoreCapability } from '@reactionary/core';
4
+ import { type MedusaAPI } from '../core/client.js';
5
+ import type { MedusaStoreFactory } from '../factories/store/store.factory.js';
6
+ import type { MedusaConfiguration } from '../schema/configuration.schema.js';
7
+ /**
8
+ * Exposes Medusa stock locations that have a fulfillment set of type "pickup"
9
+ * as the brand's own stores, for in-store pickup (BOPIS).
10
+ */
11
+ export declare class MedusaStoreCapability<TFactory extends StoreFactory = MedusaStoreFactory> extends StoreCapability<StoreFactoryOutput<TFactory>> {
12
+ medusaApi: MedusaAPI;
13
+ protected config: MedusaConfiguration;
14
+ protected factory: StoreFactoryWithOutput<TFactory>;
15
+ /**
16
+ * The fields requested for stock locations. Override in a subclass if a
17
+ * project needs more relations.
18
+ */
19
+ protected stockLocationFields: string;
20
+ constructor(config: MedusaConfiguration, cache: Cache, context: RequestContext, medusaApi: MedusaAPI, factory: StoreFactoryWithOutput<TFactory>);
21
+ /**
22
+ * Whether a stock location is a store customers can pick up orders from,
23
+ * i.e. it has a fulfillment set of type "pickup".
24
+ */
25
+ protected isPickupLocation(location: AdminStockLocation): boolean;
26
+ /**
27
+ * Fetches all stock locations that are pickup stores. Medusa installations
28
+ * have a small, bounded number of stock locations, so fetching them all and
29
+ * filtering client-side is acceptable.
30
+ */
31
+ protected fetchPickupLocations(): Promise<AdminStockLocation[]>;
32
+ list(payload: StoreQueryForList): Promise<Result<StorePaginatedResult>>;
33
+ getById(payload: StoreQueryById): Promise<Result<StoreFactoryOutput<TFactory>, NotFoundError>>;
34
+ /**
35
+ * Extension point resolving a stock location's geo coordinates. Medusa
36
+ * stock locations carry no first-class coordinates, so by default they are
37
+ * read from the location metadata keys `latitude` and `longitude`. Returns
38
+ * undefined when the location has no usable coordinates.
39
+ */
40
+ protected parseLocationCoordinates(location: AdminStockLocation): {
41
+ latitude: number;
42
+ longitude: number;
43
+ } | undefined;
44
+ queryByProximity(payload: StoreQueryByProximity): Promise<Result<Array<StoreFactoryOutput<TFactory>>>>;
45
+ }
@@ -1,4 +1,4 @@
1
- import type { CartFactory, CategoryFactory, CheckoutFactory, ClientFromCapabilities, CompanyFactory, CompanyRegistrationFactory, EmployeeFactory, EmployeeInvitationFactory, InventoryFactory, PersonalizationProfileFactory, OrderFactory, OrderSearchFactory, PriceFactory, ProductAssociationsFactory, ProductFactory, ProductSearchFactory, ProfileFactory } from '@reactionary/core';
1
+ import type { CartFactory, CategoryFactory, CheckoutFactory, ClientFromCapabilities, CompanyFactory, CompanyRegistrationFactory, EmployeeFactory, EmployeeInvitationFactory, InventoryFactory, PersonalizationProfileFactory, OrderFactory, OrderSearchFactory, PriceFactory, ProductAssociationsFactory, ProductFactory, ProductSearchFactory, ProfileFactory, StoreFactory } from '@reactionary/core';
2
2
  import type { MedusaCapabilities } from '../schema/capabilities.schema.js';
3
3
  import type { MedusaCartFactory } from '../factories/cart/cart.factory.js';
4
4
  import type { MedusaCategoryFactory } from '../factories/category/category.factory.js';
@@ -15,6 +15,7 @@ import type { MedusaProductAssociationsFactory } from '../factories/product-asso
15
15
  import type { MedusaProductFactory } from '../factories/product/product.factory.js';
16
16
  import type { MedusaProductSearchFactory } from '../factories/product-search/product-search.factory.js';
17
17
  import type { MedusaProfileFactory } from '../factories/profile/profile.factory.js';
18
+ import type { MedusaStoreFactory } from '../factories/store/store.factory.js';
18
19
  import type { MedusaPersonalizationProfileFactory } from '../factories/personalization-profile/personalization-profile.factory.js';
19
20
  import type { MedusaCartCapability } from '../capabilities/cart.capability.js';
20
21
  import type { MedusaCategoryCapability } from '../capabilities/category.capability.js';
@@ -31,10 +32,11 @@ import type { MedusaProductAssociationsCapability } from '../capabilities/produc
31
32
  import type { MedusaProductCapability } from '../capabilities/product.capability.js';
32
33
  import type { MedusaProductSearchCapability } from '../capabilities/product-search.capability.js';
33
34
  import type { MedusaProfileCapability } from '../capabilities/profile.capability.js';
35
+ import type { MedusaStoreCapability } from '../capabilities/store.capability.js';
34
36
  import type { MedusaPersonalizationProfileCapability } from '../capabilities/personalization-profile.capability.js';
35
37
  import type { MedusaProductRecommendationsCapability } from '../capabilities/product-recommendations.capability.js';
36
38
  import type { MedusaIdentityCapability } from '../capabilities/identity.capability.js';
37
- type OverridableCapabilityKey = 'product' | 'productSearch' | 'cart' | 'checkout' | 'category' | 'price' | 'order' | 'orderSearch' | 'inventory' | 'profile' | 'productAssociations' | 'personalizationProfile' | 'employee' | 'employeeInvitation' | 'company' | 'companyRegistration';
39
+ type OverridableCapabilityKey = 'product' | 'productSearch' | 'cart' | 'checkout' | 'category' | 'price' | 'order' | 'orderSearch' | 'inventory' | 'profile' | 'productAssociations' | 'personalizationProfile' | 'employee' | 'employeeInvitation' | 'company' | 'companyRegistration' | 'store';
38
40
  type EnabledCapability<TCapability> = TCapability extends {
39
41
  enabled: true;
40
42
  } ? true : false;
@@ -69,6 +71,7 @@ type FactoryContractMap = {
69
71
  employeeInvitation: EmployeeInvitationFactory;
70
72
  company: CompanyFactory;
71
73
  companyRegistration: CompanyRegistrationFactory;
74
+ store: StoreFactory;
72
75
  };
73
76
  type DefaultFactoryMap = {
74
77
  product: MedusaProductFactory;
@@ -87,6 +90,7 @@ type DefaultFactoryMap = {
87
90
  employeeInvitation: MedusaEmployeeInvitationFactory;
88
91
  company: MedusaCompanyFactory;
89
92
  companyRegistration: MedusaCompanyRegistrationFactory;
93
+ store: MedusaStoreFactory;
90
94
  };
91
95
  type ResolvedFactoryMap<T extends MedusaCapabilities> = {
92
96
  [K in OverridableCapabilityKey]: ExtractCapabilityFactory<T[K], FactoryContractMap[K], DefaultFactoryMap[K]>;
@@ -108,6 +112,7 @@ type DefaultCapabilityMap<T extends MedusaCapabilities> = {
108
112
  employeeInvitation: MedusaEmployeeInvitationCapability<ResolvedFactoryMap<T>['employeeInvitation']>;
109
113
  company: MedusaCompanyCapability<ResolvedFactoryMap<T>['company']>;
110
114
  companyRegistration: MedusaCompanyRegistrationCapability<ResolvedFactoryMap<T>['companyRegistration']>;
115
+ store: MedusaStoreCapability<ResolvedFactoryMap<T>['store']>;
111
116
  identity: MedusaIdentityCapability;
112
117
  productRecommendations: MedusaProductRecommendationsCapability;
113
118
  };
@@ -1,7 +1,15 @@
1
1
  import type { StorePaymentCollection } from '@medusajs/types';
2
2
  import { type StoreCart, type StoreCartAddress, type StoreCartLineItem, type StoreCartShippingOptionWithServiceZone, type StorePaymentProvider } from '@medusajs/types';
3
- import { type Address, type AnyCheckoutSchema, type AnyPaymentMethodSchema, type AnyShippingMethodSchema, type CheckoutFactory, type CheckoutItem, type CheckoutSchema, type CostBreakDown, type Currency, type ItemCostBreakdown, type PaymentInstruction, type PaymentMethodIdentifier, type PaymentMethodSchema, type RequestContext, type ShippingInstruction, type ShippingMethodSchema } from '@reactionary/core';
3
+ import { type Address, type AnyCheckoutSchema, type AnyPaymentMethodSchema, type AnyShippingMethodSchema, type CheckoutFactory, type CheckoutItem, type CheckoutSchema, type CostBreakDown, type Currency, type ItemCostBreakdown, type PaymentInstruction, type PaymentMethodIdentifier, type PaymentMethodSchema, type RequestContext, type ShippingFulfillmentType, type ShippingInstruction, type ShippingMethodSchema, type ShippingProtocolData } from '@reactionary/core';
4
4
  import type * as z from 'zod';
5
+ /**
6
+ * A cart shipping option with the optional metadata relation, which the
7
+ * Medusa HTTP types do not declare on the store-level shipping option even
8
+ * though it can be requested via the fields parameter.
9
+ */
10
+ export type MedusaShippingOptionWithMetadata = StoreCartShippingOptionWithServiceZone & {
11
+ metadata?: Record<string, unknown> | null;
12
+ };
5
13
  export declare class MedusaCheckoutFactory<TCheckoutSchema extends AnyCheckoutSchema = typeof CheckoutSchema, TShippingMethodSchema extends AnyShippingMethodSchema = typeof ShippingMethodSchema, TPaymentMethodSchema extends AnyPaymentMethodSchema = typeof PaymentMethodSchema> implements CheckoutFactory<TCheckoutSchema, TShippingMethodSchema, TPaymentMethodSchema> {
6
14
  readonly checkoutSchema: TCheckoutSchema;
7
15
  readonly shippingMethodSchema: TShippingMethodSchema;
@@ -36,7 +44,28 @@ export declare class MedusaCheckoutFactory<TCheckoutSchema extends AnyCheckoutSc
36
44
  protected isReadyForFinalization(price: CostBreakDown, paymentInstructions: PaymentInstruction[], billingAddress?: Address, shippingAddress?: Address, shippingInstruction?: ShippingInstruction): boolean;
37
45
  protected isCart(data: StoreCart): data is StoreCart;
38
46
  parseCheckout(context: RequestContext, data: StoreCart): z.output<TCheckoutSchema>;
39
- parseShippingMethod(_context: RequestContext, data: StoreCartShippingOptionWithServiceZone): z.output<TShippingMethodSchema>;
47
+ parseShippingMethod(_context: RequestContext, data: MedusaShippingOptionWithMetadata): z.output<TShippingMethodSchema>;
48
+ /**
49
+ * Extension point to control how a Medusa shipping option maps to the core
50
+ * fulfillment type. A shipping option belonging to a fulfillment set of type
51
+ * "pickup" is in-store pickup (BOPIS); everything else is a delivery.
52
+ */
53
+ protected parseFulfillmentType(_context: RequestContext, data: MedusaShippingOptionWithMetadata): ShippingFulfillmentType;
54
+ /**
55
+ * Extension point mapping a Medusa shipping option's metadata to the
56
+ * shipping method's protocolData, so medusa-side providers can push
57
+ * additional data (e.g. available pickup time slots) that reaches the
58
+ * client. String and JSON-object values are kept as-is, other primitives
59
+ * are stringified, and arrays/null entries are skipped.
60
+ */
61
+ protected parseShippingMethodProtocolData(_context: RequestContext, data: MedusaShippingOptionWithMetadata): ShippingProtocolData[] | undefined;
62
+ /**
63
+ * Parses shipping protocol data persisted as a JSON object string (keys
64
+ * mapping to string or JSON-object values) back into protocol data entries.
65
+ * Returns undefined when nothing (valid) is stored, keeping the field absent
66
+ * for carts that never set it.
67
+ */
68
+ protected parseShippingProtocolData(raw: unknown): ShippingProtocolData[] | undefined;
40
69
  protected parsePaymentInstruction(_context: RequestContext, remotePayment: StorePaymentCollection, checkout: StoreCart): {
41
70
  identifier: {
42
71
  key: string;
@@ -15,3 +15,4 @@ export * from './product-associations/product-associations.factory.js';
15
15
  export * from './product-search/product-search.factory.js';
16
16
  export * from './profile/profile.factory.js';
17
17
  export * from './personalization-profile/personalization-profile.factory.js';
18
+ export * from './store/store.factory.js';
@@ -0,0 +1,13 @@
1
+ import type { AdminStockLocation } from '@medusajs/types';
2
+ import type { Address, AnyStoreSchema, RequestContext, StoreFactory, StoreSchema } from '@reactionary/core';
3
+ import type * as z from 'zod';
4
+ export declare class MedusaStoreFactory<TStoreSchema extends AnyStoreSchema = typeof StoreSchema> implements StoreFactory<TStoreSchema> {
5
+ readonly storeSchema: TStoreSchema;
6
+ constructor(storeSchema: TStoreSchema);
7
+ /**
8
+ * Extension point to control the parsing of the stock location address
9
+ * into the store's address.
10
+ */
11
+ protected parseAddress(_context: RequestContext, data: AdminStockLocation): Address | undefined;
12
+ parseStore(context: RequestContext, data: AdminStockLocation): z.output<TStoreSchema>;
13
+ }
package/src/index.d.ts CHANGED
@@ -23,3 +23,4 @@ export * from './capabilities/category.capability.js';
23
23
  export * from './capabilities/checkout.capability.js';
24
24
  export * from './capabilities/product.capability.js';
25
25
  export * from './capabilities/personalization-profile.capability.js';
26
+ export * from './capabilities/store.capability.js';
@@ -1,4 +1,4 @@
1
- import type { CartFactory, CartFactoryWithOutput, CartCapability, CategoryFactory, CategoryFactoryWithOutput, CategoryCapability, CheckoutFactory, CheckoutFactoryWithOutput, CheckoutCapability, CompanyFactory, CompanyFactoryWithOutput, CompanyCapability, CompanyRegistrationFactory, CompanyRegistrationFactoryWithOutput, CompanyRegistrationCapability, EmployeeFactory, EmployeeFactoryWithOutput, EmployeeCapability, EmployeeInvitationFactory, EmployeeInvitationFactoryWithOutput, EmployeeInvitationCapability, IdentityCapability, InventoryFactory, InventoryFactoryWithOutput, InventoryCapability, PersonalizationProfileFactory, PersonalizationProfileFactoryWithOutput, PersonalizationProfileCapability, OrderFactory, OrderFactoryWithOutput, OrderCapability, OrderSearchFactory, OrderSearchFactoryWithOutput, OrderSearchCapability, PriceFactory, PriceFactoryWithOutput, PriceCapability, ProductAssociationsFactory, ProductAssociationsFactoryWithOutput, ProductAssociationsCapability, ProductFactory, ProductFactoryWithOutput, ProductCapability, ProductRecommendationsCapability, ProductSearchFactory, ProductSearchFactoryWithOutput, ProductSearchCapability, ProfileFactory, ProfileFactoryWithOutput, ProfileCapability, RequestContext, Cache } from '@reactionary/core';
1
+ import type { CartFactory, CartFactoryWithOutput, CartCapability, CategoryFactory, CategoryFactoryWithOutput, CategoryCapability, CheckoutFactory, CheckoutFactoryWithOutput, CheckoutCapability, CompanyFactory, CompanyFactoryWithOutput, CompanyCapability, CompanyRegistrationFactory, CompanyRegistrationFactoryWithOutput, CompanyRegistrationCapability, EmployeeFactory, EmployeeFactoryWithOutput, EmployeeCapability, EmployeeInvitationFactory, EmployeeInvitationFactoryWithOutput, EmployeeInvitationCapability, IdentityCapability, InventoryFactory, InventoryFactoryWithOutput, InventoryCapability, PersonalizationProfileFactory, PersonalizationProfileFactoryWithOutput, PersonalizationProfileCapability, OrderFactory, OrderFactoryWithOutput, OrderCapability, OrderSearchFactory, OrderSearchFactoryWithOutput, OrderSearchCapability, PriceFactory, PriceFactoryWithOutput, PriceCapability, ProductAssociationsFactory, ProductAssociationsFactoryWithOutput, ProductAssociationsCapability, ProductFactory, ProductFactoryWithOutput, ProductCapability, ProductRecommendationsCapability, ProductSearchFactory, ProductSearchFactoryWithOutput, ProductSearchCapability, ProfileFactory, ProfileFactoryWithOutput, ProfileCapability, StoreFactory, StoreFactoryWithOutput, StoreCapability, RequestContext, Cache } from '@reactionary/core';
2
2
  import type { MedusaConfiguration } from './configuration.schema.js';
3
3
  import type { MedusaAPI } from '../core/client.js';
4
4
  import * as z from 'zod';
@@ -91,6 +91,11 @@ export declare const MedusaCapabilitiesSchema: z.ZodObject<{
91
91
  factory: z.ZodOptional<z.ZodUnknown>;
92
92
  capability: z.ZodOptional<z.ZodUnknown>;
93
93
  }, z.core.$loose>>>;
94
+ store: z.ZodOptional<z.ZodOptional<z.ZodObject<{
95
+ enabled: z.ZodBoolean;
96
+ factory: z.ZodOptional<z.ZodUnknown>;
97
+ capability: z.ZodOptional<z.ZodUnknown>;
98
+ }, z.core.$loose>>>;
94
99
  }, z.core.$loose>;
95
100
  export interface MedusaCapabilityFactoryArgs {
96
101
  cache: Cache;
@@ -128,7 +133,8 @@ export type MedusaProfileCapabilityConfig = MedusaCapabilityConfig<ProfileFactor
128
133
  export type MedusaProductAssociationsCapabilityConfig = MedusaCapabilityConfig<ProductAssociationsFactoryWithOutput<ProductAssociationsFactory>, ProductAssociationsCapability>;
129
134
  export type MedusaPersonalizationProfileCapabilityConfig = MedusaCapabilityConfig<PersonalizationProfileFactoryWithOutput<PersonalizationProfileFactory>, PersonalizationProfileCapability>;
130
135
  export type MedusaProductRecommendationsCapabilityConfig = MedusaDirectCapabilityConfig<ProductRecommendationsCapability>;
131
- export type MedusaCapabilities<TProductFactory extends ProductFactory = ProductFactory, TProductCapability extends ProductCapability = ProductCapability, TProductSearchFactory extends ProductSearchFactory = ProductSearchFactory, TProductSearchCapability extends ProductSearchCapability = ProductSearchCapability, TCartFactory extends CartFactory = CartFactory, TCartCapability extends CartCapability = CartCapability, TCheckoutFactory extends CheckoutFactory = CheckoutFactory, TCheckoutCapability extends CheckoutCapability = CheckoutCapability, TCategoryFactory extends CategoryFactory = CategoryFactory, TCategoryCapability extends CategoryCapability = CategoryCapability, TPriceFactory extends PriceFactory = PriceFactory, TPriceCapability extends PriceCapability = PriceCapability, TOrderFactory extends OrderFactory = OrderFactory, TOrderCapability extends OrderCapability = OrderCapability, TOrderSearchFactory extends OrderSearchFactory = OrderSearchFactory, TOrderSearchCapability extends OrderSearchCapability = OrderSearchCapability, TInventoryFactory extends InventoryFactory = InventoryFactory, TInventoryCapability extends InventoryCapability = InventoryCapability, TIdentityCapability extends IdentityCapability = IdentityCapability, TProfileFactory extends ProfileFactory = ProfileFactory, TProfileCapability extends ProfileCapability = ProfileCapability, TProductAssociationsFactory extends ProductAssociationsFactory = ProductAssociationsFactory, TProductAssociationsCapability extends ProductAssociationsCapability = ProductAssociationsCapability, TProductRecommendationsCapability extends ProductRecommendationsCapability = ProductRecommendationsCapability, TPersonalizationProfileFactory extends PersonalizationProfileFactory = PersonalizationProfileFactory, TPersonalizationProfileCapability extends PersonalizationProfileCapability = PersonalizationProfileCapability, TEmployeeFactory extends EmployeeFactory = EmployeeFactory, TEmployeeCapability extends EmployeeCapability = EmployeeCapability, TEmployeeInvitationFactory extends EmployeeInvitationFactory = EmployeeInvitationFactory, TEmployeeInvitationCapability extends EmployeeInvitationCapability = EmployeeInvitationCapability, TCompanyFactory extends CompanyFactory = CompanyFactory, TCompanyCapability extends CompanyCapability = CompanyCapability, TCompanyRegistrationFactory extends CompanyRegistrationFactory = CompanyRegistrationFactory, TCompanyRegistrationCapability extends CompanyRegistrationCapability = CompanyRegistrationCapability> = {
136
+ export type MedusaStoreCapabilityConfig = MedusaCapabilityConfig<StoreFactoryWithOutput<StoreFactory>, StoreCapability>;
137
+ export type MedusaCapabilities<TProductFactory extends ProductFactory = ProductFactory, TProductCapability extends ProductCapability = ProductCapability, TProductSearchFactory extends ProductSearchFactory = ProductSearchFactory, TProductSearchCapability extends ProductSearchCapability = ProductSearchCapability, TCartFactory extends CartFactory = CartFactory, TCartCapability extends CartCapability = CartCapability, TCheckoutFactory extends CheckoutFactory = CheckoutFactory, TCheckoutCapability extends CheckoutCapability = CheckoutCapability, TCategoryFactory extends CategoryFactory = CategoryFactory, TCategoryCapability extends CategoryCapability = CategoryCapability, TPriceFactory extends PriceFactory = PriceFactory, TPriceCapability extends PriceCapability = PriceCapability, TOrderFactory extends OrderFactory = OrderFactory, TOrderCapability extends OrderCapability = OrderCapability, TOrderSearchFactory extends OrderSearchFactory = OrderSearchFactory, TOrderSearchCapability extends OrderSearchCapability = OrderSearchCapability, TInventoryFactory extends InventoryFactory = InventoryFactory, TInventoryCapability extends InventoryCapability = InventoryCapability, TIdentityCapability extends IdentityCapability = IdentityCapability, TProfileFactory extends ProfileFactory = ProfileFactory, TProfileCapability extends ProfileCapability = ProfileCapability, TProductAssociationsFactory extends ProductAssociationsFactory = ProductAssociationsFactory, TProductAssociationsCapability extends ProductAssociationsCapability = ProductAssociationsCapability, TProductRecommendationsCapability extends ProductRecommendationsCapability = ProductRecommendationsCapability, TPersonalizationProfileFactory extends PersonalizationProfileFactory = PersonalizationProfileFactory, TPersonalizationProfileCapability extends PersonalizationProfileCapability = PersonalizationProfileCapability, TEmployeeFactory extends EmployeeFactory = EmployeeFactory, TEmployeeCapability extends EmployeeCapability = EmployeeCapability, TEmployeeInvitationFactory extends EmployeeInvitationFactory = EmployeeInvitationFactory, TEmployeeInvitationCapability extends EmployeeInvitationCapability = EmployeeInvitationCapability, TCompanyFactory extends CompanyFactory = CompanyFactory, TCompanyCapability extends CompanyCapability = CompanyCapability, TCompanyRegistrationFactory extends CompanyRegistrationFactory = CompanyRegistrationFactory, TCompanyRegistrationCapability extends CompanyRegistrationCapability = CompanyRegistrationCapability, TStoreFactory extends StoreFactory = StoreFactory, TStoreCapability extends StoreCapability = StoreCapability> = {
132
138
  product?: MedusaCapabilityConfig<ProductFactoryWithOutput<TProductFactory>, TProductCapability>;
133
139
  productSearch?: MedusaCapabilityConfig<ProductSearchFactoryWithOutput<TProductSearchFactory>, TProductSearchCapability>;
134
140
  cart?: MedusaCapabilityConfig<CartFactoryWithOutput<TCartFactory>, TCartCapability>;
@@ -147,5 +153,6 @@ export type MedusaCapabilities<TProductFactory extends ProductFactory = ProductF
147
153
  productAssociations?: MedusaCapabilityConfig<ProductAssociationsFactoryWithOutput<TProductAssociationsFactory>, TProductAssociationsCapability>;
148
154
  productRecommendations?: MedusaDirectCapabilityConfig<TProductRecommendationsCapability>;
149
155
  personalizationProfile?: MedusaCapabilityConfig<PersonalizationProfileFactoryWithOutput<TPersonalizationProfileFactory>, TPersonalizationProfileCapability>;
156
+ store?: MedusaCapabilityConfig<StoreFactoryWithOutput<TStoreFactory>, TStoreCapability>;
150
157
  };
151
158
  export type ParsedMedusaCapabilities = z.infer<typeof MedusaCapabilitiesSchema>;
@@ -41,6 +41,10 @@ export declare const MedusaSessionSchema: z.ZodObject<{
41
41
  name: z.ZodString;
42
42
  currency_code: z.ZodString;
43
43
  }, z.core.$loose>>;
44
+ finalizedOrders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
45
+ key: z.ZodString;
46
+ display_id: z.ZodOptional<z.ZodNumber>;
47
+ }, z.core.$loose>>>;
44
48
  }, z.core.$loose>;
45
49
  export type MedusaCartIdentifier = InferType<typeof MedusaCartIdentifierSchema>;
46
50
  export type MedusaOrderIdentifier = z.infer<typeof MedusaOrderIdentifierSchema>;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Great-circle distance between two coordinates in kilometers, using the
3
+ * haversine formula.
4
+ */
5
+ export declare function haversineDistanceKm(latitude1: number, longitude1: number, latitude2: number, longitude2: number): number;
package/utils/geo.js ADDED
@@ -0,0 +1,11 @@
1
+ const EARTH_RADIUS_KM = 6371;
2
+ function haversineDistanceKm(latitude1, longitude1, latitude2, longitude2) {
3
+ const toRadians = (degrees) => degrees * Math.PI / 180;
4
+ const deltaLatitude = toRadians(latitude2 - latitude1);
5
+ const deltaLongitude = toRadians(longitude2 - longitude1);
6
+ const a = Math.sin(deltaLatitude / 2) ** 2 + Math.cos(toRadians(latitude1)) * Math.cos(toRadians(latitude2)) * Math.sin(deltaLongitude / 2) ** 2;
7
+ return 2 * EARTH_RADIUS_KM * Math.asin(Math.sqrt(a));
8
+ }
9
+ export {
10
+ haversineDistanceKm
11
+ };