@thorprovider/adapters 5.0.0 → 5.0.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/AGENTS.md +1 -1
- package/CHANGELOG.md +6 -0
- package/README.md +53 -631
- package/dist/index.d.mts +406 -194
- package/dist/index.d.ts +406 -194
- package/dist/index.js +3092 -604
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3081 -602
- package/dist/index.mjs.map +1 -1
- package/docs/RETRY_SOLUTION_SUMMARY.md +1 -1
- package/docs/authentication.md +0 -26
- package/docs/capabilities-system.md +66 -36
- package/docs/getting-started.md +1 -1
- package/docs/providers-overview.md +16 -13
- package/docs/update-documentation.md +10 -8
- package/package.json +6 -5
- package/quick-reference.md +0 -33
- package/src/backend/factory.ts +11 -1
- package/src/factory/index.ts +52 -74
- package/src/index.ts +17 -3
- package/src/providers/bigcommerce/bigcommerce.ts +6 -0
- package/src/providers/bigcommerce/index.ts +315 -0
- package/src/providers/bigcommerce/transforms.ts +227 -0
- package/src/providers/index.ts +16 -1
- package/src/providers/magento/index.ts +387 -0
- package/src/providers/magento/magento.ts +5 -0
- package/src/providers/magento/queries.ts +267 -0
- package/src/providers/magento/transforms.ts +249 -0
- package/src/providers/medusa/auth.test.ts +94 -92
- package/src/providers/medusa/auth.ts +72 -123
- package/src/providers/medusa/index.test.ts +392 -0
- package/src/providers/medusa/index.ts +1 -21
- package/src/providers/medusa/live-smoke.test.ts +412 -0
- package/src/providers/shopify/auth.ts +402 -0
- package/src/providers/shopify/index.ts +444 -0
- package/src/providers/shopify/mutations.ts +247 -0
- package/src/providers/shopify/queries.ts +324 -0
- package/src/providers/shopify/shopify.ts +7 -0
- package/src/providers/shopify/transforms.ts +275 -0
- package/src/providers/spree/index.ts +344 -0
- package/src/providers/spree/spree.ts +5 -0
- package/src/providers/spree/transforms.ts +254 -0
- package/src/providers/woocommerce/index.ts +310 -0
- package/src/providers/woocommerce/transforms.ts +254 -0
- package/src/providers/woocommerce/woocommerce.ts +5 -0
- package/src/types/capabilities.types.ts +250 -0
- package/vitest.live.config.ts +18 -0
package/dist/index.js
CHANGED
|
@@ -32,6 +32,8 @@ var src_exports = {};
|
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
AdminAuthService: () => AdminAuthService,
|
|
34
34
|
AdminSessionService: () => AdminSessionService,
|
|
35
|
+
BIGCOMMERCE_CAPABILITIES: () => BIGCOMMERCE_CAPABILITIES,
|
|
36
|
+
BigCommerceProvider: () => BigCommerceProvider,
|
|
35
37
|
CHANNEL_ASSIGNED_AT_KEY: () => CHANNEL_ASSIGNED_AT_KEY,
|
|
36
38
|
CHANNEL_ASSIGNED_BY_KEY: () => CHANNEL_ASSIGNED_BY_KEY,
|
|
37
39
|
CHANNEL_SCOPE_KEY: () => CHANNEL_SCOPE_KEY,
|
|
@@ -40,8 +42,10 @@ __export(src_exports, {
|
|
|
40
42
|
CircuitBreakerOpenError: () => CircuitBreakerOpenError,
|
|
41
43
|
CommerceError: () => CommerceError,
|
|
42
44
|
ConfigurationError: () => ConfigurationError,
|
|
45
|
+
MAGENTO_CAPABILITIES: () => MAGENTO_CAPABILITIES,
|
|
43
46
|
MEDUSA_CAPABILITIES: () => MEDUSA_CAPABILITIES,
|
|
44
47
|
MOCK_CAPABILITIES: () => MOCK_CAPABILITIES,
|
|
48
|
+
MagentoProvider: () => MagentoProvider,
|
|
45
49
|
MedusaMetadataAdapter: () => MedusaMetadataAdapter,
|
|
46
50
|
MedusaProvider: () => MedusaProvider,
|
|
47
51
|
MetricsService: () => MetricsService,
|
|
@@ -49,14 +53,21 @@ __export(src_exports, {
|
|
|
49
53
|
ProviderAPIError: () => ProviderAPIError,
|
|
50
54
|
ProviderNotFoundError: () => ProviderNotFoundError,
|
|
51
55
|
ResourceNotFoundError: () => ResourceNotFoundError,
|
|
56
|
+
SHOPIFY_CAPABILITIES: () => SHOPIFY_CAPABILITIES,
|
|
57
|
+
SPREE_CAPABILITIES: () => SPREE_CAPABILITIES,
|
|
52
58
|
STELORDER_CAPABILITIES: () => STELORDER_CAPABILITIES,
|
|
59
|
+
ShopifyProvider: () => ShopifyProvider,
|
|
60
|
+
SpreeProvider: () => SpreeProvider,
|
|
53
61
|
StelOrderProvider: () => StelOrderProvider,
|
|
54
62
|
StorefrontConfigError: () => import_types2.StorefrontConfigError,
|
|
63
|
+
WOOCOMMERCE_CAPABILITIES: () => WOOCOMMERCE_CAPABILITIES,
|
|
64
|
+
WooCommerceProvider: () => WooCommerceProvider,
|
|
55
65
|
assertEntityBelongsToChannel: () => assertEntityBelongsToChannel,
|
|
56
66
|
backendCircuitBreaker: () => backendCircuitBreaker,
|
|
57
67
|
buildChannelScopeMetadata: () => buildChannelScopeMetadata,
|
|
58
68
|
calculateDistance: () => calculateDistance,
|
|
59
69
|
createBackendMetadataAdapter: () => createBackendMetadataAdapter,
|
|
70
|
+
createFetchWithRetry: () => createFetchWithRetry,
|
|
60
71
|
createLogger: () => createLogger,
|
|
61
72
|
createProvider: () => createProvider,
|
|
62
73
|
extractErrorMessage: () => extractErrorMessage,
|
|
@@ -67,7 +78,6 @@ __export(src_exports, {
|
|
|
67
78
|
getBackendProviderId: () => getBackendProviderId,
|
|
68
79
|
getCapabilityDescription: () => getCapabilityDescription,
|
|
69
80
|
getChannelScope: () => getChannelScope,
|
|
70
|
-
getRegisteredProviders: () => getRegisteredProviders,
|
|
71
81
|
isBackendCapabilities: () => isBackendCapabilities,
|
|
72
82
|
isGlobalEntity: () => isGlobalEntity,
|
|
73
83
|
isProviderType: () => isProviderType,
|
|
@@ -77,7 +87,6 @@ __export(src_exports, {
|
|
|
77
87
|
numberToMoney: () => numberToMoney,
|
|
78
88
|
partitionByChannel: () => partitionByChannel,
|
|
79
89
|
planFulfillment: () => planFulfillment,
|
|
80
|
-
registerProvider: () => registerProvider,
|
|
81
90
|
transformFulfillmentOption: () => transformFulfillmentOption,
|
|
82
91
|
transformInventoryLevel: () => transformInventoryLevel,
|
|
83
92
|
transformStockLocation: () => transformStockLocation,
|
|
@@ -194,6 +203,209 @@ var STELORDER_CAPABILITIES = {
|
|
|
194
203
|
// Loyalty
|
|
195
204
|
loyaltyProgram: false
|
|
196
205
|
};
|
|
206
|
+
var SHOPIFY_CAPABILITIES = {
|
|
207
|
+
// Profile Management
|
|
208
|
+
updateProfile: true,
|
|
209
|
+
updateEmail: true,
|
|
210
|
+
changePasswordDirect: true,
|
|
211
|
+
changePasswordViaEmail: true,
|
|
212
|
+
deleteAccount: false,
|
|
213
|
+
// Requires admin API
|
|
214
|
+
// Avatar
|
|
215
|
+
uploadAvatar: false,
|
|
216
|
+
avatarViaMetadata: true,
|
|
217
|
+
// Custom Attributes
|
|
218
|
+
customAttributes: true,
|
|
219
|
+
// Orders
|
|
220
|
+
fetchOrders: true,
|
|
221
|
+
cancelOrder: true,
|
|
222
|
+
// Addresses
|
|
223
|
+
manageAddresses: true,
|
|
224
|
+
defaultAddress: true,
|
|
225
|
+
// Cart/Checkout
|
|
226
|
+
applyDiscounts: true,
|
|
227
|
+
shippingCalculation: true,
|
|
228
|
+
guestCheckout: true,
|
|
229
|
+
savedPaymentMethods: true,
|
|
230
|
+
listPaymentProviders: true,
|
|
231
|
+
regionSpecificPayments: false,
|
|
232
|
+
// Global payment methods
|
|
233
|
+
// Wishlist
|
|
234
|
+
wishlist: false,
|
|
235
|
+
// Requires app
|
|
236
|
+
// Search
|
|
237
|
+
advancedSearch: true,
|
|
238
|
+
facetedFilters: true,
|
|
239
|
+
// Reviews
|
|
240
|
+
productReviews: false,
|
|
241
|
+
// Requires app
|
|
242
|
+
// Loyalty
|
|
243
|
+
loyaltyProgram: false
|
|
244
|
+
// Requires app
|
|
245
|
+
};
|
|
246
|
+
var BIGCOMMERCE_CAPABILITIES = {
|
|
247
|
+
// Profile Management
|
|
248
|
+
updateProfile: true,
|
|
249
|
+
updateEmail: true,
|
|
250
|
+
changePasswordDirect: true,
|
|
251
|
+
changePasswordViaEmail: true,
|
|
252
|
+
deleteAccount: false,
|
|
253
|
+
// Avatar
|
|
254
|
+
uploadAvatar: false,
|
|
255
|
+
avatarViaMetadata: false,
|
|
256
|
+
// Custom Attributes
|
|
257
|
+
customAttributes: true,
|
|
258
|
+
// Orders
|
|
259
|
+
fetchOrders: true,
|
|
260
|
+
cancelOrder: false,
|
|
261
|
+
// Admin API only
|
|
262
|
+
// Addresses
|
|
263
|
+
manageAddresses: true,
|
|
264
|
+
defaultAddress: false,
|
|
265
|
+
// Cart/Checkout
|
|
266
|
+
applyDiscounts: true,
|
|
267
|
+
shippingCalculation: true,
|
|
268
|
+
guestCheckout: true,
|
|
269
|
+
savedPaymentMethods: true,
|
|
270
|
+
listPaymentProviders: true,
|
|
271
|
+
regionSpecificPayments: false,
|
|
272
|
+
// Wishlist
|
|
273
|
+
wishlist: true,
|
|
274
|
+
// Native support
|
|
275
|
+
// Search
|
|
276
|
+
advancedSearch: true,
|
|
277
|
+
facetedFilters: true,
|
|
278
|
+
// Reviews
|
|
279
|
+
productReviews: true,
|
|
280
|
+
// Native support
|
|
281
|
+
// Loyalty
|
|
282
|
+
loyaltyProgram: false
|
|
283
|
+
};
|
|
284
|
+
var WOOCOMMERCE_CAPABILITIES = {
|
|
285
|
+
// Profile Management
|
|
286
|
+
updateProfile: true,
|
|
287
|
+
updateEmail: true,
|
|
288
|
+
changePasswordDirect: false,
|
|
289
|
+
// WordPress handles auth
|
|
290
|
+
changePasswordViaEmail: true,
|
|
291
|
+
deleteAccount: false,
|
|
292
|
+
// Avatar
|
|
293
|
+
uploadAvatar: false,
|
|
294
|
+
avatarViaMetadata: false,
|
|
295
|
+
// Gravatar-based
|
|
296
|
+
// Custom Attributes
|
|
297
|
+
customAttributes: true,
|
|
298
|
+
// Via meta_data
|
|
299
|
+
// Orders
|
|
300
|
+
fetchOrders: true,
|
|
301
|
+
cancelOrder: true,
|
|
302
|
+
// Addresses
|
|
303
|
+
manageAddresses: true,
|
|
304
|
+
defaultAddress: false,
|
|
305
|
+
// Billing/shipping only
|
|
306
|
+
// Cart/Checkout
|
|
307
|
+
applyDiscounts: true,
|
|
308
|
+
// Coupon codes
|
|
309
|
+
shippingCalculation: true,
|
|
310
|
+
guestCheckout: true,
|
|
311
|
+
savedPaymentMethods: false,
|
|
312
|
+
listPaymentProviders: true,
|
|
313
|
+
regionSpecificPayments: false,
|
|
314
|
+
// Wishlist
|
|
315
|
+
wishlist: false,
|
|
316
|
+
// Requires plugin
|
|
317
|
+
// Search
|
|
318
|
+
advancedSearch: false,
|
|
319
|
+
// Basic WP search
|
|
320
|
+
facetedFilters: false,
|
|
321
|
+
// Requires plugin
|
|
322
|
+
// Reviews
|
|
323
|
+
productReviews: true,
|
|
324
|
+
// Native WP reviews
|
|
325
|
+
// Loyalty
|
|
326
|
+
loyaltyProgram: false
|
|
327
|
+
};
|
|
328
|
+
var SPREE_CAPABILITIES = {
|
|
329
|
+
// Profile Management
|
|
330
|
+
updateProfile: true,
|
|
331
|
+
updateEmail: true,
|
|
332
|
+
changePasswordDirect: true,
|
|
333
|
+
changePasswordViaEmail: true,
|
|
334
|
+
deleteAccount: false,
|
|
335
|
+
// Avatar
|
|
336
|
+
uploadAvatar: false,
|
|
337
|
+
avatarViaMetadata: false,
|
|
338
|
+
// Custom Attributes
|
|
339
|
+
customAttributes: false,
|
|
340
|
+
// Orders
|
|
341
|
+
fetchOrders: true,
|
|
342
|
+
cancelOrder: true,
|
|
343
|
+
// Addresses
|
|
344
|
+
manageAddresses: true,
|
|
345
|
+
defaultAddress: true,
|
|
346
|
+
// Cart/Checkout
|
|
347
|
+
applyDiscounts: true,
|
|
348
|
+
shippingCalculation: true,
|
|
349
|
+
guestCheckout: true,
|
|
350
|
+
savedPaymentMethods: false,
|
|
351
|
+
listPaymentProviders: true,
|
|
352
|
+
regionSpecificPayments: false,
|
|
353
|
+
// Wishlist
|
|
354
|
+
wishlist: true,
|
|
355
|
+
// Native support
|
|
356
|
+
// Search
|
|
357
|
+
advancedSearch: false,
|
|
358
|
+
facetedFilters: true,
|
|
359
|
+
// Taxon-based filtering
|
|
360
|
+
// Reviews
|
|
361
|
+
productReviews: false,
|
|
362
|
+
// Requires extension
|
|
363
|
+
// Loyalty
|
|
364
|
+
loyaltyProgram: false
|
|
365
|
+
};
|
|
366
|
+
var MAGENTO_CAPABILITIES = {
|
|
367
|
+
// Profile Management
|
|
368
|
+
updateProfile: true,
|
|
369
|
+
updateEmail: true,
|
|
370
|
+
changePasswordDirect: true,
|
|
371
|
+
changePasswordViaEmail: true,
|
|
372
|
+
deleteAccount: false,
|
|
373
|
+
// Admin only
|
|
374
|
+
// Avatar
|
|
375
|
+
uploadAvatar: false,
|
|
376
|
+
avatarViaMetadata: false,
|
|
377
|
+
// Custom Attributes
|
|
378
|
+
customAttributes: true,
|
|
379
|
+
// EAV model
|
|
380
|
+
// Orders
|
|
381
|
+
fetchOrders: true,
|
|
382
|
+
cancelOrder: true,
|
|
383
|
+
// Addresses
|
|
384
|
+
manageAddresses: true,
|
|
385
|
+
defaultAddress: true,
|
|
386
|
+
// Cart/Checkout
|
|
387
|
+
applyDiscounts: true,
|
|
388
|
+
shippingCalculation: true,
|
|
389
|
+
guestCheckout: true,
|
|
390
|
+
savedPaymentMethods: true,
|
|
391
|
+
listPaymentProviders: true,
|
|
392
|
+
regionSpecificPayments: true,
|
|
393
|
+
// Store-view scoped
|
|
394
|
+
// Wishlist
|
|
395
|
+
wishlist: true,
|
|
396
|
+
// Native support
|
|
397
|
+
// Search
|
|
398
|
+
advancedSearch: true,
|
|
399
|
+
// Elasticsearch/OpenSearch
|
|
400
|
+
facetedFilters: true,
|
|
401
|
+
// Layered navigation
|
|
402
|
+
// Reviews
|
|
403
|
+
productReviews: true,
|
|
404
|
+
// Native support
|
|
405
|
+
// Loyalty
|
|
406
|
+
loyaltyProgram: false
|
|
407
|
+
// Requires extension
|
|
408
|
+
};
|
|
197
409
|
function isBackendCapabilities(obj) {
|
|
198
410
|
if (typeof obj !== "object" || obj === null) return false;
|
|
199
411
|
const capabilities = obj;
|
|
@@ -408,6 +620,9 @@ async function fetchWithRetry(url, init, retryConfig) {
|
|
|
408
620
|
}
|
|
409
621
|
throw lastError;
|
|
410
622
|
}
|
|
623
|
+
function createFetchWithRetry(retryConfig) {
|
|
624
|
+
return (url, init) => fetchWithRetry(url, init, retryConfig);
|
|
625
|
+
}
|
|
411
626
|
|
|
412
627
|
// src/resilience/circuit-breaker.ts
|
|
413
628
|
var CircuitBreaker = class {
|
|
@@ -1413,7 +1628,13 @@ function createBackendMetadataAdapter(config) {
|
|
|
1413
1628
|
});
|
|
1414
1629
|
return new MedusaMetadataAdapter(medusa);
|
|
1415
1630
|
}
|
|
1416
|
-
|
|
1631
|
+
if (provider === "shopify") {
|
|
1632
|
+
throw new Error("Shopify metadata adapter not yet implemented");
|
|
1633
|
+
}
|
|
1634
|
+
if (provider === "woocommerce") {
|
|
1635
|
+
throw new Error("WooCommerce metadata adapter not yet implemented");
|
|
1636
|
+
}
|
|
1637
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
1417
1638
|
}
|
|
1418
1639
|
|
|
1419
1640
|
// src/metadata/channel-scope.ts
|
|
@@ -1608,17 +1829,11 @@ var MedusaAuthProvider = class {
|
|
|
1608
1829
|
logger;
|
|
1609
1830
|
authConfig;
|
|
1610
1831
|
storageManager;
|
|
1611
|
-
|
|
1612
|
-
baseUrl;
|
|
1613
|
-
/** Publishable API key — sent as x-publishable-api-key on store calls */
|
|
1614
|
-
publishableKey;
|
|
1615
|
-
constructor(sdk, authConfig, debug = false, baseUrl = "", publishableKey = "") {
|
|
1832
|
+
constructor(sdk, authConfig, debug = false) {
|
|
1616
1833
|
this.sdk = sdk;
|
|
1617
1834
|
this.authConfig = authConfig;
|
|
1618
1835
|
this.logger = createLogger("MedusaAuthProvider", debug);
|
|
1619
1836
|
this.storageManager = new AuthStorageManager(authConfig);
|
|
1620
|
-
this.baseUrl = baseUrl;
|
|
1621
|
-
this.publishableKey = publishableKey;
|
|
1622
1837
|
this.logger.info("Medusa Auth Provider initialized", {
|
|
1623
1838
|
method: authConfig.method,
|
|
1624
1839
|
storage: authConfig.storage
|
|
@@ -1630,17 +1845,28 @@ var MedusaAuthProvider = class {
|
|
|
1630
1845
|
async login(credentials) {
|
|
1631
1846
|
this.logger.debug("login", { email: credentials.email });
|
|
1632
1847
|
try {
|
|
1633
|
-
const
|
|
1848
|
+
const loginResult = await this.sdk.auth.login("customer", "emailpass", {
|
|
1634
1849
|
email: credentials.email,
|
|
1635
1850
|
password: credentials.password
|
|
1636
1851
|
});
|
|
1637
|
-
if (typeof
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1852
|
+
if (typeof loginResult !== "string") {
|
|
1853
|
+
if ("verification_required" in loginResult && loginResult.verification_required) {
|
|
1854
|
+
this.logger.warn("Authentication requires email verification", loginResult);
|
|
1855
|
+
return {
|
|
1856
|
+
requiresAction: true,
|
|
1857
|
+
token: loginResult.token
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
if ("location" in loginResult) {
|
|
1861
|
+
this.logger.warn("Authentication requires additional actions", loginResult);
|
|
1862
|
+
return {
|
|
1863
|
+
requiresAction: true,
|
|
1864
|
+
redirectUrl: loginResult.location
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
throw new Error("Unexpected login result type");
|
|
1643
1868
|
}
|
|
1869
|
+
const token = loginResult;
|
|
1644
1870
|
if (this.authConfig.method === "jwt") {
|
|
1645
1871
|
await this.storageManager.setToken(token);
|
|
1646
1872
|
}
|
|
@@ -1662,19 +1888,38 @@ var MedusaAuthProvider = class {
|
|
|
1662
1888
|
async register(data) {
|
|
1663
1889
|
this.logger.debug("register", { email: data.email });
|
|
1664
1890
|
try {
|
|
1665
|
-
|
|
1891
|
+
await this.sdk.auth.register("customer", "emailpass", {
|
|
1666
1892
|
email: data.email,
|
|
1667
1893
|
password: data.password
|
|
1668
1894
|
});
|
|
1669
|
-
if (typeof token !== "string") {
|
|
1670
|
-
throw new Error("Registration returned non-string token");
|
|
1671
|
-
}
|
|
1672
1895
|
const { customer } = await this.sdk.store.customer.create({
|
|
1673
1896
|
email: data.email,
|
|
1674
1897
|
first_name: data.first_name,
|
|
1675
1898
|
last_name: data.last_name,
|
|
1676
1899
|
phone: data.phone
|
|
1677
1900
|
});
|
|
1901
|
+
const loginResult = await this.sdk.auth.login("customer", "emailpass", {
|
|
1902
|
+
email: data.email,
|
|
1903
|
+
password: data.password
|
|
1904
|
+
});
|
|
1905
|
+
if (typeof loginResult !== "string") {
|
|
1906
|
+
if ("verification_required" in loginResult && loginResult.verification_required) {
|
|
1907
|
+
this.logger.warn("Registration requires email verification", loginResult);
|
|
1908
|
+
return {
|
|
1909
|
+
requiresAction: true,
|
|
1910
|
+
token: loginResult.token
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
if ("location" in loginResult) {
|
|
1914
|
+
this.logger.warn("Registration requires additional actions", loginResult);
|
|
1915
|
+
return {
|
|
1916
|
+
requiresAction: true,
|
|
1917
|
+
redirectUrl: loginResult.location
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
throw new Error("Unexpected login result after registration");
|
|
1921
|
+
}
|
|
1922
|
+
const token = loginResult;
|
|
1678
1923
|
if (this.authConfig.method === "jwt") {
|
|
1679
1924
|
await this.storageManager.setToken(token);
|
|
1680
1925
|
}
|
|
@@ -1894,81 +2139,6 @@ var MedusaAuthProvider = class {
|
|
|
1894
2139
|
void 0
|
|
1895
2140
|
);
|
|
1896
2141
|
}
|
|
1897
|
-
// ========================================
|
|
1898
|
-
// Multi-Tenant — Channel Link
|
|
1899
|
-
// ========================================
|
|
1900
|
-
/**
|
|
1901
|
-
* Link the currently authenticated customer to the storefront's sales channel.
|
|
1902
|
-
*
|
|
1903
|
-
* Must be called immediately after a successful `login()` so the customer
|
|
1904
|
-
* appears in the admin panel of the correct storefront channel.
|
|
1905
|
-
*
|
|
1906
|
-
* Implements `POST /store/thor/customers/me/channels`.
|
|
1907
|
-
* Idempotent — returns `{ linked: false, reason: 'already_linked' }` when
|
|
1908
|
-
* the link already exists.
|
|
1909
|
-
*
|
|
1910
|
-
* @throws {ProviderAPIError} When not authenticated (401) or channel cannot
|
|
1911
|
-
* be resolved from the publishable key (400).
|
|
1912
|
-
*/
|
|
1913
|
-
async linkCustomerToChannel() {
|
|
1914
|
-
this.logger.debug("linkCustomerToChannel");
|
|
1915
|
-
if (!this.baseUrl || !this.publishableKey) {
|
|
1916
|
-
throw new ProviderAPIError(
|
|
1917
|
-
"linkCustomerToChannel requires baseUrl and publishableKey to be set on the auth provider.",
|
|
1918
|
-
"CONFIGURATION_ERROR",
|
|
1919
|
-
void 0
|
|
1920
|
-
);
|
|
1921
|
-
}
|
|
1922
|
-
const token = await this.storageManager.getToken();
|
|
1923
|
-
const headers = {
|
|
1924
|
-
"x-publishable-api-key": this.publishableKey,
|
|
1925
|
-
"Content-Type": "application/json"
|
|
1926
|
-
};
|
|
1927
|
-
if (token) {
|
|
1928
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
1929
|
-
}
|
|
1930
|
-
try {
|
|
1931
|
-
const url = `${this.baseUrl}/store/thor/customers/me/channels`;
|
|
1932
|
-
const response = await fetch(url, {
|
|
1933
|
-
method: "POST",
|
|
1934
|
-
headers
|
|
1935
|
-
});
|
|
1936
|
-
if (response.status === 401) {
|
|
1937
|
-
throw new ProviderAPIError(
|
|
1938
|
-
"linkCustomerToChannel: not authenticated. Call login() first.",
|
|
1939
|
-
"NOT_AUTHENTICATED",
|
|
1940
|
-
401
|
|
1941
|
-
);
|
|
1942
|
-
}
|
|
1943
|
-
if (response.status === 400) {
|
|
1944
|
-
const body = await response.json().catch(() => ({}));
|
|
1945
|
-
throw new ProviderAPIError(
|
|
1946
|
-
`linkCustomerToChannel: could not resolve sales channel from publishable key. ${body?.message ?? ""}`.trim(),
|
|
1947
|
-
"CHANNEL_RESOLUTION_FAILED",
|
|
1948
|
-
400
|
|
1949
|
-
);
|
|
1950
|
-
}
|
|
1951
|
-
if (!response.ok) {
|
|
1952
|
-
const body = await response.json().catch(() => ({}));
|
|
1953
|
-
throw new ProviderAPIError(
|
|
1954
|
-
`linkCustomerToChannel failed with status ${response.status}: ${body?.message ?? "Unknown error"}`,
|
|
1955
|
-
"LINK_CHANNEL_FAILED",
|
|
1956
|
-
response.status
|
|
1957
|
-
);
|
|
1958
|
-
}
|
|
1959
|
-
const data = await response.json();
|
|
1960
|
-
this.logger.info("Customer linked to channel", { linked: data.linked, reason: data.reason });
|
|
1961
|
-
return data;
|
|
1962
|
-
} catch (error) {
|
|
1963
|
-
if (error instanceof ProviderAPIError) throw error;
|
|
1964
|
-
this.logger.error("linkCustomerToChannel failed", error);
|
|
1965
|
-
throw new ProviderAPIError(
|
|
1966
|
-
`linkCustomerToChannel failed: ${error.message}`,
|
|
1967
|
-
"LINK_CHANNEL_FAILED",
|
|
1968
|
-
error
|
|
1969
|
-
);
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
2142
|
};
|
|
1973
2143
|
|
|
1974
2144
|
// src/providers/medusa/payment.ts
|
|
@@ -2111,317 +2281,33 @@ async function getPaymentMethods(sdk, options) {
|
|
|
2111
2281
|
}
|
|
2112
2282
|
}
|
|
2113
2283
|
|
|
2114
|
-
// src/providers/medusa/
|
|
2115
|
-
var
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
constructor(config) {
|
|
2120
|
-
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
2121
|
-
this.apiKey = config.apiKey;
|
|
2122
|
-
this.logger = createLogger("MedusaAdminClient", config.debug);
|
|
2123
|
-
}
|
|
2124
|
-
// -----------------------------------------------------------------------
|
|
2125
|
-
// Private — HTTP helper
|
|
2126
|
-
// -----------------------------------------------------------------------
|
|
2127
|
-
async request(method, path, body) {
|
|
2128
|
-
const url = `${this.baseUrl}${path}`;
|
|
2129
|
-
this.logger.debug(`[admin] ${method} ${path}`);
|
|
2130
|
-
const headers = {
|
|
2131
|
-
"x-medusa-access-token": this.apiKey,
|
|
2132
|
-
"Content-Type": "application/json"
|
|
2133
|
-
};
|
|
2134
|
-
const init = {
|
|
2135
|
-
method,
|
|
2136
|
-
headers,
|
|
2137
|
-
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
2138
|
-
};
|
|
2139
|
-
let response;
|
|
2140
|
-
try {
|
|
2141
|
-
response = await fetch(url, init);
|
|
2142
|
-
} catch (networkError) {
|
|
2143
|
-
throw new ProviderAPIError(
|
|
2144
|
-
`[admin] Network error on ${method} ${path}: ${networkError.message}`,
|
|
2145
|
-
"NETWORK_ERROR",
|
|
2146
|
-
networkError
|
|
2147
|
-
);
|
|
2148
|
-
}
|
|
2149
|
-
if (!response.ok) {
|
|
2150
|
-
let message = `HTTP ${response.status}`;
|
|
2151
|
-
try {
|
|
2152
|
-
const json = await response.json();
|
|
2153
|
-
message = json?.message ?? message;
|
|
2154
|
-
} catch {
|
|
2155
|
-
}
|
|
2156
|
-
throw new ProviderAPIError(
|
|
2157
|
-
`[admin] ${method} ${path} failed: ${message}`,
|
|
2158
|
-
`ADMIN_API_${response.status}`,
|
|
2159
|
-
response.status
|
|
2160
|
-
);
|
|
2161
|
-
}
|
|
2162
|
-
return response.json();
|
|
2163
|
-
}
|
|
2164
|
-
// -----------------------------------------------------------------------
|
|
2165
|
-
// 3.1 — Sales Channel: Customers
|
|
2166
|
-
// -----------------------------------------------------------------------
|
|
2284
|
+
// src/providers/medusa/index.ts
|
|
2285
|
+
var MEDUSA_CART_RETRIEVE_FIELDS = "+items,+items.variant,+items.variant.product,+items.variant.product.thumbnail,+items.variant.options,+items.variant.options.option";
|
|
2286
|
+
var MedusaProvider = class {
|
|
2287
|
+
name = "medusa";
|
|
2288
|
+
version = "2.0.0";
|
|
2167
2289
|
/**
|
|
2168
|
-
*
|
|
2169
|
-
*
|
|
2170
|
-
* `GET /
|
|
2290
|
+
* Medusa backend capabilities.
|
|
2291
|
+
* Starts with static defaults, replaced by dynamic values from
|
|
2292
|
+
* `GET /store/thor/capabilities` once the fetch completes.
|
|
2171
2293
|
*/
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
"GET",
|
|
2176
|
-
`/admin/thor/sales-channels/${salesChannelId}/customers?limit=${limit}&offset=${offset}`
|
|
2177
|
-
);
|
|
2178
|
-
}
|
|
2179
|
-
// -----------------------------------------------------------------------
|
|
2180
|
-
// 3.2 — Sales Channel: API Keys
|
|
2181
|
-
// -----------------------------------------------------------------------
|
|
2294
|
+
_dynamicCapabilities = null;
|
|
2295
|
+
/** Promise that resolves when capabilities fetch completes (success or failure) */
|
|
2296
|
+
_capabilitiesPromise = null;
|
|
2182
2297
|
/**
|
|
2183
|
-
*
|
|
2184
|
-
*
|
|
2185
|
-
* `GET /admin/thor/sales-channels/:id/api-keys`
|
|
2298
|
+
* Getter returns dynamic capabilities if available, otherwise static fallback.
|
|
2186
2299
|
*/
|
|
2187
|
-
|
|
2188
|
-
return this.
|
|
2189
|
-
"GET",
|
|
2190
|
-
`/admin/thor/sales-channels/${salesChannelId}/api-keys`
|
|
2191
|
-
);
|
|
2300
|
+
get capabilities() {
|
|
2301
|
+
return this._dynamicCapabilities ?? MEDUSA_CAPABILITIES;
|
|
2192
2302
|
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
async getChannelCategories(salesChannelId, options = {}) {
|
|
2202
|
-
const { limit = 20, offset = 0, include_descendants = false } = options;
|
|
2203
|
-
const qs = new URLSearchParams({
|
|
2204
|
-
limit: String(limit),
|
|
2205
|
-
offset: String(offset),
|
|
2206
|
-
include_descendants: String(include_descendants)
|
|
2207
|
-
});
|
|
2208
|
-
return this.request(
|
|
2209
|
-
"GET",
|
|
2210
|
-
`/admin/thor/sales-channels/${salesChannelId}/categories?${qs}`
|
|
2211
|
-
);
|
|
2212
|
-
}
|
|
2213
|
-
// -----------------------------------------------------------------------
|
|
2214
|
-
// 3.4 — Category: Sales Channels
|
|
2215
|
-
// -----------------------------------------------------------------------
|
|
2216
|
-
/**
|
|
2217
|
-
* Sales channels to which a category is assigned.
|
|
2218
|
-
*
|
|
2219
|
-
* `GET /admin/thor/categories/:id/sales-channels`
|
|
2220
|
-
*/
|
|
2221
|
-
async getCategorySalesChannels(categoryId) {
|
|
2222
|
-
return this.request(
|
|
2223
|
-
"GET",
|
|
2224
|
-
`/admin/thor/categories/${categoryId}/sales-channels`
|
|
2225
|
-
);
|
|
2226
|
-
}
|
|
2227
|
-
// -----------------------------------------------------------------------
|
|
2228
|
-
// 3.5 — Category: Assign to Sales Channels
|
|
2229
|
-
// -----------------------------------------------------------------------
|
|
2230
|
-
/**
|
|
2231
|
-
* Assign a category to one or more sales channels (idempotent).
|
|
2232
|
-
*
|
|
2233
|
-
* `POST /admin/thor/categories/:id/sales-channels`
|
|
2234
|
-
*/
|
|
2235
|
-
async assignCategoryToChannels(categoryId, body) {
|
|
2236
|
-
return this.request(
|
|
2237
|
-
"POST",
|
|
2238
|
-
`/admin/thor/categories/${categoryId}/sales-channels`,
|
|
2239
|
-
body
|
|
2240
|
-
);
|
|
2241
|
-
}
|
|
2242
|
-
// -----------------------------------------------------------------------
|
|
2243
|
-
// 3.6 — Category: Unassign from Sales Channels
|
|
2244
|
-
// -----------------------------------------------------------------------
|
|
2245
|
-
/**
|
|
2246
|
-
* Remove category assignment from one or more sales channels (idempotent).
|
|
2247
|
-
*
|
|
2248
|
-
* `DELETE /admin/thor/categories/:id/sales-channels`
|
|
2249
|
-
*/
|
|
2250
|
-
async unassignCategoryFromChannels(categoryId, body) {
|
|
2251
|
-
return this.request(
|
|
2252
|
-
"DELETE",
|
|
2253
|
-
`/admin/thor/categories/${categoryId}/sales-channels`,
|
|
2254
|
-
body
|
|
2255
|
-
);
|
|
2256
|
-
}
|
|
2257
|
-
// -----------------------------------------------------------------------
|
|
2258
|
-
// 3.7 — Collection: Sales Channels
|
|
2259
|
-
// -----------------------------------------------------------------------
|
|
2260
|
-
/**
|
|
2261
|
-
* Sales channels to which a collection is assigned.
|
|
2262
|
-
*
|
|
2263
|
-
* `GET /admin/thor/collections/:id/sales-channels`
|
|
2264
|
-
*/
|
|
2265
|
-
async getCollectionSalesChannels(collectionId) {
|
|
2266
|
-
return this.request(
|
|
2267
|
-
"GET",
|
|
2268
|
-
`/admin/thor/collections/${collectionId}/sales-channels`
|
|
2269
|
-
);
|
|
2270
|
-
}
|
|
2271
|
-
// -----------------------------------------------------------------------
|
|
2272
|
-
// 3.8 — Collection: Assign to Sales Channels
|
|
2273
|
-
// -----------------------------------------------------------------------
|
|
2274
|
-
/**
|
|
2275
|
-
* Assign a collection to one or more sales channels (idempotent).
|
|
2276
|
-
*
|
|
2277
|
-
* `POST /admin/thor/collections/:id/sales-channels`
|
|
2278
|
-
*/
|
|
2279
|
-
async assignCollectionToChannels(collectionId, body) {
|
|
2280
|
-
return this.request(
|
|
2281
|
-
"POST",
|
|
2282
|
-
`/admin/thor/collections/${collectionId}/sales-channels`,
|
|
2283
|
-
body
|
|
2284
|
-
);
|
|
2285
|
-
}
|
|
2286
|
-
// -----------------------------------------------------------------------
|
|
2287
|
-
// 3.9 — Collection: Unassign from Sales Channels
|
|
2288
|
-
// -----------------------------------------------------------------------
|
|
2289
|
-
/**
|
|
2290
|
-
* Remove collection assignment from one or more sales channels (idempotent).
|
|
2291
|
-
*
|
|
2292
|
-
* `DELETE /admin/thor/collections/:id/sales-channels`
|
|
2293
|
-
*/
|
|
2294
|
-
async unassignCollectionFromChannels(collectionId, body) {
|
|
2295
|
-
return this.request(
|
|
2296
|
-
"DELETE",
|
|
2297
|
-
`/admin/thor/collections/${collectionId}/sales-channels`,
|
|
2298
|
-
body
|
|
2299
|
-
);
|
|
2300
|
-
}
|
|
2301
|
-
// -----------------------------------------------------------------------
|
|
2302
|
-
// 3.10 — Storefront Config: Read
|
|
2303
|
-
// -----------------------------------------------------------------------
|
|
2304
|
-
/**
|
|
2305
|
-
* Read the visual storefront configuration for a channel.
|
|
2306
|
-
* `:id` is the `sales_channel_id`.
|
|
2307
|
-
*
|
|
2308
|
-
* `GET /admin/thor/storefront-config/:id`
|
|
2309
|
-
*/
|
|
2310
|
-
async getStorefrontConfig(salesChannelId) {
|
|
2311
|
-
return this.request(
|
|
2312
|
-
"GET",
|
|
2313
|
-
`/admin/thor/storefront-config/${salesChannelId}`
|
|
2314
|
-
);
|
|
2315
|
-
}
|
|
2316
|
-
// -----------------------------------------------------------------------
|
|
2317
|
-
// 3.11 — Storefront Config: Update
|
|
2318
|
-
// -----------------------------------------------------------------------
|
|
2319
|
-
/**
|
|
2320
|
-
* Update the visual storefront configuration for a channel.
|
|
2321
|
-
* Triggers the `storefront_config.updated` event → ISR webhook.
|
|
2322
|
-
*
|
|
2323
|
-
* `PUT /admin/thor/storefront-config/:id`
|
|
2324
|
-
*/
|
|
2325
|
-
async updateStorefrontConfig(salesChannelId, body) {
|
|
2326
|
-
return this.request(
|
|
2327
|
-
"PUT",
|
|
2328
|
-
`/admin/thor/storefront-config/${salesChannelId}`,
|
|
2329
|
-
body
|
|
2330
|
-
);
|
|
2331
|
-
}
|
|
2332
|
-
// -----------------------------------------------------------------------
|
|
2333
|
-
// 3.12 — Site Designer Config: Read
|
|
2334
|
-
// -----------------------------------------------------------------------
|
|
2335
|
-
/**
|
|
2336
|
-
* Read the active Site Designer configuration for a channel, including the
|
|
2337
|
-
* last 10 history entries.
|
|
2338
|
-
*
|
|
2339
|
-
* `GET /admin/thor/site-config/:sales_channel_id`
|
|
2340
|
-
*/
|
|
2341
|
-
async getSiteConfig(salesChannelId) {
|
|
2342
|
-
return this.request(
|
|
2343
|
-
"GET",
|
|
2344
|
-
`/admin/thor/site-config/${salesChannelId}`
|
|
2345
|
-
);
|
|
2346
|
-
}
|
|
2347
|
-
// -----------------------------------------------------------------------
|
|
2348
|
-
// 3.13 — Site Designer Config: Update
|
|
2349
|
-
// -----------------------------------------------------------------------
|
|
2350
|
-
/**
|
|
2351
|
-
* Update the Site Designer configuration for a channel.
|
|
2352
|
-
* Creates a new history entry on every PUT.
|
|
2353
|
-
*
|
|
2354
|
-
* `PUT /admin/thor/site-config/:sales_channel_id`
|
|
2355
|
-
*/
|
|
2356
|
-
async updateSiteConfig(salesChannelId, body) {
|
|
2357
|
-
return this.request(
|
|
2358
|
-
"PUT",
|
|
2359
|
-
`/admin/thor/site-config/${salesChannelId}`,
|
|
2360
|
-
body
|
|
2361
|
-
);
|
|
2362
|
-
}
|
|
2363
|
-
// -----------------------------------------------------------------------
|
|
2364
|
-
// 3.14 — Site Designer Config: History
|
|
2365
|
-
// -----------------------------------------------------------------------
|
|
2366
|
-
/**
|
|
2367
|
-
* Full paginated history of Site Designer versions for a channel.
|
|
2368
|
-
* Entries are immutable — they are never deleted.
|
|
2369
|
-
*
|
|
2370
|
-
* `GET /admin/thor/site-config/:sales_channel_id/history`
|
|
2371
|
-
*/
|
|
2372
|
-
async getSiteConfigHistory(salesChannelId, options = {}) {
|
|
2373
|
-
const { limit = 20, offset = 0 } = options;
|
|
2374
|
-
return this.request(
|
|
2375
|
-
"GET",
|
|
2376
|
-
`/admin/thor/site-config/${salesChannelId}/history?limit=${limit}&offset=${offset}`
|
|
2377
|
-
);
|
|
2378
|
-
}
|
|
2379
|
-
// -----------------------------------------------------------------------
|
|
2380
|
-
// 3.15 — Site Designer Config: Restore
|
|
2381
|
-
// -----------------------------------------------------------------------
|
|
2382
|
-
/**
|
|
2383
|
-
* Restore a previous Site Designer version.
|
|
2384
|
-
* Internally runs the update workflow with the stored config of the target
|
|
2385
|
-
* version — this creates a **new** history entry and does NOT mutate past
|
|
2386
|
-
* entries.
|
|
2387
|
-
*
|
|
2388
|
-
* `POST /admin/thor/site-config/:sales_channel_id/restore/:version`
|
|
2389
|
-
*/
|
|
2390
|
-
async restoreSiteConfig(salesChannelId, version) {
|
|
2391
|
-
return this.request(
|
|
2392
|
-
"POST",
|
|
2393
|
-
`/admin/thor/site-config/${salesChannelId}/restore/${encodeURIComponent(version)}`
|
|
2394
|
-
);
|
|
2395
|
-
}
|
|
2396
|
-
};
|
|
2397
|
-
|
|
2398
|
-
// src/providers/medusa/index.ts
|
|
2399
|
-
var MEDUSA_CART_RETRIEVE_FIELDS = "+items,+items.variant,+items.variant.product,+items.variant.product.thumbnail,+items.variant.options,+items.variant.options.option";
|
|
2400
|
-
var MedusaProvider = class {
|
|
2401
|
-
name = "medusa";
|
|
2402
|
-
version = "2.0.0";
|
|
2403
|
-
/**
|
|
2404
|
-
* Medusa backend capabilities.
|
|
2405
|
-
* Starts with static defaults, replaced by dynamic values from
|
|
2406
|
-
* `GET /store/thor/capabilities` once the fetch completes.
|
|
2407
|
-
*/
|
|
2408
|
-
_dynamicCapabilities = null;
|
|
2409
|
-
/** Promise that resolves when capabilities fetch completes (success or failure) */
|
|
2410
|
-
_capabilitiesPromise = null;
|
|
2411
|
-
/**
|
|
2412
|
-
* Getter returns dynamic capabilities if available, otherwise static fallback.
|
|
2413
|
-
*/
|
|
2414
|
-
get capabilities() {
|
|
2415
|
-
return this._dynamicCapabilities ?? MEDUSA_CAPABILITIES;
|
|
2416
|
-
}
|
|
2417
|
-
sdk;
|
|
2418
|
-
logger;
|
|
2419
|
-
currencyCode;
|
|
2420
|
-
retryConfig;
|
|
2421
|
-
baseUrl;
|
|
2422
|
-
publishableKey;
|
|
2423
|
-
/** Secret API key for admin operations (if configured). */
|
|
2424
|
-
adminApiKey;
|
|
2303
|
+
sdk;
|
|
2304
|
+
logger;
|
|
2305
|
+
currencyCode;
|
|
2306
|
+
retryConfig;
|
|
2307
|
+
baseUrl;
|
|
2308
|
+
publishableKey;
|
|
2309
|
+
/** Secret API key for admin operations (if configured). */
|
|
2310
|
+
adminApiKey;
|
|
2425
2311
|
/**
|
|
2426
2312
|
* Dedicated SDK instance for admin calls.
|
|
2427
2313
|
* Separate from `this.sdk` to prevent the admin API key from being overwritten
|
|
@@ -2429,18 +2315,6 @@ var MedusaProvider = class {
|
|
|
2429
2315
|
* Only created when adminConfig.apiKey is provided.
|
|
2430
2316
|
*/
|
|
2431
2317
|
sdkAdmin;
|
|
2432
|
-
/**
|
|
2433
|
-
* Admin client for Thor multi-tenant endpoints (`/admin/thor/`).
|
|
2434
|
-
* Available when `adminConfig.apiKey` is provided.
|
|
2435
|
-
*
|
|
2436
|
-
* @example
|
|
2437
|
-
* ```typescript
|
|
2438
|
-
* const { customers } = await commerce.admin.getChannelCustomers('sc_01JXXXXX');
|
|
2439
|
-
* const { api_keys } = await commerce.admin.getChannelApiKeys('sc_01JXXXXX');
|
|
2440
|
-
* await commerce.admin.updateStorefrontConfig('sc_01JXXXXX', { theme_accent_color: '#EF4444', ... });
|
|
2441
|
-
* ```
|
|
2442
|
-
*/
|
|
2443
|
-
admin;
|
|
2444
2318
|
/** Cached distinct product status values — rarely changes, cached for process lifetime */
|
|
2445
2319
|
_cachedProductStatuses = null;
|
|
2446
2320
|
/**
|
|
@@ -2511,15 +2385,10 @@ var MedusaProvider = class {
|
|
|
2511
2385
|
debug: config.debug ?? false
|
|
2512
2386
|
// No publishableKey or auth config: this instance is admin-only, no JWT needed
|
|
2513
2387
|
});
|
|
2514
|
-
this.admin = new MedusaAdminClient({
|
|
2515
|
-
baseUrl: config.baseUrl,
|
|
2516
|
-
apiKey: config.adminConfig.apiKey,
|
|
2517
|
-
debug: config.debug
|
|
2518
|
-
});
|
|
2519
2388
|
this.logger.info("[Admin Auth] Dedicated admin SDK instance created with API key");
|
|
2520
2389
|
}
|
|
2521
2390
|
if (config.auth) {
|
|
2522
|
-
this.auth = new MedusaAuthProvider(this.sdk, config.auth, config.debug
|
|
2391
|
+
this.auth = new MedusaAuthProvider(this.sdk, config.auth, config.debug);
|
|
2523
2392
|
this.logger.info("Auth provider initialized");
|
|
2524
2393
|
}
|
|
2525
2394
|
this._capabilitiesPromise = this._fetchCapabilities();
|
|
@@ -5482,187 +5351,2794 @@ var MedusaProvider = class {
|
|
|
5482
5351
|
);
|
|
5483
5352
|
}
|
|
5484
5353
|
}
|
|
5485
|
-
/**
|
|
5486
|
-
* Medusa always requires storefront configuration for multi-tenant support.
|
|
5487
|
-
*/
|
|
5488
|
-
isStorefrontRequired() {
|
|
5489
|
-
return true;
|
|
5354
|
+
/**
|
|
5355
|
+
* Medusa always requires storefront configuration for multi-tenant support.
|
|
5356
|
+
*/
|
|
5357
|
+
isStorefrontRequired() {
|
|
5358
|
+
return true;
|
|
5359
|
+
}
|
|
5360
|
+
// ============================================
|
|
5361
|
+
// Thor Plugin — Storefront Configuration
|
|
5362
|
+
// ============================================
|
|
5363
|
+
/** In-memory cache for storefront config (60s TTL) */
|
|
5364
|
+
_storefrontConfigCache = null;
|
|
5365
|
+
/** In-memory cache for designer config (60s TTL) */
|
|
5366
|
+
_designerConfigCache = null;
|
|
5367
|
+
/** Cache TTL for Thor plugin configs (60 seconds) */
|
|
5368
|
+
THOR_CONFIG_CACHE_TTL = 60 * 1e3;
|
|
5369
|
+
/**
|
|
5370
|
+
* Get per-channel storefront configuration from Thor Commerce plugin.
|
|
5371
|
+
* Calls `GET /store/thor/config`.
|
|
5372
|
+
*
|
|
5373
|
+
* @returns StorefrontConfig or null if plugin is not installed / config not set
|
|
5374
|
+
*/
|
|
5375
|
+
async getStorefrontConfig() {
|
|
5376
|
+
if (this._storefrontConfigCache && Date.now() < this._storefrontConfigCache.expiresAt) {
|
|
5377
|
+
return this._storefrontConfigCache.config;
|
|
5378
|
+
}
|
|
5379
|
+
try {
|
|
5380
|
+
const url = `${this.baseUrl}/store/thor/config`;
|
|
5381
|
+
const response = await fetchWithRetry(
|
|
5382
|
+
url,
|
|
5383
|
+
{
|
|
5384
|
+
method: "GET",
|
|
5385
|
+
headers: {
|
|
5386
|
+
"x-publishable-api-key": this.publishableKey,
|
|
5387
|
+
"Content-Type": "application/json"
|
|
5388
|
+
}
|
|
5389
|
+
},
|
|
5390
|
+
this.retryConfig
|
|
5391
|
+
);
|
|
5392
|
+
if (response.status === 404) {
|
|
5393
|
+
this._storefrontConfigCache = { config: null, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5394
|
+
return null;
|
|
5395
|
+
}
|
|
5396
|
+
if (!response.ok) {
|
|
5397
|
+
this.logger.warn("[StorefrontConfig] Non-OK response", { status: response.status });
|
|
5398
|
+
return null;
|
|
5399
|
+
}
|
|
5400
|
+
const data = await response.json();
|
|
5401
|
+
const config = transformStorefrontConfig(data.storefront_config ?? data);
|
|
5402
|
+
this._storefrontConfigCache = { config, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5403
|
+
this.logger.debug("[StorefrontConfig] Loaded from backend", { id: config.id });
|
|
5404
|
+
return config;
|
|
5405
|
+
} catch (error) {
|
|
5406
|
+
this.logger.warn("[StorefrontConfig] Failed to fetch", {
|
|
5407
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5408
|
+
});
|
|
5409
|
+
return null;
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
/**
|
|
5413
|
+
* Get active Site Designer configuration from Thor Commerce plugin.
|
|
5414
|
+
* Calls `GET /store/thor/site-config`.
|
|
5415
|
+
*
|
|
5416
|
+
* @returns Versioned DesignerConfig or null if plugin is not installed / config not set
|
|
5417
|
+
*/
|
|
5418
|
+
async getDesignerConfig() {
|
|
5419
|
+
if (this._designerConfigCache && Date.now() < this._designerConfigCache.expiresAt) {
|
|
5420
|
+
return this._designerConfigCache.result;
|
|
5421
|
+
}
|
|
5422
|
+
try {
|
|
5423
|
+
const url = `${this.baseUrl}/store/thor/site-config`;
|
|
5424
|
+
const response = await fetchWithRetry(
|
|
5425
|
+
url,
|
|
5426
|
+
{
|
|
5427
|
+
method: "GET",
|
|
5428
|
+
headers: {
|
|
5429
|
+
"x-publishable-api-key": this.publishableKey,
|
|
5430
|
+
"Content-Type": "application/json"
|
|
5431
|
+
}
|
|
5432
|
+
},
|
|
5433
|
+
this.retryConfig
|
|
5434
|
+
);
|
|
5435
|
+
if (response.status === 404) {
|
|
5436
|
+
this._designerConfigCache = { result: null, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5437
|
+
return null;
|
|
5438
|
+
}
|
|
5439
|
+
if (!response.ok) {
|
|
5440
|
+
this.logger.warn("[DesignerConfig] Non-OK response", { status: response.status });
|
|
5441
|
+
return null;
|
|
5442
|
+
}
|
|
5443
|
+
const data = await response.json();
|
|
5444
|
+
const result = {
|
|
5445
|
+
version: data.version ?? "",
|
|
5446
|
+
config: data.config
|
|
5447
|
+
};
|
|
5448
|
+
this._designerConfigCache = { result, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5449
|
+
this.logger.debug("[DesignerConfig] Loaded from backend", { version: result.version });
|
|
5450
|
+
return result;
|
|
5451
|
+
} catch (error) {
|
|
5452
|
+
this.logger.warn("[DesignerConfig] Failed to fetch", {
|
|
5453
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5454
|
+
});
|
|
5455
|
+
return null;
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
5458
|
+
// ============================================
|
|
5459
|
+
// Thor Plugin — Admin Site Designer
|
|
5460
|
+
// ============================================
|
|
5461
|
+
/**
|
|
5462
|
+
* Get admin Site Designer config + history for a sales channel.
|
|
5463
|
+
* Calls `GET /admin/thor/site-config/:salesChannelId`.
|
|
5464
|
+
*/
|
|
5465
|
+
async getAdminDesignerConfig(salesChannelId) {
|
|
5466
|
+
return this.makeAdminRequest(async () => {
|
|
5467
|
+
const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}`;
|
|
5468
|
+
let data;
|
|
5469
|
+
try {
|
|
5470
|
+
data = await this.sdk.client.fetch(path, { method: "GET" });
|
|
5471
|
+
} catch (error) {
|
|
5472
|
+
if (error?.status === 404) return null;
|
|
5473
|
+
throw new ProviderAPIError(
|
|
5474
|
+
`Failed to fetch admin designer config: ${error?.message ?? error?.status}`,
|
|
5475
|
+
"medusa",
|
|
5476
|
+
error?.status
|
|
5477
|
+
);
|
|
5478
|
+
}
|
|
5479
|
+
return {
|
|
5480
|
+
version: data.version ?? "",
|
|
5481
|
+
config: data.config,
|
|
5482
|
+
history: data.history
|
|
5483
|
+
};
|
|
5484
|
+
});
|
|
5485
|
+
}
|
|
5486
|
+
/**
|
|
5487
|
+
* Update the Site Designer config for a sales channel.
|
|
5488
|
+
* Calls `PUT /admin/thor/site-config/:salesChannelId`.
|
|
5489
|
+
*/
|
|
5490
|
+
async updateAdminDesignerConfig(salesChannelId, config) {
|
|
5491
|
+
return this.makeAdminRequest(async () => {
|
|
5492
|
+
const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}`;
|
|
5493
|
+
let data;
|
|
5494
|
+
try {
|
|
5495
|
+
data = await this.sdk.client.fetch(path, {
|
|
5496
|
+
method: "PUT",
|
|
5497
|
+
body: JSON.stringify({ config }),
|
|
5498
|
+
headers: { "Content-Type": "application/json" }
|
|
5499
|
+
});
|
|
5500
|
+
} catch (error) {
|
|
5501
|
+
throw new ProviderAPIError(
|
|
5502
|
+
`Failed to update admin designer config: ${error?.message ?? error?.status}`,
|
|
5503
|
+
"medusa",
|
|
5504
|
+
error?.status
|
|
5505
|
+
);
|
|
5506
|
+
}
|
|
5507
|
+
return {
|
|
5508
|
+
version: data.version ?? "",
|
|
5509
|
+
config: data.config
|
|
5510
|
+
};
|
|
5511
|
+
});
|
|
5512
|
+
}
|
|
5513
|
+
/**
|
|
5514
|
+
* Restore a previous Site Designer config version.
|
|
5515
|
+
* Calls `POST /admin/thor/site-config/:salesChannelId/restore/:version`.
|
|
5516
|
+
*/
|
|
5517
|
+
async restoreAdminDesignerConfig(salesChannelId, version) {
|
|
5518
|
+
return this.makeAdminRequest(async () => {
|
|
5519
|
+
const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}/restore/${encodeURIComponent(version)}`;
|
|
5520
|
+
let data;
|
|
5521
|
+
try {
|
|
5522
|
+
data = await this.sdk.client.fetch(path, { method: "POST" });
|
|
5523
|
+
} catch (error) {
|
|
5524
|
+
throw new ProviderAPIError(
|
|
5525
|
+
`Failed to restore admin designer config: ${error?.message ?? error?.status}`,
|
|
5526
|
+
"medusa",
|
|
5527
|
+
error?.status
|
|
5528
|
+
);
|
|
5529
|
+
}
|
|
5530
|
+
return {
|
|
5531
|
+
version: data.version ?? "",
|
|
5532
|
+
config: data.config
|
|
5533
|
+
};
|
|
5534
|
+
});
|
|
5535
|
+
}
|
|
5536
|
+
};
|
|
5537
|
+
|
|
5538
|
+
// src/providers/shopify/transforms.ts
|
|
5539
|
+
function transformMoney2(shopifyMoney) {
|
|
5540
|
+
return {
|
|
5541
|
+
amount: shopifyMoney?.amount ?? "0.00",
|
|
5542
|
+
currencyCode: shopifyMoney?.currencyCode?.toUpperCase() ?? "USD"
|
|
5543
|
+
};
|
|
5544
|
+
}
|
|
5545
|
+
function transformImage2(shopifyImage) {
|
|
5546
|
+
return {
|
|
5547
|
+
url: shopifyImage?.url ?? "",
|
|
5548
|
+
altText: shopifyImage?.altText ?? "",
|
|
5549
|
+
width: shopifyImage?.width ?? 800,
|
|
5550
|
+
height: shopifyImage?.height ?? 800
|
|
5551
|
+
};
|
|
5552
|
+
}
|
|
5553
|
+
function transformVariant(raw) {
|
|
5554
|
+
return {
|
|
5555
|
+
id: raw.id,
|
|
5556
|
+
title: raw.title ?? "",
|
|
5557
|
+
availableForSale: raw.availableForSale ?? true,
|
|
5558
|
+
price: transformMoney2(raw.price),
|
|
5559
|
+
compareAtPrice: raw.compareAtPrice ? transformMoney2(raw.compareAtPrice) : void 0,
|
|
5560
|
+
selectedOptions: (raw.selectedOptions ?? []).map((opt) => ({
|
|
5561
|
+
name: opt.name,
|
|
5562
|
+
value: opt.value
|
|
5563
|
+
})),
|
|
5564
|
+
image: raw.image ? transformImage2(raw.image) : void 0
|
|
5565
|
+
};
|
|
5566
|
+
}
|
|
5567
|
+
function transformOption(raw) {
|
|
5568
|
+
return {
|
|
5569
|
+
id: raw.id,
|
|
5570
|
+
name: raw.name,
|
|
5571
|
+
values: raw.values ?? []
|
|
5572
|
+
};
|
|
5573
|
+
}
|
|
5574
|
+
function transformProduct2(raw) {
|
|
5575
|
+
const images = (raw.images?.edges ?? []).map((edge) => transformImage2(edge.node));
|
|
5576
|
+
const variants = (raw.variants?.edges ?? []).map((edge) => transformVariant(edge.node));
|
|
5577
|
+
return {
|
|
5578
|
+
id: raw.id,
|
|
5579
|
+
handle: raw.handle,
|
|
5580
|
+
title: raw.title,
|
|
5581
|
+
description: raw.description ?? "",
|
|
5582
|
+
descriptionHtml: raw.descriptionHtml ?? "",
|
|
5583
|
+
availableForSale: raw.availableForSale ?? true,
|
|
5584
|
+
featuredImage: raw.featuredImage ? transformImage2(raw.featuredImage) : images[0] ?? { url: "", altText: "", width: 800, height: 800 },
|
|
5585
|
+
images,
|
|
5586
|
+
options: (raw.options ?? []).map(transformOption),
|
|
5587
|
+
variants,
|
|
5588
|
+
priceRange: {
|
|
5589
|
+
minVariantPrice: transformMoney2(raw.priceRange?.minVariantPrice),
|
|
5590
|
+
maxVariantPrice: transformMoney2(raw.priceRange?.maxVariantPrice)
|
|
5591
|
+
},
|
|
5592
|
+
seo: {
|
|
5593
|
+
title: raw.seo?.title ?? raw.title ?? "",
|
|
5594
|
+
description: raw.seo?.description ?? raw.description ?? ""
|
|
5595
|
+
},
|
|
5596
|
+
tags: raw.tags ?? [],
|
|
5597
|
+
createdAt: raw.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5598
|
+
updatedAt: raw.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5599
|
+
vendor: raw.vendor,
|
|
5600
|
+
productType: raw.productType
|
|
5601
|
+
};
|
|
5602
|
+
}
|
|
5603
|
+
function transformCollection2(raw) {
|
|
5604
|
+
return {
|
|
5605
|
+
id: raw.id ?? "",
|
|
5606
|
+
handle: raw.handle,
|
|
5607
|
+
title: raw.title,
|
|
5608
|
+
description: raw.description ?? "",
|
|
5609
|
+
seo: {
|
|
5610
|
+
title: raw.seo?.title ?? raw.title ?? "",
|
|
5611
|
+
description: raw.seo?.description ?? raw.description ?? ""
|
|
5612
|
+
},
|
|
5613
|
+
updatedAt: raw.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5614
|
+
path: `/collections/${raw.handle}`
|
|
5615
|
+
};
|
|
5616
|
+
}
|
|
5617
|
+
function transformCartItem2(raw) {
|
|
5618
|
+
const merchandise = raw.merchandise ?? {};
|
|
5619
|
+
const product = merchandise.product ?? {};
|
|
5620
|
+
return {
|
|
5621
|
+
id: raw.id,
|
|
5622
|
+
quantity: raw.quantity ?? 1,
|
|
5623
|
+
cost: {
|
|
5624
|
+
totalAmount: transformMoney2(raw.cost?.totalAmount)
|
|
5625
|
+
},
|
|
5626
|
+
merchandise: {
|
|
5627
|
+
id: merchandise.id ?? "",
|
|
5628
|
+
title: merchandise.title ?? "",
|
|
5629
|
+
selectedOptions: (merchandise.selectedOptions ?? []).map((opt) => ({
|
|
5630
|
+
name: opt.name,
|
|
5631
|
+
value: opt.value
|
|
5632
|
+
})),
|
|
5633
|
+
product: {
|
|
5634
|
+
id: product.id ?? "",
|
|
5635
|
+
handle: product.handle ?? "",
|
|
5636
|
+
title: product.title ?? "",
|
|
5637
|
+
featuredImage: product.featuredImage ? transformImage2(product.featuredImage) : { url: "", altText: "", width: 800, height: 800 }
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
};
|
|
5641
|
+
}
|
|
5642
|
+
function transformCart2(raw) {
|
|
5643
|
+
const lines = (raw.lines?.edges ?? []).map((edge) => transformCartItem2(edge.node));
|
|
5644
|
+
return {
|
|
5645
|
+
id: raw.id,
|
|
5646
|
+
checkoutUrl: raw.checkoutUrl ?? "",
|
|
5647
|
+
totalQuantity: raw.totalQuantity ?? 0,
|
|
5648
|
+
cost: {
|
|
5649
|
+
subtotalAmount: transformMoney2(raw.cost?.subtotalAmount),
|
|
5650
|
+
totalAmount: transformMoney2(raw.cost?.totalAmount),
|
|
5651
|
+
totalTaxAmount: raw.cost?.totalTaxAmount ? transformMoney2(raw.cost.totalTaxAmount) : void 0
|
|
5652
|
+
},
|
|
5653
|
+
lines
|
|
5654
|
+
};
|
|
5655
|
+
}
|
|
5656
|
+
|
|
5657
|
+
// src/providers/shopify/queries.ts
|
|
5658
|
+
var PRODUCT_FRAGMENT = `
|
|
5659
|
+
fragment ProductFields on Product {
|
|
5660
|
+
id
|
|
5661
|
+
title
|
|
5662
|
+
handle
|
|
5663
|
+
description
|
|
5664
|
+
descriptionHtml
|
|
5665
|
+
vendor
|
|
5666
|
+
productType
|
|
5667
|
+
tags
|
|
5668
|
+
availableForSale
|
|
5669
|
+
createdAt
|
|
5670
|
+
updatedAt
|
|
5671
|
+
seo {
|
|
5672
|
+
title
|
|
5673
|
+
description
|
|
5674
|
+
}
|
|
5675
|
+
featuredImage {
|
|
5676
|
+
url
|
|
5677
|
+
altText
|
|
5678
|
+
width
|
|
5679
|
+
height
|
|
5680
|
+
}
|
|
5681
|
+
images(first: 10) {
|
|
5682
|
+
edges {
|
|
5683
|
+
node {
|
|
5684
|
+
url
|
|
5685
|
+
altText
|
|
5686
|
+
width
|
|
5687
|
+
height
|
|
5688
|
+
}
|
|
5689
|
+
}
|
|
5690
|
+
}
|
|
5691
|
+
priceRange {
|
|
5692
|
+
minVariantPrice {
|
|
5693
|
+
amount
|
|
5694
|
+
currencyCode
|
|
5695
|
+
}
|
|
5696
|
+
maxVariantPrice {
|
|
5697
|
+
amount
|
|
5698
|
+
currencyCode
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
options {
|
|
5702
|
+
id
|
|
5703
|
+
name
|
|
5704
|
+
values
|
|
5705
|
+
}
|
|
5706
|
+
variants(first: 100) {
|
|
5707
|
+
edges {
|
|
5708
|
+
node {
|
|
5709
|
+
id
|
|
5710
|
+
title
|
|
5711
|
+
availableForSale
|
|
5712
|
+
price {
|
|
5713
|
+
amount
|
|
5714
|
+
currencyCode
|
|
5715
|
+
}
|
|
5716
|
+
compareAtPrice {
|
|
5717
|
+
amount
|
|
5718
|
+
currencyCode
|
|
5719
|
+
}
|
|
5720
|
+
selectedOptions {
|
|
5721
|
+
name
|
|
5722
|
+
value
|
|
5723
|
+
}
|
|
5724
|
+
image {
|
|
5725
|
+
url
|
|
5726
|
+
altText
|
|
5727
|
+
width
|
|
5728
|
+
height
|
|
5729
|
+
}
|
|
5730
|
+
}
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
`;
|
|
5735
|
+
var GET_PRODUCT_BY_HANDLE = `
|
|
5736
|
+
query GetProductByHandle($handle: String!) {
|
|
5737
|
+
product(handle: $handle) {
|
|
5738
|
+
...ProductFields
|
|
5739
|
+
}
|
|
5740
|
+
}
|
|
5741
|
+
${PRODUCT_FRAGMENT}
|
|
5742
|
+
`;
|
|
5743
|
+
var GET_PRODUCTS = `
|
|
5744
|
+
query GetProducts($first: Int!, $query: String, $sortKey: ProductSortKeys, $reverse: Boolean, $after: String) {
|
|
5745
|
+
products(first: $first, query: $query, sortKey: $sortKey, reverse: $reverse, after: $after) {
|
|
5746
|
+
edges {
|
|
5747
|
+
node {
|
|
5748
|
+
...ProductFields
|
|
5749
|
+
}
|
|
5750
|
+
cursor
|
|
5751
|
+
}
|
|
5752
|
+
pageInfo {
|
|
5753
|
+
hasNextPage
|
|
5754
|
+
hasPreviousPage
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5758
|
+
${PRODUCT_FRAGMENT}
|
|
5759
|
+
`;
|
|
5760
|
+
var GET_PRODUCT_RECOMMENDATIONS = `
|
|
5761
|
+
query GetProductRecommendations($productId: ID!) {
|
|
5762
|
+
productRecommendations(productId: $productId) {
|
|
5763
|
+
...ProductFields
|
|
5764
|
+
}
|
|
5765
|
+
}
|
|
5766
|
+
${PRODUCT_FRAGMENT}
|
|
5767
|
+
`;
|
|
5768
|
+
var COLLECTION_FRAGMENT = `
|
|
5769
|
+
fragment CollectionFields on Collection {
|
|
5770
|
+
id
|
|
5771
|
+
title
|
|
5772
|
+
handle
|
|
5773
|
+
description
|
|
5774
|
+
image {
|
|
5775
|
+
url
|
|
5776
|
+
altText
|
|
5777
|
+
width
|
|
5778
|
+
height
|
|
5779
|
+
}
|
|
5780
|
+
seo {
|
|
5781
|
+
title
|
|
5782
|
+
description
|
|
5783
|
+
}
|
|
5784
|
+
updatedAt
|
|
5785
|
+
}
|
|
5786
|
+
`;
|
|
5787
|
+
var GET_COLLECTION_BY_HANDLE = `
|
|
5788
|
+
query GetCollectionByHandle($handle: String!) {
|
|
5789
|
+
collection(handle: $handle) {
|
|
5790
|
+
...CollectionFields
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
${COLLECTION_FRAGMENT}
|
|
5794
|
+
`;
|
|
5795
|
+
var GET_COLLECTIONS = `
|
|
5796
|
+
query GetCollections($first: Int!) {
|
|
5797
|
+
collections(first: $first) {
|
|
5798
|
+
edges {
|
|
5799
|
+
node {
|
|
5800
|
+
...CollectionFields
|
|
5801
|
+
}
|
|
5802
|
+
}
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5805
|
+
${COLLECTION_FRAGMENT}
|
|
5806
|
+
`;
|
|
5807
|
+
var GET_COLLECTION_PRODUCTS = `
|
|
5808
|
+
query GetCollectionProducts($handle: String!, $first: Int!, $sortKey: ProductCollectionSortKeys, $reverse: Boolean, $after: String) {
|
|
5809
|
+
collection(handle: $handle) {
|
|
5810
|
+
products(first: $first, sortKey: $sortKey, reverse: $reverse, after: $after) {
|
|
5811
|
+
edges {
|
|
5812
|
+
node {
|
|
5813
|
+
...ProductFields
|
|
5814
|
+
}
|
|
5815
|
+
cursor
|
|
5816
|
+
}
|
|
5817
|
+
pageInfo {
|
|
5818
|
+
hasNextPage
|
|
5819
|
+
}
|
|
5820
|
+
}
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
${PRODUCT_FRAGMENT}
|
|
5824
|
+
`;
|
|
5825
|
+
var CART_FRAGMENT = `
|
|
5826
|
+
fragment CartFields on Cart {
|
|
5827
|
+
id
|
|
5828
|
+
checkoutUrl
|
|
5829
|
+
totalQuantity
|
|
5830
|
+
cost {
|
|
5831
|
+
subtotalAmount {
|
|
5832
|
+
amount
|
|
5833
|
+
currencyCode
|
|
5834
|
+
}
|
|
5835
|
+
totalAmount {
|
|
5836
|
+
amount
|
|
5837
|
+
currencyCode
|
|
5838
|
+
}
|
|
5839
|
+
totalTaxAmount {
|
|
5840
|
+
amount
|
|
5841
|
+
currencyCode
|
|
5842
|
+
}
|
|
5843
|
+
}
|
|
5844
|
+
lines(first: 100) {
|
|
5845
|
+
edges {
|
|
5846
|
+
node {
|
|
5847
|
+
id
|
|
5848
|
+
quantity
|
|
5849
|
+
cost {
|
|
5850
|
+
totalAmount {
|
|
5851
|
+
amount
|
|
5852
|
+
currencyCode
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
merchandise {
|
|
5856
|
+
... on ProductVariant {
|
|
5857
|
+
id
|
|
5858
|
+
title
|
|
5859
|
+
selectedOptions {
|
|
5860
|
+
name
|
|
5861
|
+
value
|
|
5862
|
+
}
|
|
5863
|
+
product {
|
|
5864
|
+
id
|
|
5865
|
+
handle
|
|
5866
|
+
title
|
|
5867
|
+
featuredImage {
|
|
5868
|
+
url
|
|
5869
|
+
altText
|
|
5870
|
+
width
|
|
5871
|
+
height
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
}
|
|
5876
|
+
}
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
`;
|
|
5881
|
+
var GET_CART = `
|
|
5882
|
+
query GetCart($cartId: ID!) {
|
|
5883
|
+
cart(id: $cartId) {
|
|
5884
|
+
...CartFields
|
|
5885
|
+
}
|
|
5886
|
+
}
|
|
5887
|
+
${CART_FRAGMENT}
|
|
5888
|
+
`;
|
|
5889
|
+
|
|
5890
|
+
// src/providers/shopify/mutations.ts
|
|
5891
|
+
var CART_CREATE = `
|
|
5892
|
+
mutation CartCreate($input: CartInput!) {
|
|
5893
|
+
cartCreate(input: $input) {
|
|
5894
|
+
cart {
|
|
5895
|
+
...CartFields
|
|
5896
|
+
}
|
|
5897
|
+
userErrors {
|
|
5898
|
+
field
|
|
5899
|
+
message
|
|
5900
|
+
code
|
|
5901
|
+
}
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
${CART_FRAGMENT}
|
|
5905
|
+
`;
|
|
5906
|
+
var CART_LINES_ADD = `
|
|
5907
|
+
mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
|
|
5908
|
+
cartLinesAdd(cartId: $cartId, lines: $lines) {
|
|
5909
|
+
cart {
|
|
5910
|
+
...CartFields
|
|
5911
|
+
}
|
|
5912
|
+
userErrors {
|
|
5913
|
+
field
|
|
5914
|
+
message
|
|
5915
|
+
code
|
|
5916
|
+
}
|
|
5917
|
+
}
|
|
5918
|
+
}
|
|
5919
|
+
${CART_FRAGMENT}
|
|
5920
|
+
`;
|
|
5921
|
+
var CART_LINES_UPDATE = `
|
|
5922
|
+
mutation CartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
|
|
5923
|
+
cartLinesUpdate(cartId: $cartId, lines: $lines) {
|
|
5924
|
+
cart {
|
|
5925
|
+
...CartFields
|
|
5926
|
+
}
|
|
5927
|
+
userErrors {
|
|
5928
|
+
field
|
|
5929
|
+
message
|
|
5930
|
+
code
|
|
5931
|
+
}
|
|
5932
|
+
}
|
|
5933
|
+
}
|
|
5934
|
+
${CART_FRAGMENT}
|
|
5935
|
+
`;
|
|
5936
|
+
var CART_LINES_REMOVE = `
|
|
5937
|
+
mutation CartLinesRemove($cartId: ID!, $lineIds: [ID!]!) {
|
|
5938
|
+
cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
|
|
5939
|
+
cart {
|
|
5940
|
+
...CartFields
|
|
5941
|
+
}
|
|
5942
|
+
userErrors {
|
|
5943
|
+
field
|
|
5944
|
+
message
|
|
5945
|
+
code
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
${CART_FRAGMENT}
|
|
5950
|
+
`;
|
|
5951
|
+
var CART_DISCOUNT_CODES_UPDATE = `
|
|
5952
|
+
mutation CartDiscountCodesUpdate($cartId: ID!, $discountCodes: [String!]!) {
|
|
5953
|
+
cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) {
|
|
5954
|
+
cart {
|
|
5955
|
+
...CartFields
|
|
5956
|
+
}
|
|
5957
|
+
userErrors {
|
|
5958
|
+
field
|
|
5959
|
+
message
|
|
5960
|
+
code
|
|
5961
|
+
}
|
|
5962
|
+
}
|
|
5963
|
+
}
|
|
5964
|
+
${CART_FRAGMENT}
|
|
5965
|
+
`;
|
|
5966
|
+
|
|
5967
|
+
// src/providers/shopify/index.ts
|
|
5968
|
+
var ShopifyProvider = class {
|
|
5969
|
+
name = "shopify";
|
|
5970
|
+
version = "1.0.0";
|
|
5971
|
+
capabilities = SHOPIFY_CAPABILITIES;
|
|
5972
|
+
logger;
|
|
5973
|
+
storeDomain;
|
|
5974
|
+
apiVersion;
|
|
5975
|
+
endpoint;
|
|
5976
|
+
headers;
|
|
5977
|
+
constructor(config) {
|
|
5978
|
+
this.logger = createLogger("ShopifyProvider", config.debug);
|
|
5979
|
+
this.storeDomain = config.storeDomain.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
5980
|
+
this.apiVersion = config.apiVersion ?? "2026-04";
|
|
5981
|
+
this.endpoint = `https://${this.storeDomain}/api/${this.apiVersion}/graphql.json`;
|
|
5982
|
+
this.headers = {
|
|
5983
|
+
"Content-Type": "application/json",
|
|
5984
|
+
...config.privateAccessToken ? { "Shopify-Storefront-Private-Token": config.privateAccessToken } : { "X-Shopify-Storefront-Access-Token": config.publicAccessToken }
|
|
5985
|
+
};
|
|
5986
|
+
this.logger.info("Initializing Shopify Provider", {
|
|
5987
|
+
storeDomain: this.storeDomain,
|
|
5988
|
+
apiVersion: this.apiVersion,
|
|
5989
|
+
authType: config.privateAccessToken ? "private" : "public"
|
|
5990
|
+
});
|
|
5991
|
+
}
|
|
5992
|
+
// ============================================
|
|
5993
|
+
// GraphQL Client
|
|
5994
|
+
// ============================================
|
|
5995
|
+
async query(gql, variables) {
|
|
5996
|
+
const response = await fetch(this.endpoint, {
|
|
5997
|
+
method: "POST",
|
|
5998
|
+
headers: this.headers,
|
|
5999
|
+
body: JSON.stringify({ query: gql, variables })
|
|
6000
|
+
});
|
|
6001
|
+
if (!response.ok) {
|
|
6002
|
+
throw new ProviderAPIError(
|
|
6003
|
+
`Shopify API error: ${response.status} ${response.statusText}`,
|
|
6004
|
+
"SHOPIFY_API_ERROR",
|
|
6005
|
+
response.status
|
|
6006
|
+
);
|
|
6007
|
+
}
|
|
6008
|
+
const json = await response.json();
|
|
6009
|
+
if (json.errors?.length) {
|
|
6010
|
+
const message = json.errors.map((e) => e.message).join("; ");
|
|
6011
|
+
throw new ProviderAPIError(`Shopify GraphQL error: ${message}`, "SHOPIFY_GRAPHQL_ERROR");
|
|
6012
|
+
}
|
|
6013
|
+
return json.data;
|
|
6014
|
+
}
|
|
6015
|
+
// ============================================
|
|
6016
|
+
// Product Operations
|
|
6017
|
+
// ============================================
|
|
6018
|
+
async getProduct(handle) {
|
|
6019
|
+
const data = await this.query(GET_PRODUCT_BY_HANDLE, { handle });
|
|
6020
|
+
return data.product ? transformProduct2(data.product) : void 0;
|
|
6021
|
+
}
|
|
6022
|
+
async getProducts(options) {
|
|
6023
|
+
const first = options?.first ?? 20;
|
|
6024
|
+
const sortKeyMap = {
|
|
6025
|
+
"title-asc": "TITLE",
|
|
6026
|
+
"title-desc": "TITLE",
|
|
6027
|
+
"price-asc": "PRICE",
|
|
6028
|
+
"price-desc": "PRICE",
|
|
6029
|
+
"best-selling": "BEST_SELLING",
|
|
6030
|
+
"created-desc": "CREATED_AT",
|
|
6031
|
+
"relevance": "RELEVANCE"
|
|
6032
|
+
};
|
|
6033
|
+
const sortKey = options?.sortKey ? sortKeyMap[options.sortKey] ?? "RELEVANCE" : "RELEVANCE";
|
|
6034
|
+
const reverse = options?.sortKey?.endsWith("-desc") ?? false;
|
|
6035
|
+
const data = await this.query(GET_PRODUCTS, {
|
|
6036
|
+
first,
|
|
6037
|
+
query: options?.query,
|
|
6038
|
+
sortKey,
|
|
6039
|
+
reverse
|
|
6040
|
+
});
|
|
6041
|
+
return data.products.edges.map((edge) => transformProduct2(edge.node));
|
|
6042
|
+
}
|
|
6043
|
+
async getProductRecommendations(productId) {
|
|
6044
|
+
const data = await this.query(GET_PRODUCT_RECOMMENDATIONS, {
|
|
6045
|
+
productId
|
|
6046
|
+
});
|
|
6047
|
+
return (data.productRecommendations ?? []).map(transformProduct2);
|
|
6048
|
+
}
|
|
6049
|
+
// ============================================
|
|
6050
|
+
// Collection Operations
|
|
6051
|
+
// ============================================
|
|
6052
|
+
async getCollection(handle) {
|
|
6053
|
+
const data = await this.query(GET_COLLECTION_BY_HANDLE, { handle });
|
|
6054
|
+
return data.collection ? transformCollection2(data.collection) : void 0;
|
|
6055
|
+
}
|
|
6056
|
+
async getCollections(options) {
|
|
6057
|
+
const data = await this.query(GET_COLLECTIONS, {
|
|
6058
|
+
first: options?.limit ?? 50
|
|
6059
|
+
});
|
|
6060
|
+
return data.collections.edges.map((edge) => transformCollection2(edge.node));
|
|
6061
|
+
}
|
|
6062
|
+
async getCollectionProducts(handle, options) {
|
|
6063
|
+
const data = await this.query(
|
|
6064
|
+
GET_COLLECTION_PRODUCTS,
|
|
6065
|
+
{
|
|
6066
|
+
handle,
|
|
6067
|
+
first: options?.first ?? 20,
|
|
6068
|
+
sortKey: options?.sortKey === "price-asc" || options?.sortKey === "price-desc" ? "PRICE" : "BEST_SELLING",
|
|
6069
|
+
reverse: options?.sortKey?.endsWith("-desc") ?? false
|
|
6070
|
+
}
|
|
6071
|
+
);
|
|
6072
|
+
return (data.collection?.products?.edges ?? []).map((edge) => transformProduct2(edge.node));
|
|
6073
|
+
}
|
|
6074
|
+
// ============================================
|
|
6075
|
+
// Category Operations (Shopify does not have native categories)
|
|
6076
|
+
// ============================================
|
|
6077
|
+
async getCategory() {
|
|
6078
|
+
this.logger.warn("Shopify does not support native categories. Use collections instead.");
|
|
6079
|
+
return void 0;
|
|
6080
|
+
}
|
|
6081
|
+
async getCategories() {
|
|
6082
|
+
return [];
|
|
6083
|
+
}
|
|
6084
|
+
async getCategoryProducts() {
|
|
6085
|
+
return [];
|
|
6086
|
+
}
|
|
6087
|
+
// ============================================
|
|
6088
|
+
// Cart Operations
|
|
6089
|
+
// ============================================
|
|
6090
|
+
async createCart() {
|
|
6091
|
+
const data = await this.query(CART_CREATE, {
|
|
6092
|
+
input: {}
|
|
6093
|
+
});
|
|
6094
|
+
if (data.cartCreate.userErrors?.length) {
|
|
6095
|
+
throw new ProviderAPIError(
|
|
6096
|
+
`Cart creation failed: ${data.cartCreate.userErrors.map((e) => e.message).join("; ")}`,
|
|
6097
|
+
"SHOPIFY_CART_ERROR"
|
|
6098
|
+
);
|
|
6099
|
+
}
|
|
6100
|
+
return transformCart2(data.cartCreate.cart);
|
|
6101
|
+
}
|
|
6102
|
+
async getCart(cartId) {
|
|
6103
|
+
const data = await this.query(GET_CART, { cartId });
|
|
6104
|
+
return data.cart ? transformCart2(data.cart) : void 0;
|
|
6105
|
+
}
|
|
6106
|
+
async addToCart(cartId, lines) {
|
|
6107
|
+
const data = await this.query(CART_LINES_ADD, {
|
|
6108
|
+
cartId,
|
|
6109
|
+
lines: lines.map((l) => ({ merchandiseId: l.merchandiseId, quantity: l.quantity }))
|
|
6110
|
+
});
|
|
6111
|
+
if (data.cartLinesAdd.userErrors?.length) {
|
|
6112
|
+
throw new ProviderAPIError(
|
|
6113
|
+
`Add to cart failed: ${data.cartLinesAdd.userErrors.map((e) => e.message).join("; ")}`,
|
|
6114
|
+
"SHOPIFY_CART_ERROR"
|
|
6115
|
+
);
|
|
6116
|
+
}
|
|
6117
|
+
return transformCart2(data.cartLinesAdd.cart);
|
|
6118
|
+
}
|
|
6119
|
+
async removeFromCart(cartId, lineIds) {
|
|
6120
|
+
const data = await this.query(CART_LINES_REMOVE, {
|
|
6121
|
+
cartId,
|
|
6122
|
+
lineIds
|
|
6123
|
+
});
|
|
6124
|
+
if (data.cartLinesRemove.userErrors?.length) {
|
|
6125
|
+
throw new ProviderAPIError(
|
|
6126
|
+
`Remove from cart failed: ${data.cartLinesRemove.userErrors.map((e) => e.message).join("; ")}`,
|
|
6127
|
+
"SHOPIFY_CART_ERROR"
|
|
6128
|
+
);
|
|
6129
|
+
}
|
|
6130
|
+
return transformCart2(data.cartLinesRemove.cart);
|
|
6131
|
+
}
|
|
6132
|
+
async updateCart(cartId, lines) {
|
|
6133
|
+
const data = await this.query(CART_LINES_UPDATE, {
|
|
6134
|
+
cartId,
|
|
6135
|
+
lines: lines.map((l) => ({ id: l.id, quantity: l.quantity }))
|
|
6136
|
+
});
|
|
6137
|
+
if (data.cartLinesUpdate.userErrors?.length) {
|
|
6138
|
+
throw new ProviderAPIError(
|
|
6139
|
+
`Update cart failed: ${data.cartLinesUpdate.userErrors.map((e) => e.message).join("; ")}`,
|
|
6140
|
+
"SHOPIFY_CART_ERROR"
|
|
6141
|
+
);
|
|
6142
|
+
}
|
|
6143
|
+
return transformCart2(data.cartLinesUpdate.cart);
|
|
6144
|
+
}
|
|
6145
|
+
// ============================================
|
|
6146
|
+
// Checkout Operations
|
|
6147
|
+
// ============================================
|
|
6148
|
+
async completeCheckout(_cartId) {
|
|
6149
|
+
throw new ProviderAPIError(
|
|
6150
|
+
"Shopify uses hosted checkout. Redirect the buyer to cart.checkoutUrl instead of calling completeCheckout().",
|
|
6151
|
+
"SHOPIFY_HOSTED_CHECKOUT"
|
|
6152
|
+
);
|
|
6153
|
+
}
|
|
6154
|
+
async getShippingMethods(_cartId) {
|
|
6155
|
+
return [];
|
|
6156
|
+
}
|
|
6157
|
+
async getRegionCountries() {
|
|
6158
|
+
return [];
|
|
6159
|
+
}
|
|
6160
|
+
async addShippingMethod(_cartId, _methodId) {
|
|
6161
|
+
throw new ProviderAPIError(
|
|
6162
|
+
"Shopify handles shipping selection in hosted checkout.",
|
|
6163
|
+
"SHOPIFY_HOSTED_CHECKOUT"
|
|
6164
|
+
);
|
|
6165
|
+
}
|
|
6166
|
+
async initiatePaymentSession(_cartId, _providerId) {
|
|
6167
|
+
throw new ProviderAPIError(
|
|
6168
|
+
"Shopify handles payment in hosted checkout.",
|
|
6169
|
+
"SHOPIFY_HOSTED_CHECKOUT"
|
|
6170
|
+
);
|
|
6171
|
+
}
|
|
6172
|
+
async applyDiscountCode(cartId, code) {
|
|
6173
|
+
const currentCart = await this.getCart(cartId);
|
|
6174
|
+
const existingCodes = [];
|
|
6175
|
+
existingCodes.push(code);
|
|
6176
|
+
const data = await this.query(
|
|
6177
|
+
CART_DISCOUNT_CODES_UPDATE,
|
|
6178
|
+
{ cartId, discountCodes: existingCodes }
|
|
6179
|
+
);
|
|
6180
|
+
if (data.cartDiscountCodesUpdate.userErrors?.length) {
|
|
6181
|
+
throw new ProviderAPIError(
|
|
6182
|
+
`Discount code failed: ${data.cartDiscountCodesUpdate.userErrors.map((e) => e.message).join("; ")}`,
|
|
6183
|
+
"SHOPIFY_DISCOUNT_ERROR"
|
|
6184
|
+
);
|
|
6185
|
+
}
|
|
6186
|
+
return transformCart2(data.cartDiscountCodesUpdate.cart);
|
|
6187
|
+
}
|
|
6188
|
+
async removeDiscountCode(cartId, _code) {
|
|
6189
|
+
const data = await this.query(
|
|
6190
|
+
CART_DISCOUNT_CODES_UPDATE,
|
|
6191
|
+
{ cartId, discountCodes: [] }
|
|
6192
|
+
);
|
|
6193
|
+
return transformCart2(data.cartDiscountCodesUpdate.cart);
|
|
6194
|
+
}
|
|
6195
|
+
// ============================================
|
|
6196
|
+
// Customer Operations
|
|
6197
|
+
// ============================================
|
|
6198
|
+
async getOrders(options) {
|
|
6199
|
+
this.logger.warn("Shopify orders require Customer Account API (planned upgrade path).");
|
|
6200
|
+
return [];
|
|
6201
|
+
}
|
|
6202
|
+
// ============================================
|
|
6203
|
+
// Payment Operations
|
|
6204
|
+
// ============================================
|
|
6205
|
+
async getPaymentMethods(_options) {
|
|
6206
|
+
return [];
|
|
6207
|
+
}
|
|
6208
|
+
// ============================================
|
|
6209
|
+
// Inventory Operations (not available via Storefront API)
|
|
6210
|
+
// ============================================
|
|
6211
|
+
async getStockLocations() {
|
|
6212
|
+
return [];
|
|
6213
|
+
}
|
|
6214
|
+
async checkInventoryLevels() {
|
|
6215
|
+
return [];
|
|
6216
|
+
}
|
|
6217
|
+
async validateCartStock() {
|
|
6218
|
+
return { allAvailable: true, unavailableItems: [] };
|
|
6219
|
+
}
|
|
6220
|
+
async getFulfillmentOptions() {
|
|
6221
|
+
return [];
|
|
6222
|
+
}
|
|
6223
|
+
// ============================================
|
|
6224
|
+
// Admin Operations (not available via Storefront API)
|
|
6225
|
+
// ============================================
|
|
6226
|
+
async cancelOrder(_orderId) {
|
|
6227
|
+
throw new ProviderAPIError(
|
|
6228
|
+
"Order cancellation requires Shopify Admin API (not available in Storefront API).",
|
|
6229
|
+
"SHOPIFY_ADMIN_REQUIRED"
|
|
6230
|
+
);
|
|
6231
|
+
}
|
|
6232
|
+
async createFulfillment(_orderId) {
|
|
6233
|
+
throw new ProviderAPIError(
|
|
6234
|
+
"Fulfillment requires Shopify Admin API.",
|
|
6235
|
+
"SHOPIFY_ADMIN_REQUIRED"
|
|
6236
|
+
);
|
|
6237
|
+
}
|
|
6238
|
+
async createRefund(_orderId) {
|
|
6239
|
+
throw new ProviderAPIError(
|
|
6240
|
+
"Refunds require Shopify Admin API.",
|
|
6241
|
+
"SHOPIFY_ADMIN_REQUIRED"
|
|
6242
|
+
);
|
|
6243
|
+
}
|
|
6244
|
+
async getAdminOrders() {
|
|
6245
|
+
return { orders: [], count: 0 };
|
|
6246
|
+
}
|
|
6247
|
+
// ============================================
|
|
6248
|
+
// Storefront / Multi-Tenant
|
|
6249
|
+
// ============================================
|
|
6250
|
+
async getStorefrontContext() {
|
|
6251
|
+
return {
|
|
6252
|
+
id: this.storeDomain,
|
|
6253
|
+
name: this.storeDomain,
|
|
6254
|
+
platformType: "shopify",
|
|
6255
|
+
requiresProductScoping: false,
|
|
6256
|
+
currencyCode: "USD"
|
|
6257
|
+
};
|
|
6258
|
+
}
|
|
6259
|
+
isStorefrontRequired() {
|
|
6260
|
+
return false;
|
|
6261
|
+
}
|
|
6262
|
+
};
|
|
6263
|
+
|
|
6264
|
+
// src/providers/bigcommerce/transforms.ts
|
|
6265
|
+
function transformMoney3(amount, currencyCode = "USD") {
|
|
6266
|
+
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount ?? 0;
|
|
6267
|
+
return {
|
|
6268
|
+
amount: numAmount.toFixed(2),
|
|
6269
|
+
currencyCode: currencyCode.toUpperCase()
|
|
6270
|
+
};
|
|
6271
|
+
}
|
|
6272
|
+
function transformImage3(url, alt = "") {
|
|
6273
|
+
return { url, altText: alt, width: 800, height: 800 };
|
|
6274
|
+
}
|
|
6275
|
+
function transformProduct3(raw, currencyCode = "USD") {
|
|
6276
|
+
const images = (raw.images ?? []).map(
|
|
6277
|
+
(img) => transformImage3(img.url_standard ?? img.url_thumbnail ?? "", img.description ?? "")
|
|
6278
|
+
);
|
|
6279
|
+
const variants = (raw.variants ?? []).map((v) => ({
|
|
6280
|
+
id: String(v.id),
|
|
6281
|
+
title: v.option_display_name ?? `Variant ${v.id}`,
|
|
6282
|
+
availableForSale: v.purchasing_disabled !== true,
|
|
6283
|
+
price: transformMoney3(v.price ?? raw.price, currencyCode),
|
|
6284
|
+
compareAtPrice: v.sale_price ? transformMoney3(v.sale_price, currencyCode) : void 0,
|
|
6285
|
+
selectedOptions: (v.option_values ?? []).map((ov) => ({
|
|
6286
|
+
name: ov.option_display_name ?? "",
|
|
6287
|
+
value: ov.label ?? ""
|
|
6288
|
+
}))
|
|
6289
|
+
}));
|
|
6290
|
+
const options = (raw.options ?? []).map((opt) => ({
|
|
6291
|
+
id: String(opt.id),
|
|
6292
|
+
name: opt.display_name ?? "",
|
|
6293
|
+
values: (opt.option_values ?? []).map((val) => val.label ?? "")
|
|
6294
|
+
}));
|
|
6295
|
+
const price = raw.price ?? raw.calculated_price?.as_entered ?? 0;
|
|
6296
|
+
return {
|
|
6297
|
+
id: String(raw.id),
|
|
6298
|
+
handle: raw.custom_url?.url?.replace(/^\//, "").replace(/\/$/, "") ?? String(raw.id),
|
|
6299
|
+
title: raw.name ?? "",
|
|
6300
|
+
description: raw.plain_text_description ?? raw.description ?? "",
|
|
6301
|
+
descriptionHtml: raw.description ?? "",
|
|
6302
|
+
availableForSale: raw.availability === "available" || raw.is_visible !== false,
|
|
6303
|
+
featuredImage: images[0] ?? { url: "", altText: "", width: 800, height: 800 },
|
|
6304
|
+
images,
|
|
6305
|
+
options,
|
|
6306
|
+
variants,
|
|
6307
|
+
priceRange: {
|
|
6308
|
+
minVariantPrice: transformMoney3(price, currencyCode),
|
|
6309
|
+
maxVariantPrice: transformMoney3(price, currencyCode)
|
|
6310
|
+
},
|
|
6311
|
+
seo: {
|
|
6312
|
+
title: raw.page_title ?? raw.name ?? "",
|
|
6313
|
+
description: raw.meta_description ?? ""
|
|
6314
|
+
},
|
|
6315
|
+
tags: [],
|
|
6316
|
+
createdAt: raw.date_created ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6317
|
+
updatedAt: raw.date_modified ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
6318
|
+
};
|
|
6319
|
+
}
|
|
6320
|
+
function transformCollection3(raw) {
|
|
6321
|
+
return {
|
|
6322
|
+
id: String(raw.id ?? ""),
|
|
6323
|
+
handle: raw.custom_url?.url?.replace(/^\//, "").replace(/\/$/, "") ?? String(raw.id),
|
|
6324
|
+
title: raw.name ?? "",
|
|
6325
|
+
description: raw.description ?? "",
|
|
6326
|
+
seo: {
|
|
6327
|
+
title: raw.page_title ?? raw.name ?? "",
|
|
6328
|
+
description: raw.meta_description ?? ""
|
|
6329
|
+
},
|
|
6330
|
+
updatedAt: raw.date_modified ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6331
|
+
path: `/collections/${raw.custom_url?.url?.replace(/^\//, "").replace(/\/$/, "") ?? raw.id}`
|
|
6332
|
+
};
|
|
6333
|
+
}
|
|
6334
|
+
function transformCart3(raw, currencyCode = "USD") {
|
|
6335
|
+
const lineItems = Object.values(raw.line_items ?? {}).flat();
|
|
6336
|
+
const lines = lineItems.map((item) => ({
|
|
6337
|
+
id: item.id,
|
|
6338
|
+
quantity: item.quantity ?? 1,
|
|
6339
|
+
cost: {
|
|
6340
|
+
totalAmount: transformMoney3(item.extended_sale_price ?? item.sale_price ?? 0, currencyCode)
|
|
6341
|
+
},
|
|
6342
|
+
merchandise: {
|
|
6343
|
+
id: String(item.variant_id ?? item.product_id),
|
|
6344
|
+
title: item.name ?? "",
|
|
6345
|
+
selectedOptions: (item.options ?? []).map((opt) => ({
|
|
6346
|
+
name: opt.name ?? "",
|
|
6347
|
+
value: opt.value ?? ""
|
|
6348
|
+
})),
|
|
6349
|
+
product: {
|
|
6350
|
+
id: String(item.product_id),
|
|
6351
|
+
handle: String(item.product_id),
|
|
6352
|
+
title: item.name ?? "",
|
|
6353
|
+
featuredImage: item.image_url ? transformImage3(item.image_url) : { url: "", altText: "", width: 800, height: 800 }
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6356
|
+
}));
|
|
6357
|
+
return {
|
|
6358
|
+
id: raw.id,
|
|
6359
|
+
checkoutUrl: raw.redirect_urls?.checkout_url ?? "",
|
|
6360
|
+
totalQuantity: lines.reduce((sum, l) => sum + l.quantity, 0),
|
|
6361
|
+
cost: {
|
|
6362
|
+
subtotalAmount: transformMoney3(raw.base_amount ?? 0, currencyCode),
|
|
6363
|
+
totalAmount: transformMoney3(raw.cart_amount ?? 0, currencyCode)
|
|
6364
|
+
},
|
|
6365
|
+
lines
|
|
6366
|
+
};
|
|
6367
|
+
}
|
|
6368
|
+
|
|
6369
|
+
// src/providers/bigcommerce/index.ts
|
|
6370
|
+
var BigCommerceProvider = class {
|
|
6371
|
+
name = "bigcommerce";
|
|
6372
|
+
version = "1.0.0";
|
|
6373
|
+
capabilities = BIGCOMMERCE_CAPABILITIES;
|
|
6374
|
+
logger;
|
|
6375
|
+
storefrontApiUrl;
|
|
6376
|
+
storeHash;
|
|
6377
|
+
channelId;
|
|
6378
|
+
currencyCode;
|
|
6379
|
+
headers;
|
|
6380
|
+
constructor(config) {
|
|
6381
|
+
this.logger = createLogger("BigCommerceProvider", config.debug);
|
|
6382
|
+
this.storefrontApiUrl = config.storefrontApiUrl.replace(/\/$/, "");
|
|
6383
|
+
this.storeHash = config.storeHash;
|
|
6384
|
+
this.channelId = config.channelId;
|
|
6385
|
+
this.currencyCode = config.currencyCode ?? "USD";
|
|
6386
|
+
this.headers = {
|
|
6387
|
+
"Content-Type": "application/json",
|
|
6388
|
+
"Authorization": `Bearer ${config.storefrontApiToken}`
|
|
6389
|
+
};
|
|
6390
|
+
this.logger.info("Initializing BigCommerce Provider", { storeHash: this.storeHash, channelId: this.channelId });
|
|
6391
|
+
}
|
|
6392
|
+
async fetchApi(path, init) {
|
|
6393
|
+
const url = `${this.storefrontApiUrl}${path}`;
|
|
6394
|
+
const response = await fetch(url, {
|
|
6395
|
+
...init,
|
|
6396
|
+
headers: { ...this.headers, ...init?.headers }
|
|
6397
|
+
});
|
|
6398
|
+
if (!response.ok) {
|
|
6399
|
+
throw new ProviderAPIError(
|
|
6400
|
+
`BigCommerce API error: ${response.status} ${response.statusText}`,
|
|
6401
|
+
"BIGCOMMERCE_API_ERROR",
|
|
6402
|
+
response.status
|
|
6403
|
+
);
|
|
6404
|
+
}
|
|
6405
|
+
return response.json();
|
|
6406
|
+
}
|
|
6407
|
+
// ============================================
|
|
6408
|
+
// Product Operations
|
|
6409
|
+
// ============================================
|
|
6410
|
+
async getProduct(handle) {
|
|
6411
|
+
try {
|
|
6412
|
+
const data = await this.fetchApi(`/api/storefront/products?include=variants,options,images&keyword=${encodeURIComponent(handle)}`);
|
|
6413
|
+
const product = data.data?.[0];
|
|
6414
|
+
return product ? transformProduct3(product, this.currencyCode) : void 0;
|
|
6415
|
+
} catch {
|
|
6416
|
+
return void 0;
|
|
6417
|
+
}
|
|
6418
|
+
}
|
|
6419
|
+
async getProducts(options) {
|
|
6420
|
+
const limit = options?.first ?? 20;
|
|
6421
|
+
let path = `/api/storefront/products?include=variants,options,images&limit=${limit}`;
|
|
6422
|
+
if (options?.query) path += `&keyword=${encodeURIComponent(options.query)}`;
|
|
6423
|
+
const data = await this.fetchApi(path);
|
|
6424
|
+
return (data.data ?? []).map((p) => transformProduct3(p, this.currencyCode));
|
|
6425
|
+
}
|
|
6426
|
+
async getProductRecommendations(_productId) {
|
|
6427
|
+
return [];
|
|
6428
|
+
}
|
|
6429
|
+
// ============================================
|
|
6430
|
+
// Collection Operations (BigCommerce Categories)
|
|
6431
|
+
// ============================================
|
|
6432
|
+
async getCollection(handle) {
|
|
6433
|
+
try {
|
|
6434
|
+
const data = await this.fetchApi(`/api/storefront/categories?url=/${handle}/`);
|
|
6435
|
+
const cat = data.data?.[0];
|
|
6436
|
+
return cat ? transformCollection3(cat) : void 0;
|
|
6437
|
+
} catch {
|
|
6438
|
+
return void 0;
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
async getCollections(options) {
|
|
6442
|
+
const limit = options?.limit ?? 50;
|
|
6443
|
+
const data = await this.fetchApi(`/api/storefront/categories?limit=${limit}`);
|
|
6444
|
+
return (data.data ?? []).map(transformCollection3);
|
|
6445
|
+
}
|
|
6446
|
+
async getCollectionProducts(handle, options) {
|
|
6447
|
+
const limit = options?.first ?? 20;
|
|
6448
|
+
const data = await this.fetchApi(
|
|
6449
|
+
`/api/storefront/products?include=variants,options,images&categories:in=${handle}&limit=${limit}`
|
|
6450
|
+
);
|
|
6451
|
+
return (data.data ?? []).map((p) => transformProduct3(p, this.currencyCode));
|
|
6452
|
+
}
|
|
6453
|
+
// ============================================
|
|
6454
|
+
// Category Operations
|
|
6455
|
+
// ============================================
|
|
6456
|
+
async getCategory(idOrHandle) {
|
|
6457
|
+
return this.getCollection(idOrHandle);
|
|
6458
|
+
}
|
|
6459
|
+
async getCategories(options) {
|
|
6460
|
+
return this.getCollections(options);
|
|
6461
|
+
}
|
|
6462
|
+
async getCategoryProducts(handle, options) {
|
|
6463
|
+
return this.getCollectionProducts(handle, options);
|
|
6464
|
+
}
|
|
6465
|
+
// ============================================
|
|
6466
|
+
// Cart Operations
|
|
6467
|
+
// ============================================
|
|
6468
|
+
async createCart() {
|
|
6469
|
+
const data = await this.fetchApi("/api/storefront/carts", {
|
|
6470
|
+
method: "POST",
|
|
6471
|
+
body: JSON.stringify({ line_items: [] })
|
|
6472
|
+
});
|
|
6473
|
+
return transformCart3(data.data ?? data, this.currencyCode);
|
|
6474
|
+
}
|
|
6475
|
+
async getCart(cartId) {
|
|
6476
|
+
try {
|
|
6477
|
+
const data = await this.fetchApi(`/api/storefront/carts/${cartId}?include=line_items.physical_items.options`);
|
|
6478
|
+
return transformCart3(data.data ?? data, this.currencyCode);
|
|
6479
|
+
} catch {
|
|
6480
|
+
return void 0;
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
async addToCart(cartId, lines) {
|
|
6484
|
+
const lineItems = lines.map((l) => ({
|
|
6485
|
+
product_id: parseInt(l.merchandiseId, 10),
|
|
6486
|
+
quantity: l.quantity
|
|
6487
|
+
}));
|
|
6488
|
+
const data = await this.fetchApi(`/api/storefront/carts/${cartId}/items`, {
|
|
6489
|
+
method: "POST",
|
|
6490
|
+
body: JSON.stringify({ line_items: lineItems })
|
|
6491
|
+
});
|
|
6492
|
+
return transformCart3(data.data ?? data, this.currencyCode);
|
|
6493
|
+
}
|
|
6494
|
+
async removeFromCart(cartId, lineIds) {
|
|
6495
|
+
for (const lineId of lineIds) {
|
|
6496
|
+
await this.fetchApi(`/api/storefront/carts/${cartId}/items/${lineId}`, { method: "DELETE" });
|
|
6497
|
+
}
|
|
6498
|
+
const cart = await this.getCart(cartId);
|
|
6499
|
+
if (!cart) throw new ProviderAPIError("Cart not found after removal", "BIGCOMMERCE_CART_ERROR");
|
|
6500
|
+
return cart;
|
|
6501
|
+
}
|
|
6502
|
+
async updateCart(cartId, lines) {
|
|
6503
|
+
for (const line of lines) {
|
|
6504
|
+
await this.fetchApi(`/api/storefront/carts/${cartId}/items/${line.id}`, {
|
|
6505
|
+
method: "PUT",
|
|
6506
|
+
body: JSON.stringify({ line_item: { quantity: line.quantity } })
|
|
6507
|
+
});
|
|
6508
|
+
}
|
|
6509
|
+
const cart = await this.getCart(cartId);
|
|
6510
|
+
if (!cart) throw new ProviderAPIError("Cart not found after update", "BIGCOMMERCE_CART_ERROR");
|
|
6511
|
+
return cart;
|
|
6512
|
+
}
|
|
6513
|
+
// ============================================
|
|
6514
|
+
// Checkout Operations (Hosted)
|
|
6515
|
+
// ============================================
|
|
6516
|
+
async completeCheckout(_cartId) {
|
|
6517
|
+
throw new ProviderAPIError(
|
|
6518
|
+
"BigCommerce uses Embedded Checkout. Redirect the buyer to cart.checkoutUrl.",
|
|
6519
|
+
"BIGCOMMERCE_HOSTED_CHECKOUT"
|
|
6520
|
+
);
|
|
6521
|
+
}
|
|
6522
|
+
async getShippingMethods(_cartId) {
|
|
6523
|
+
return [];
|
|
6524
|
+
}
|
|
6525
|
+
async getRegionCountries() {
|
|
6526
|
+
return [];
|
|
6527
|
+
}
|
|
6528
|
+
async addShippingMethod(_cartId, _methodId) {
|
|
6529
|
+
throw new ProviderAPIError("BigCommerce handles shipping in Embedded Checkout.", "BIGCOMMERCE_HOSTED_CHECKOUT");
|
|
6530
|
+
}
|
|
6531
|
+
async initiatePaymentSession(_cartId, _providerId) {
|
|
6532
|
+
throw new ProviderAPIError("BigCommerce handles payment in Embedded Checkout.", "BIGCOMMERCE_HOSTED_CHECKOUT");
|
|
6533
|
+
}
|
|
6534
|
+
async applyDiscountCode(cartId, code) {
|
|
6535
|
+
const data = await this.fetchApi(`/api/storefront/carts/${cartId}/coupons`, {
|
|
6536
|
+
method: "POST",
|
|
6537
|
+
body: JSON.stringify({ coupon_code: code })
|
|
6538
|
+
});
|
|
6539
|
+
return transformCart3(data.data ?? data, this.currencyCode);
|
|
6540
|
+
}
|
|
6541
|
+
async removeDiscountCode(cartId, code) {
|
|
6542
|
+
await this.fetchApi(`/api/storefront/carts/${cartId}/coupons/${encodeURIComponent(code)}`, { method: "DELETE" });
|
|
6543
|
+
const cart = await this.getCart(cartId);
|
|
6544
|
+
if (!cart) throw new ProviderAPIError("Cart not found after coupon removal", "BIGCOMMERCE_CART_ERROR");
|
|
6545
|
+
return cart;
|
|
6546
|
+
}
|
|
6547
|
+
// ============================================
|
|
6548
|
+
// Customer, Order, Payment, Inventory, Admin
|
|
6549
|
+
// ============================================
|
|
6550
|
+
async getOrders(_options) {
|
|
6551
|
+
return [];
|
|
6552
|
+
}
|
|
6553
|
+
async getPaymentMethods(_options) {
|
|
6554
|
+
return [];
|
|
6555
|
+
}
|
|
6556
|
+
async getStockLocations() {
|
|
6557
|
+
return [];
|
|
6558
|
+
}
|
|
6559
|
+
async checkInventoryLevels() {
|
|
6560
|
+
return [];
|
|
6561
|
+
}
|
|
6562
|
+
async validateCartStock() {
|
|
6563
|
+
return { allAvailable: true, unavailableItems: [] };
|
|
6564
|
+
}
|
|
6565
|
+
async getFulfillmentOptions() {
|
|
6566
|
+
return [];
|
|
6567
|
+
}
|
|
6568
|
+
async cancelOrder(_orderId) {
|
|
6569
|
+
throw new ProviderAPIError("Order cancellation requires BigCommerce Management API.", "BIGCOMMERCE_ADMIN_REQUIRED");
|
|
6570
|
+
}
|
|
6571
|
+
async createFulfillment(_orderId) {
|
|
6572
|
+
throw new ProviderAPIError("Fulfillment requires BigCommerce Management API.", "BIGCOMMERCE_ADMIN_REQUIRED");
|
|
6573
|
+
}
|
|
6574
|
+
async createRefund(_orderId) {
|
|
6575
|
+
throw new ProviderAPIError("Refunds require BigCommerce Management API.", "BIGCOMMERCE_ADMIN_REQUIRED");
|
|
6576
|
+
}
|
|
6577
|
+
async getAdminOrders() {
|
|
6578
|
+
return { orders: [], count: 0 };
|
|
6579
|
+
}
|
|
6580
|
+
async getStorefrontContext() {
|
|
6581
|
+
return {
|
|
6582
|
+
id: this.channelId,
|
|
6583
|
+
name: `BigCommerce Store ${this.storeHash}`,
|
|
6584
|
+
platformType: "bigcommerce",
|
|
6585
|
+
requiresProductScoping: false,
|
|
6586
|
+
currencyCode: this.currencyCode
|
|
6587
|
+
};
|
|
6588
|
+
}
|
|
6589
|
+
isStorefrontRequired() {
|
|
6590
|
+
return false;
|
|
6591
|
+
}
|
|
6592
|
+
};
|
|
6593
|
+
|
|
6594
|
+
// src/providers/woocommerce/transforms.ts
|
|
6595
|
+
function transformMoney4(amount, currencyCode = "USD") {
|
|
6596
|
+
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount ?? 0;
|
|
6597
|
+
return {
|
|
6598
|
+
amount: isNaN(numAmount) ? "0.00" : numAmount.toFixed(2),
|
|
6599
|
+
currencyCode: currencyCode.toUpperCase()
|
|
6600
|
+
};
|
|
6601
|
+
}
|
|
6602
|
+
function transformImage4(url, alt = "") {
|
|
6603
|
+
return { url, altText: alt, width: 800, height: 800 };
|
|
6604
|
+
}
|
|
6605
|
+
function transformProduct4(raw, currencyCode = "USD") {
|
|
6606
|
+
const images = (raw.images ?? []).map(
|
|
6607
|
+
(img) => transformImage4(img.src ?? "", img.alt ?? img.name ?? "")
|
|
6608
|
+
);
|
|
6609
|
+
const variants = (raw.variations ?? []).map((v) => ({
|
|
6610
|
+
id: String(v.id),
|
|
6611
|
+
title: v.name ?? "",
|
|
6612
|
+
availableForSale: v.in_stock !== false,
|
|
6613
|
+
price: transformMoney4(v.prices?.price ?? v.price, currencyCode),
|
|
6614
|
+
compareAtPrice: v.prices?.regular_price ? transformMoney4(v.prices.regular_price, currencyCode) : void 0,
|
|
6615
|
+
selectedOptions: (v.attributes ?? []).map((attr) => ({
|
|
6616
|
+
name: attr.name ?? "",
|
|
6617
|
+
value: attr.value ?? ""
|
|
6618
|
+
}))
|
|
6619
|
+
}));
|
|
6620
|
+
const options = (raw.attributes ?? []).filter((attr) => attr.variation === true || attr.has_options === true).map((attr) => ({
|
|
6621
|
+
id: String(attr.id),
|
|
6622
|
+
name: attr.name ?? "",
|
|
6623
|
+
values: (attr.terms ?? attr.options ?? []).map(
|
|
6624
|
+
(t) => typeof t === "string" ? t : t.name ?? t.value ?? ""
|
|
6625
|
+
)
|
|
6626
|
+
}));
|
|
6627
|
+
const priceMinor = raw.prices?.price ?? "0";
|
|
6628
|
+
const priceDecimalPlaces = raw.prices?.currency_minor_unit ?? 2;
|
|
6629
|
+
const priceMajor = parseInt(priceMinor, 10) / Math.pow(10, priceDecimalPlaces);
|
|
6630
|
+
return {
|
|
6631
|
+
id: String(raw.id),
|
|
6632
|
+
handle: raw.slug ?? String(raw.id),
|
|
6633
|
+
title: raw.name ?? "",
|
|
6634
|
+
description: raw.short_description ?? raw.description ?? "",
|
|
6635
|
+
descriptionHtml: raw.description ?? "",
|
|
6636
|
+
availableForSale: raw.is_purchasable !== false && raw.is_in_stock !== false,
|
|
6637
|
+
featuredImage: images[0] ?? { url: "", altText: "", width: 800, height: 800 },
|
|
6638
|
+
images,
|
|
6639
|
+
options,
|
|
6640
|
+
variants,
|
|
6641
|
+
priceRange: {
|
|
6642
|
+
minVariantPrice: transformMoney4(priceMajor, currencyCode),
|
|
6643
|
+
maxVariantPrice: transformMoney4(priceMajor, currencyCode)
|
|
6644
|
+
},
|
|
6645
|
+
seo: {
|
|
6646
|
+
title: raw.name ?? "",
|
|
6647
|
+
description: raw.short_description?.replace(/<[^>]*>/g, "") ?? ""
|
|
6648
|
+
},
|
|
6649
|
+
tags: [],
|
|
6650
|
+
createdAt: raw.date_created ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6651
|
+
updatedAt: raw.date_modified ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
6652
|
+
};
|
|
6653
|
+
}
|
|
6654
|
+
function transformCollection4(raw) {
|
|
6655
|
+
return {
|
|
6656
|
+
id: String(raw.id ?? ""),
|
|
6657
|
+
handle: raw.slug ?? String(raw.id),
|
|
6658
|
+
title: raw.name ?? "",
|
|
6659
|
+
description: raw.description ?? "",
|
|
6660
|
+
seo: {
|
|
6661
|
+
title: raw.name ?? "",
|
|
6662
|
+
description: raw.description?.replace(/<[^>]*>/g, "") ?? ""
|
|
6663
|
+
},
|
|
6664
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6665
|
+
path: `/collections/${raw.slug ?? raw.id}`
|
|
6666
|
+
};
|
|
6667
|
+
}
|
|
6668
|
+
function transformCart4(raw, currencyCode = "USD") {
|
|
6669
|
+
const lines = (raw.items ?? []).map((item) => {
|
|
6670
|
+
const minorUnit2 = item.prices?.currency_minor_unit ?? 2;
|
|
6671
|
+
const divisor2 = Math.pow(10, minorUnit2);
|
|
6672
|
+
const lineTotal = parseInt(item.totals?.line_total ?? "0", 10) / divisor2;
|
|
6673
|
+
return {
|
|
6674
|
+
id: item.key ?? String(item.id),
|
|
6675
|
+
quantity: item.quantity ?? 1,
|
|
6676
|
+
cost: {
|
|
6677
|
+
totalAmount: transformMoney4(lineTotal, currencyCode)
|
|
6678
|
+
},
|
|
6679
|
+
merchandise: {
|
|
6680
|
+
id: String(item.id),
|
|
6681
|
+
title: item.name ?? "",
|
|
6682
|
+
selectedOptions: (item.variation ?? []).map((v) => ({
|
|
6683
|
+
name: v.attribute ?? "",
|
|
6684
|
+
value: v.value ?? ""
|
|
6685
|
+
})),
|
|
6686
|
+
product: {
|
|
6687
|
+
id: String(item.id),
|
|
6688
|
+
handle: item.slug ?? String(item.id),
|
|
6689
|
+
title: item.name ?? "",
|
|
6690
|
+
featuredImage: item.images?.[0] ? transformImage4(item.images[0].src, item.images[0].alt ?? "") : { url: "", altText: "", width: 800, height: 800 }
|
|
6691
|
+
}
|
|
6692
|
+
}
|
|
6693
|
+
};
|
|
6694
|
+
});
|
|
6695
|
+
const minorUnit = raw.totals?.currency_minor_unit ?? 2;
|
|
6696
|
+
const divisor = Math.pow(10, minorUnit);
|
|
6697
|
+
const subtotal = parseInt(raw.totals?.total_items ?? "0", 10) / divisor;
|
|
6698
|
+
const total = parseInt(raw.totals?.total_price ?? "0", 10) / divisor;
|
|
6699
|
+
return {
|
|
6700
|
+
id: raw.cart_key ?? void 0,
|
|
6701
|
+
checkoutUrl: "",
|
|
6702
|
+
// WooCommerce handles checkout via its own /checkout page
|
|
6703
|
+
totalQuantity: raw.items_count ?? lines.reduce((sum, l) => sum + l.quantity, 0),
|
|
6704
|
+
cost: {
|
|
6705
|
+
subtotalAmount: transformMoney4(subtotal, currencyCode),
|
|
6706
|
+
totalAmount: transformMoney4(total, currencyCode)
|
|
6707
|
+
},
|
|
6708
|
+
lines
|
|
6709
|
+
};
|
|
6710
|
+
}
|
|
6711
|
+
function transformWooAddress(raw) {
|
|
6712
|
+
return {
|
|
6713
|
+
id: void 0,
|
|
6714
|
+
firstName: raw.first_name ?? "",
|
|
6715
|
+
lastName: raw.last_name ?? "",
|
|
6716
|
+
address1: raw.address_1 ?? "",
|
|
6717
|
+
address2: raw.address_2 ?? "",
|
|
6718
|
+
city: raw.city ?? "",
|
|
6719
|
+
province: raw.state ?? "",
|
|
6720
|
+
countryCode: raw.country ?? "",
|
|
6721
|
+
postalCode: raw.postcode ?? "",
|
|
6722
|
+
phone: raw.phone ?? "",
|
|
6723
|
+
company: raw.company ?? ""
|
|
6724
|
+
};
|
|
6725
|
+
}
|
|
6726
|
+
function transformOrder4(raw, currencyCode = "USD") {
|
|
6727
|
+
const items = (raw.line_items ?? []).map((item) => ({
|
|
6728
|
+
id: String(item.id),
|
|
6729
|
+
title: item.name ?? "",
|
|
6730
|
+
quantity: item.quantity ?? 1,
|
|
6731
|
+
price: transformMoney4(item.price ?? 0, currencyCode),
|
|
6732
|
+
total: transformMoney4(item.total ?? 0, currencyCode),
|
|
6733
|
+
productId: String(item.product_id),
|
|
6734
|
+
variantId: item.variation_id ? String(item.variation_id) : void 0
|
|
6735
|
+
}));
|
|
6736
|
+
const statusMap = {
|
|
6737
|
+
pending: "pending",
|
|
6738
|
+
processing: "processing",
|
|
6739
|
+
"on-hold": "pending",
|
|
6740
|
+
completed: "completed",
|
|
6741
|
+
cancelled: "cancelled",
|
|
6742
|
+
refunded: "refunded",
|
|
6743
|
+
failed: "cancelled"
|
|
6744
|
+
};
|
|
6745
|
+
const paymentMap = {
|
|
6746
|
+
pending: "pending",
|
|
6747
|
+
processing: "authorized",
|
|
6748
|
+
completed: "paid",
|
|
6749
|
+
refunded: "refunded"
|
|
6750
|
+
};
|
|
6751
|
+
return {
|
|
6752
|
+
id: String(raw.id),
|
|
6753
|
+
orderNumber: String(raw.number ?? raw.id),
|
|
6754
|
+
email: raw.billing?.email ?? "",
|
|
6755
|
+
status: statusMap[raw.status] ?? "pending",
|
|
6756
|
+
paymentStatus: paymentMap[raw.status] ?? "pending",
|
|
6757
|
+
fulfillmentStatus: raw.status === "completed" ? "fulfilled" : "unfulfilled",
|
|
6758
|
+
items,
|
|
6759
|
+
total: transformMoney4(raw.total ?? 0, currencyCode),
|
|
6760
|
+
subtotal: transformMoney4(raw.subtotal ?? 0, currencyCode),
|
|
6761
|
+
tax: transformMoney4(raw.total_tax ?? 0, currencyCode),
|
|
6762
|
+
shipping: transformMoney4(raw.shipping_total ?? 0, currencyCode),
|
|
6763
|
+
shippingAddress: raw.shipping ? transformWooAddress(raw.shipping) : void 0,
|
|
6764
|
+
billingAddress: raw.billing ? transformWooAddress(raw.billing) : void 0,
|
|
6765
|
+
createdAt: raw.date_created ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6766
|
+
updatedAt: raw.date_modified ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
6767
|
+
};
|
|
6768
|
+
}
|
|
6769
|
+
|
|
6770
|
+
// src/providers/woocommerce/index.ts
|
|
6771
|
+
var WooCommerceProvider = class {
|
|
6772
|
+
name = "woocommerce";
|
|
6773
|
+
version = "1.0.0";
|
|
6774
|
+
capabilities = WOOCOMMERCE_CAPABILITIES;
|
|
6775
|
+
logger;
|
|
6776
|
+
baseUrl;
|
|
6777
|
+
storeApiBase;
|
|
6778
|
+
restApiBase;
|
|
6779
|
+
currencyCode;
|
|
6780
|
+
authHeader;
|
|
6781
|
+
constructor(config) {
|
|
6782
|
+
this.logger = createLogger("WooCommerceProvider", config.debug);
|
|
6783
|
+
this.baseUrl = config.url.replace(/\/$/, "");
|
|
6784
|
+
this.storeApiBase = `${this.baseUrl}/wp-json/wc/store/v1`;
|
|
6785
|
+
this.restApiBase = `${this.baseUrl}/wp-json/wc/v3`;
|
|
6786
|
+
this.currencyCode = config.currencyCode ?? "USD";
|
|
6787
|
+
if (config.consumerKey && config.consumerSecret) {
|
|
6788
|
+
this.authHeader = "Basic " + btoa(`${config.consumerKey}:${config.consumerSecret}`);
|
|
6789
|
+
}
|
|
6790
|
+
this.logger.info("Initializing WooCommerce Provider", { baseUrl: this.baseUrl });
|
|
6791
|
+
}
|
|
6792
|
+
async storeApiFetch(path, init) {
|
|
6793
|
+
const response = await fetch(`${this.storeApiBase}${path}`, {
|
|
6794
|
+
...init,
|
|
6795
|
+
headers: { "Content-Type": "application/json", ...init?.headers }
|
|
6796
|
+
});
|
|
6797
|
+
if (!response.ok) {
|
|
6798
|
+
throw new ProviderAPIError(`WooCommerce Store API error: ${response.status}`, "WOOCOMMERCE_API_ERROR", response.status);
|
|
6799
|
+
}
|
|
6800
|
+
return response.json();
|
|
6801
|
+
}
|
|
6802
|
+
async restApiFetch(path, init) {
|
|
6803
|
+
if (!this.authHeader) {
|
|
6804
|
+
throw new ProviderAPIError("WooCommerce REST API requires consumer key/secret.", "WOOCOMMERCE_AUTH_REQUIRED");
|
|
6805
|
+
}
|
|
6806
|
+
const response = await fetch(`${this.restApiBase}${path}`, {
|
|
6807
|
+
...init,
|
|
6808
|
+
headers: { "Content-Type": "application/json", "Authorization": this.authHeader, ...init?.headers }
|
|
6809
|
+
});
|
|
6810
|
+
if (!response.ok) {
|
|
6811
|
+
throw new ProviderAPIError(`WooCommerce REST API error: ${response.status}`, "WOOCOMMERCE_API_ERROR", response.status);
|
|
6812
|
+
}
|
|
6813
|
+
return response.json();
|
|
6814
|
+
}
|
|
6815
|
+
// ============================================
|
|
6816
|
+
// Product Operations (Store API)
|
|
6817
|
+
// ============================================
|
|
6818
|
+
async getProduct(handle) {
|
|
6819
|
+
try {
|
|
6820
|
+
const products = await this.storeApiFetch(`/products?slug=${encodeURIComponent(handle)}`);
|
|
6821
|
+
return products[0] ? transformProduct4(products[0], this.currencyCode) : void 0;
|
|
6822
|
+
} catch {
|
|
6823
|
+
return void 0;
|
|
6824
|
+
}
|
|
6825
|
+
}
|
|
6826
|
+
async getProducts(options) {
|
|
6827
|
+
let path = `/products?per_page=${options?.first ?? 20}`;
|
|
6828
|
+
if (options?.query) path += `&search=${encodeURIComponent(options.query)}`;
|
|
6829
|
+
if (options?.sortKey) {
|
|
6830
|
+
const [orderby, order] = options.sortKey.includes("-desc") ? [options.sortKey.replace("-desc", ""), "desc"] : [options.sortKey.replace("-asc", ""), "asc"];
|
|
6831
|
+
path += `&orderby=${orderby}&order=${order}`;
|
|
6832
|
+
}
|
|
6833
|
+
const products = await this.storeApiFetch(path);
|
|
6834
|
+
return products.map((p) => transformProduct4(p, this.currencyCode));
|
|
6835
|
+
}
|
|
6836
|
+
async getProductRecommendations(_productId) {
|
|
6837
|
+
return [];
|
|
6838
|
+
}
|
|
6839
|
+
// ============================================
|
|
6840
|
+
// Collection/Category Operations (Store API)
|
|
6841
|
+
// ============================================
|
|
6842
|
+
async getCollection(handle) {
|
|
6843
|
+
try {
|
|
6844
|
+
const cats = await this.storeApiFetch(`/products/categories?slug=${encodeURIComponent(handle)}`);
|
|
6845
|
+
return cats[0] ? transformCollection4(cats[0]) : void 0;
|
|
6846
|
+
} catch {
|
|
6847
|
+
return void 0;
|
|
6848
|
+
}
|
|
6849
|
+
}
|
|
6850
|
+
async getCollections(options) {
|
|
6851
|
+
const cats = await this.storeApiFetch(`/products/categories?per_page=${options?.limit ?? 50}`);
|
|
6852
|
+
return cats.map(transformCollection4);
|
|
6853
|
+
}
|
|
6854
|
+
async getCollectionProducts(handle, options) {
|
|
6855
|
+
const products = await this.storeApiFetch(
|
|
6856
|
+
`/products?category=${encodeURIComponent(handle)}&per_page=${options?.first ?? 20}`
|
|
6857
|
+
);
|
|
6858
|
+
return products.map((p) => transformProduct4(p, this.currencyCode));
|
|
6859
|
+
}
|
|
6860
|
+
async getCategory(idOrHandle) {
|
|
6861
|
+
return this.getCollection(idOrHandle);
|
|
6862
|
+
}
|
|
6863
|
+
async getCategories(options) {
|
|
6864
|
+
return this.getCollections(options);
|
|
6865
|
+
}
|
|
6866
|
+
async getCategoryProducts(handle, options) {
|
|
6867
|
+
return this.getCollectionProducts(handle, options);
|
|
6868
|
+
}
|
|
6869
|
+
// ============================================
|
|
6870
|
+
// Cart Operations (Store API)
|
|
6871
|
+
// ============================================
|
|
6872
|
+
async createCart() {
|
|
6873
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6874
|
+
return transformCart4(cart, this.currencyCode);
|
|
6875
|
+
}
|
|
6876
|
+
async getCart(_cartId) {
|
|
6877
|
+
try {
|
|
6878
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6879
|
+
return transformCart4(cart, this.currencyCode);
|
|
6880
|
+
} catch {
|
|
6881
|
+
return void 0;
|
|
6882
|
+
}
|
|
6883
|
+
}
|
|
6884
|
+
async addToCart(_cartId, lines) {
|
|
6885
|
+
for (const line of lines) {
|
|
6886
|
+
await this.storeApiFetch("/cart/add-item", {
|
|
6887
|
+
method: "POST",
|
|
6888
|
+
body: JSON.stringify({ id: parseInt(line.merchandiseId, 10), quantity: line.quantity })
|
|
6889
|
+
});
|
|
6890
|
+
}
|
|
6891
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6892
|
+
return transformCart4(cart, this.currencyCode);
|
|
6893
|
+
}
|
|
6894
|
+
async removeFromCart(_cartId, lineIds) {
|
|
6895
|
+
for (const key of lineIds) {
|
|
6896
|
+
await this.storeApiFetch("/cart/remove-item", {
|
|
6897
|
+
method: "POST",
|
|
6898
|
+
body: JSON.stringify({ key })
|
|
6899
|
+
});
|
|
6900
|
+
}
|
|
6901
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6902
|
+
return transformCart4(cart, this.currencyCode);
|
|
6903
|
+
}
|
|
6904
|
+
async updateCart(_cartId, lines) {
|
|
6905
|
+
for (const line of lines) {
|
|
6906
|
+
await this.storeApiFetch("/cart/update-item", {
|
|
6907
|
+
method: "POST",
|
|
6908
|
+
body: JSON.stringify({ key: line.id, quantity: line.quantity })
|
|
6909
|
+
});
|
|
6910
|
+
}
|
|
6911
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6912
|
+
return transformCart4(cart, this.currencyCode);
|
|
6913
|
+
}
|
|
6914
|
+
// ============================================
|
|
6915
|
+
// Checkout Operations (Store API)
|
|
6916
|
+
// ============================================
|
|
6917
|
+
async completeCheckout(_cartId) {
|
|
6918
|
+
const result = await this.storeApiFetch("/checkout", { method: "POST", body: JSON.stringify({}) });
|
|
6919
|
+
return transformOrder4(result, this.currencyCode);
|
|
6920
|
+
}
|
|
6921
|
+
async getShippingMethods(_cartId) {
|
|
6922
|
+
return [];
|
|
6923
|
+
}
|
|
6924
|
+
async getRegionCountries() {
|
|
6925
|
+
return [];
|
|
6926
|
+
}
|
|
6927
|
+
async addShippingMethod(_cartId, methodId) {
|
|
6928
|
+
await this.storeApiFetch("/cart/select-shipping-rate", {
|
|
6929
|
+
method: "POST",
|
|
6930
|
+
body: JSON.stringify({ rate_id: methodId })
|
|
6931
|
+
});
|
|
6932
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6933
|
+
return transformCart4(cart, this.currencyCode);
|
|
6934
|
+
}
|
|
6935
|
+
async initiatePaymentSession(_cartId, _providerId) {
|
|
6936
|
+
}
|
|
6937
|
+
async applyDiscountCode(_cartId, code) {
|
|
6938
|
+
await this.storeApiFetch("/cart/apply-coupon", {
|
|
6939
|
+
method: "POST",
|
|
6940
|
+
body: JSON.stringify({ code })
|
|
6941
|
+
});
|
|
6942
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6943
|
+
return transformCart4(cart, this.currencyCode);
|
|
6944
|
+
}
|
|
6945
|
+
async removeDiscountCode(_cartId, code) {
|
|
6946
|
+
await this.storeApiFetch("/cart/remove-coupon", {
|
|
6947
|
+
method: "POST",
|
|
6948
|
+
body: JSON.stringify({ code })
|
|
6949
|
+
});
|
|
6950
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6951
|
+
return transformCart4(cart, this.currencyCode);
|
|
6952
|
+
}
|
|
6953
|
+
// ============================================
|
|
6954
|
+
// Customer, Order, Payment, Inventory, Admin
|
|
6955
|
+
// ============================================
|
|
6956
|
+
async getOrders(_options) {
|
|
6957
|
+
return [];
|
|
6958
|
+
}
|
|
6959
|
+
async getPaymentMethods(_options) {
|
|
6960
|
+
return [];
|
|
6961
|
+
}
|
|
6962
|
+
async getStockLocations() {
|
|
6963
|
+
return [];
|
|
6964
|
+
}
|
|
6965
|
+
async checkInventoryLevels() {
|
|
6966
|
+
return [];
|
|
6967
|
+
}
|
|
6968
|
+
async validateCartStock() {
|
|
6969
|
+
return { allAvailable: true, unavailableItems: [] };
|
|
6970
|
+
}
|
|
6971
|
+
async getFulfillmentOptions() {
|
|
6972
|
+
return [];
|
|
6973
|
+
}
|
|
6974
|
+
async cancelOrder(_orderId) {
|
|
6975
|
+
throw new ProviderAPIError("Order cancellation requires WooCommerce REST API v3.", "WOOCOMMERCE_AUTH_REQUIRED");
|
|
6976
|
+
}
|
|
6977
|
+
async createFulfillment(_orderId) {
|
|
6978
|
+
throw new ProviderAPIError("Fulfillment requires WooCommerce REST API v3.", "WOOCOMMERCE_AUTH_REQUIRED");
|
|
6979
|
+
}
|
|
6980
|
+
async createRefund(_orderId) {
|
|
6981
|
+
throw new ProviderAPIError("Refunds require WooCommerce REST API v3.", "WOOCOMMERCE_AUTH_REQUIRED");
|
|
6982
|
+
}
|
|
6983
|
+
async getAdminOrders() {
|
|
6984
|
+
return { orders: [], count: 0 };
|
|
6985
|
+
}
|
|
6986
|
+
async getStorefrontContext() {
|
|
6987
|
+
return {
|
|
6988
|
+
id: this.baseUrl,
|
|
6989
|
+
name: `WooCommerce ${this.baseUrl}`,
|
|
6990
|
+
platformType: "woocommerce",
|
|
6991
|
+
requiresProductScoping: false,
|
|
6992
|
+
currencyCode: this.currencyCode
|
|
6993
|
+
};
|
|
6994
|
+
}
|
|
6995
|
+
isStorefrontRequired() {
|
|
6996
|
+
return false;
|
|
6997
|
+
}
|
|
6998
|
+
};
|
|
6999
|
+
|
|
7000
|
+
// src/providers/spree/transforms.ts
|
|
7001
|
+
function toMoney(amount, currencyCode) {
|
|
7002
|
+
const num = typeof amount === "string" ? parseFloat(amount) || 0 : amount;
|
|
7003
|
+
return { amount: num.toFixed(2), currencyCode };
|
|
7004
|
+
}
|
|
7005
|
+
function transformProduct5(data, included = [], currencyCode = "USD") {
|
|
7006
|
+
const attrs = data.attributes ?? data;
|
|
7007
|
+
const id = String(data.id ?? "");
|
|
7008
|
+
const images = [];
|
|
7009
|
+
const imageRels = data.relationships?.images?.data ?? [];
|
|
7010
|
+
for (const imgRef of imageRels) {
|
|
7011
|
+
const imgIncluded = included.find((i) => i.type === "image" && String(i.id) === String(imgRef.id));
|
|
7012
|
+
if (imgIncluded) {
|
|
7013
|
+
images.push({
|
|
7014
|
+
url: imgIncluded.attributes?.original_url ?? imgIncluded.attributes?.styles?.[0]?.url ?? "",
|
|
7015
|
+
altText: imgIncluded.attributes?.alt ?? attrs.name ?? "",
|
|
7016
|
+
width: void 0,
|
|
7017
|
+
height: void 0
|
|
7018
|
+
});
|
|
7019
|
+
}
|
|
7020
|
+
}
|
|
7021
|
+
if (images.length === 0 && Array.isArray(attrs.images)) {
|
|
7022
|
+
for (const img of attrs.images) {
|
|
7023
|
+
images.push({
|
|
7024
|
+
url: img.original_url ?? img.url ?? "",
|
|
7025
|
+
altText: img.alt ?? attrs.name ?? "",
|
|
7026
|
+
width: void 0,
|
|
7027
|
+
height: void 0
|
|
7028
|
+
});
|
|
7029
|
+
}
|
|
7030
|
+
}
|
|
7031
|
+
const variants = [];
|
|
7032
|
+
const variantRels = data.relationships?.variants?.data ?? [];
|
|
7033
|
+
for (const varRef of variantRels) {
|
|
7034
|
+
const varIncluded = included.find((i) => i.type === "variant" && String(i.id) === String(varRef.id));
|
|
7035
|
+
if (varIncluded) {
|
|
7036
|
+
const vAttrs = varIncluded.attributes ?? {};
|
|
7037
|
+
variants.push({
|
|
7038
|
+
id: String(varIncluded.id),
|
|
7039
|
+
title: vAttrs.name ?? vAttrs.sku ?? `Variant ${varIncluded.id}`,
|
|
7040
|
+
availableForSale: vAttrs.in_stock !== false && vAttrs.purchasable !== false,
|
|
7041
|
+
price: toMoney(vAttrs.price ?? vAttrs.display_price ?? "0", currencyCode),
|
|
7042
|
+
compareAtPrice: vAttrs.compare_at_price ? toMoney(vAttrs.compare_at_price, currencyCode) : void 0,
|
|
7043
|
+
selectedOptions: (vAttrs.option_values ?? []).map((ov) => ({
|
|
7044
|
+
name: ov.option_type_name ?? "Option",
|
|
7045
|
+
value: ov.name ?? ov.presentation ?? ""
|
|
7046
|
+
}))
|
|
7047
|
+
});
|
|
7048
|
+
}
|
|
7049
|
+
}
|
|
7050
|
+
if (variants.length === 0) {
|
|
7051
|
+
variants.push({
|
|
7052
|
+
id: attrs.default_variant_id ? String(attrs.default_variant_id) : id,
|
|
7053
|
+
title: "Default",
|
|
7054
|
+
availableForSale: attrs.in_stock !== false && attrs.purchasable !== false,
|
|
7055
|
+
price: toMoney(attrs.price ?? attrs.display_price ?? "0", currencyCode),
|
|
7056
|
+
compareAtPrice: attrs.compare_at_price ? toMoney(attrs.compare_at_price, currencyCode) : void 0,
|
|
7057
|
+
selectedOptions: []
|
|
7058
|
+
});
|
|
7059
|
+
}
|
|
7060
|
+
const options = (attrs.option_types ?? []).map((ot) => ({
|
|
7061
|
+
id: String(ot.id ?? ot.name ?? ""),
|
|
7062
|
+
name: ot.presentation ?? ot.name ?? "",
|
|
7063
|
+
values: (ot.option_values ?? []).map((ov) => ov.presentation ?? ov.name ?? "")
|
|
7064
|
+
}));
|
|
7065
|
+
return {
|
|
7066
|
+
id,
|
|
7067
|
+
handle: attrs.slug ?? id,
|
|
7068
|
+
title: attrs.name ?? "",
|
|
7069
|
+
description: attrs.description ?? "",
|
|
7070
|
+
descriptionHtml: attrs.description ?? "",
|
|
7071
|
+
availableForSale: attrs.in_stock !== false && attrs.purchasable !== false,
|
|
7072
|
+
priceRange: {
|
|
7073
|
+
minVariantPrice: variants.reduce(
|
|
7074
|
+
(min, v) => v.price.amount < min.amount ? v.price : min,
|
|
7075
|
+
variants[0]?.price ?? toMoney("0", currencyCode)
|
|
7076
|
+
),
|
|
7077
|
+
maxVariantPrice: variants.reduce(
|
|
7078
|
+
(max, v) => v.price.amount > max.amount ? v.price : max,
|
|
7079
|
+
variants[0]?.price ?? toMoney("0", currencyCode)
|
|
7080
|
+
)
|
|
7081
|
+
},
|
|
7082
|
+
variants,
|
|
7083
|
+
options,
|
|
7084
|
+
images,
|
|
7085
|
+
featuredImage: images[0] ?? { url: "", altText: attrs.name ?? "" },
|
|
7086
|
+
tags: [],
|
|
7087
|
+
seo: {
|
|
7088
|
+
title: attrs.meta_title ?? attrs.name ?? "",
|
|
7089
|
+
description: attrs.meta_description ?? ""
|
|
7090
|
+
},
|
|
7091
|
+
updatedAt: attrs.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7092
|
+
createdAt: attrs.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
7093
|
+
};
|
|
7094
|
+
}
|
|
7095
|
+
function transformCollection5(data) {
|
|
7096
|
+
const attrs = data.attributes ?? data;
|
|
7097
|
+
return {
|
|
7098
|
+
id: String(data.id ?? ""),
|
|
7099
|
+
handle: attrs.permalink ?? attrs.slug ?? String(data.id ?? ""),
|
|
7100
|
+
title: attrs.name ?? "",
|
|
7101
|
+
description: attrs.description ?? "",
|
|
7102
|
+
seo: {
|
|
7103
|
+
title: attrs.meta_title ?? attrs.name ?? "",
|
|
7104
|
+
description: attrs.meta_description ?? ""
|
|
7105
|
+
},
|
|
7106
|
+
updatedAt: attrs.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7107
|
+
path: `/collections/${attrs.permalink ?? attrs.slug ?? data.id}`
|
|
7108
|
+
};
|
|
7109
|
+
}
|
|
7110
|
+
function transformCart5(data, currencyCode = "USD") {
|
|
7111
|
+
const attrs = data.attributes ?? data;
|
|
7112
|
+
const items = (attrs.line_items ?? data.included?.filter((i) => i.type === "line_item") ?? []).map(
|
|
7113
|
+
(item) => {
|
|
7114
|
+
const itemAttrs = item.attributes ?? item;
|
|
7115
|
+
return {
|
|
7116
|
+
id: String(item.id ?? itemAttrs.id ?? ""),
|
|
7117
|
+
quantity: itemAttrs.quantity ?? 1,
|
|
7118
|
+
cost: {
|
|
7119
|
+
totalAmount: toMoney(itemAttrs.total ?? itemAttrs.display_total ?? "0", currencyCode)
|
|
7120
|
+
},
|
|
7121
|
+
merchandise: {
|
|
7122
|
+
id: String(itemAttrs.variant_id ?? item.id ?? ""),
|
|
7123
|
+
title: itemAttrs.name ?? "",
|
|
7124
|
+
selectedOptions: [],
|
|
7125
|
+
product: {
|
|
7126
|
+
id: String(itemAttrs.product_id ?? ""),
|
|
7127
|
+
handle: itemAttrs.slug ?? "",
|
|
7128
|
+
title: itemAttrs.name ?? "",
|
|
7129
|
+
featuredImage: { url: itemAttrs.image_url ?? "", altText: itemAttrs.name ?? "" }
|
|
7130
|
+
}
|
|
7131
|
+
}
|
|
7132
|
+
};
|
|
7133
|
+
}
|
|
7134
|
+
);
|
|
7135
|
+
return {
|
|
7136
|
+
id: String(data.id ?? attrs.token ?? ""),
|
|
7137
|
+
checkoutUrl: "",
|
|
7138
|
+
totalQuantity: items.reduce((sum, i) => sum + i.quantity, 0),
|
|
7139
|
+
cost: {
|
|
7140
|
+
subtotalAmount: toMoney(attrs.item_total ?? attrs.display_item_total ?? "0", currencyCode),
|
|
7141
|
+
totalAmount: toMoney(attrs.total ?? attrs.display_total ?? "0", currencyCode),
|
|
7142
|
+
totalTaxAmount: toMoney(attrs.tax_total ?? attrs.display_tax_total ?? "0", currencyCode)
|
|
7143
|
+
},
|
|
7144
|
+
lines: items
|
|
7145
|
+
};
|
|
7146
|
+
}
|
|
7147
|
+
function transformAddress2(addr) {
|
|
7148
|
+
return {
|
|
7149
|
+
id: String(addr.id ?? ""),
|
|
7150
|
+
firstName: addr.first_name ?? addr.firstname ?? "",
|
|
7151
|
+
lastName: addr.last_name ?? addr.lastname ?? "",
|
|
7152
|
+
company: addr.company ?? "",
|
|
7153
|
+
address1: addr.address1 ?? "",
|
|
7154
|
+
address2: addr.address2 ?? "",
|
|
7155
|
+
city: addr.city ?? "",
|
|
7156
|
+
province: addr.state_name ?? addr.state ?? "",
|
|
7157
|
+
postalCode: addr.zipcode ?? addr.zip ?? "",
|
|
7158
|
+
countryCode: addr.country_iso ?? addr.country_code ?? "",
|
|
7159
|
+
phone: addr.phone ?? ""
|
|
7160
|
+
};
|
|
7161
|
+
}
|
|
7162
|
+
function transformOrder5(data, currencyCode = "USD") {
|
|
7163
|
+
const attrs = data.attributes ?? data;
|
|
7164
|
+
const items = (attrs.line_items ?? []).map((item) => ({
|
|
7165
|
+
id: String(item.id ?? ""),
|
|
7166
|
+
title: item.name ?? "",
|
|
7167
|
+
quantity: item.quantity ?? 1,
|
|
7168
|
+
price: toMoney(item.price ?? "0", currencyCode),
|
|
7169
|
+
total: toMoney(item.total ?? "0", currencyCode),
|
|
7170
|
+
productId: String(item.product_id ?? ""),
|
|
7171
|
+
variantId: String(item.variant_id ?? "")
|
|
7172
|
+
}));
|
|
7173
|
+
return {
|
|
7174
|
+
id: String(data.id ?? attrs.number ?? ""),
|
|
7175
|
+
orderNumber: attrs.number ?? String(data.id ?? ""),
|
|
7176
|
+
email: attrs.email ?? "",
|
|
7177
|
+
status: attrs.state === "complete" ? "completed" : attrs.state === "canceled" ? "cancelled" : "pending",
|
|
7178
|
+
paymentStatus: attrs.payment_state === "paid" ? "paid" : attrs.payment_state === "balance_due" ? "pending" : "pending",
|
|
7179
|
+
fulfillmentStatus: attrs.shipment_state === "shipped" ? "fulfilled" : attrs.shipment_state === "partial" ? "partially_fulfilled" : "unfulfilled",
|
|
7180
|
+
items,
|
|
7181
|
+
total: toMoney(attrs.total ?? "0", currencyCode),
|
|
7182
|
+
subtotal: toMoney(attrs.item_total ?? "0", currencyCode),
|
|
7183
|
+
tax: toMoney(attrs.tax_total ?? "0", currencyCode),
|
|
7184
|
+
shipping: toMoney(attrs.ship_total ?? "0", currencyCode),
|
|
7185
|
+
shippingAddress: attrs.ship_address ? transformAddress2(attrs.ship_address) : void 0,
|
|
7186
|
+
billingAddress: attrs.bill_address ? transformAddress2(attrs.bill_address) : void 0,
|
|
7187
|
+
createdAt: attrs.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7188
|
+
updatedAt: attrs.updated_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
7189
|
+
};
|
|
7190
|
+
}
|
|
7191
|
+
|
|
7192
|
+
// src/providers/spree/index.ts
|
|
7193
|
+
var SpreeProvider = class {
|
|
7194
|
+
name = "spree";
|
|
7195
|
+
version = "1.0.0";
|
|
7196
|
+
capabilities = SPREE_CAPABILITIES;
|
|
7197
|
+
logger;
|
|
7198
|
+
baseUrl;
|
|
7199
|
+
apiBase;
|
|
7200
|
+
currencyCode;
|
|
7201
|
+
bearerToken;
|
|
7202
|
+
orderToken;
|
|
7203
|
+
// guest cart token
|
|
7204
|
+
constructor(config) {
|
|
7205
|
+
this.logger = createLogger("SpreeProvider", config.debug);
|
|
7206
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
7207
|
+
this.apiBase = `${this.baseUrl}/api/v2/storefront`;
|
|
7208
|
+
this.currencyCode = config.currencyCode ?? "USD";
|
|
7209
|
+
this.bearerToken = config.bearerToken;
|
|
7210
|
+
this.logger.info("Initializing Spree Provider", { baseUrl: this.baseUrl });
|
|
7211
|
+
}
|
|
7212
|
+
async apiFetch(path, init) {
|
|
7213
|
+
const headers = {
|
|
7214
|
+
"Content-Type": "application/vnd.api+json",
|
|
7215
|
+
"Accept": "application/vnd.api+json",
|
|
7216
|
+
...init?.headers ?? {}
|
|
7217
|
+
};
|
|
7218
|
+
if (this.bearerToken) {
|
|
7219
|
+
headers["Authorization"] = `Bearer ${this.bearerToken}`;
|
|
7220
|
+
}
|
|
7221
|
+
if (this.orderToken) {
|
|
7222
|
+
headers["X-Spree-Order-Token"] = this.orderToken;
|
|
7223
|
+
}
|
|
7224
|
+
const response = await fetch(`${this.apiBase}${path}`, {
|
|
7225
|
+
...init,
|
|
7226
|
+
headers
|
|
7227
|
+
});
|
|
7228
|
+
if (!response.ok) {
|
|
7229
|
+
throw new ProviderAPIError(
|
|
7230
|
+
`Spree API error: ${response.status}`,
|
|
7231
|
+
"SPREE_API_ERROR",
|
|
7232
|
+
response.status
|
|
7233
|
+
);
|
|
7234
|
+
}
|
|
7235
|
+
return response.json();
|
|
7236
|
+
}
|
|
7237
|
+
// ============================================
|
|
7238
|
+
// Product Operations
|
|
7239
|
+
// ============================================
|
|
7240
|
+
async getProduct(handle) {
|
|
7241
|
+
try {
|
|
7242
|
+
const { data, included } = await this.apiFetch(
|
|
7243
|
+
`/products/${encodeURIComponent(handle)}?include=images,variants,option_types`
|
|
7244
|
+
);
|
|
7245
|
+
return transformProduct5(data, included ?? [], this.currencyCode);
|
|
7246
|
+
} catch {
|
|
7247
|
+
return void 0;
|
|
7248
|
+
}
|
|
7249
|
+
}
|
|
7250
|
+
async getProducts(options) {
|
|
7251
|
+
let path = `/products?include=images,variants&per_page=${options?.first ?? 20}`;
|
|
7252
|
+
if (options?.query) path += `&filter[name]=${encodeURIComponent(options.query)}`;
|
|
7253
|
+
if (options?.sortKey) {
|
|
7254
|
+
const sortMap = {
|
|
7255
|
+
"price-asc": "price",
|
|
7256
|
+
"price-desc": "-price",
|
|
7257
|
+
"title-asc": "name",
|
|
7258
|
+
"title-desc": "-name",
|
|
7259
|
+
"created-desc": "-created_at"
|
|
7260
|
+
};
|
|
7261
|
+
if (sortMap[options.sortKey]) path += `&sort=${sortMap[options.sortKey]}`;
|
|
7262
|
+
}
|
|
7263
|
+
const { data, included } = await this.apiFetch(path);
|
|
7264
|
+
return (Array.isArray(data) ? data : []).map((p) => transformProduct5(p, included ?? [], this.currencyCode));
|
|
7265
|
+
}
|
|
7266
|
+
async getProductRecommendations(_productId) {
|
|
7267
|
+
return [];
|
|
7268
|
+
}
|
|
7269
|
+
// ============================================
|
|
7270
|
+
// Collection/Taxon Operations
|
|
7271
|
+
// ============================================
|
|
7272
|
+
async getCollection(handle) {
|
|
7273
|
+
try {
|
|
7274
|
+
const { data } = await this.apiFetch(`/taxons/${encodeURIComponent(handle)}`);
|
|
7275
|
+
return transformCollection5(data);
|
|
7276
|
+
} catch {
|
|
7277
|
+
return void 0;
|
|
7278
|
+
}
|
|
7279
|
+
}
|
|
7280
|
+
async getCollections(options) {
|
|
7281
|
+
const { data } = await this.apiFetch(`/taxons?per_page=${options?.limit ?? 50}`);
|
|
7282
|
+
return (Array.isArray(data) ? data : []).map(transformCollection5);
|
|
7283
|
+
}
|
|
7284
|
+
async getCollectionProducts(handle, options) {
|
|
7285
|
+
const { data, included } = await this.apiFetch(
|
|
7286
|
+
`/products?include=images,variants&filter[taxons]=${encodeURIComponent(handle)}&per_page=${options?.first ?? 20}`
|
|
7287
|
+
);
|
|
7288
|
+
return (Array.isArray(data) ? data : []).map((p) => transformProduct5(p, included ?? [], this.currencyCode));
|
|
7289
|
+
}
|
|
7290
|
+
async getCategory(handle) {
|
|
7291
|
+
return this.getCollection(handle);
|
|
7292
|
+
}
|
|
7293
|
+
async getCategories(options) {
|
|
7294
|
+
return this.getCollections(options);
|
|
7295
|
+
}
|
|
7296
|
+
async getCategoryProducts(handle, options) {
|
|
7297
|
+
return this.getCollectionProducts(handle, options);
|
|
7298
|
+
}
|
|
7299
|
+
// ============================================
|
|
7300
|
+
// Cart Operations
|
|
7301
|
+
// ============================================
|
|
7302
|
+
async createCart() {
|
|
7303
|
+
const { data } = await this.apiFetch("/cart", {
|
|
7304
|
+
method: "POST",
|
|
7305
|
+
body: JSON.stringify({})
|
|
7306
|
+
});
|
|
7307
|
+
if (data.attributes?.token) {
|
|
7308
|
+
this.orderToken = data.attributes.token;
|
|
7309
|
+
}
|
|
7310
|
+
return transformCart5(data, this.currencyCode);
|
|
7311
|
+
}
|
|
7312
|
+
async getCart(_cartId) {
|
|
7313
|
+
try {
|
|
7314
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7315
|
+
return transformCart5(data, this.currencyCode);
|
|
7316
|
+
} catch {
|
|
7317
|
+
return void 0;
|
|
7318
|
+
}
|
|
7319
|
+
}
|
|
7320
|
+
async addToCart(_cartId, lines) {
|
|
7321
|
+
for (const line of lines) {
|
|
7322
|
+
await this.apiFetch("/cart/add_item", {
|
|
7323
|
+
method: "POST",
|
|
7324
|
+
body: JSON.stringify({ variant_id: line.merchandiseId, quantity: line.quantity })
|
|
7325
|
+
});
|
|
7326
|
+
}
|
|
7327
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7328
|
+
return transformCart5(data, this.currencyCode);
|
|
7329
|
+
}
|
|
7330
|
+
async removeFromCart(_cartId, lineIds) {
|
|
7331
|
+
for (const id of lineIds) {
|
|
7332
|
+
await this.apiFetch(`/cart/remove_line_item/${id}`, { method: "DELETE" });
|
|
7333
|
+
}
|
|
7334
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7335
|
+
return transformCart5(data, this.currencyCode);
|
|
7336
|
+
}
|
|
7337
|
+
async updateCart(_cartId, lines) {
|
|
7338
|
+
for (const line of lines) {
|
|
7339
|
+
await this.apiFetch(`/cart/set_quantity`, {
|
|
7340
|
+
method: "PATCH",
|
|
7341
|
+
body: JSON.stringify({ line_item_id: line.id, quantity: line.quantity })
|
|
7342
|
+
});
|
|
7343
|
+
}
|
|
7344
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7345
|
+
return transformCart5(data, this.currencyCode);
|
|
7346
|
+
}
|
|
7347
|
+
// ============================================
|
|
7348
|
+
// Checkout Operations
|
|
7349
|
+
// ============================================
|
|
7350
|
+
async completeCheckout(_cartId) {
|
|
7351
|
+
const { data } = await this.apiFetch("/checkout/complete", { method: "PATCH" });
|
|
7352
|
+
return transformOrder5(data, this.currencyCode);
|
|
7353
|
+
}
|
|
7354
|
+
async getShippingMethods(_cartId) {
|
|
7355
|
+
try {
|
|
7356
|
+
const { data } = await this.apiFetch("/checkout/shipping_rates");
|
|
7357
|
+
return (Array.isArray(data) ? data : []).map((rate) => ({
|
|
7358
|
+
id: String(rate.id),
|
|
7359
|
+
name: rate.attributes?.name ?? rate.name ?? "",
|
|
7360
|
+
price: { amount: String(parseFloat(rate.attributes?.final_price ?? rate.attributes?.cost ?? "0").toFixed(2)), currencyCode: this.currencyCode }
|
|
7361
|
+
}));
|
|
7362
|
+
} catch {
|
|
7363
|
+
return [];
|
|
7364
|
+
}
|
|
7365
|
+
}
|
|
7366
|
+
async getRegionCountries() {
|
|
7367
|
+
return [];
|
|
7368
|
+
}
|
|
7369
|
+
async addShippingMethod(_cartId, methodId) {
|
|
7370
|
+
await this.apiFetch("/checkout", {
|
|
7371
|
+
method: "PATCH",
|
|
7372
|
+
body: JSON.stringify({ order: { shipments_attributes: [{ selected_shipping_rate_id: methodId }] } })
|
|
7373
|
+
});
|
|
7374
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7375
|
+
return transformCart5(data, this.currencyCode);
|
|
7376
|
+
}
|
|
7377
|
+
async initiatePaymentSession(_cartId, _providerId) {
|
|
7378
|
+
}
|
|
7379
|
+
async applyDiscountCode(_cartId, code) {
|
|
7380
|
+
await this.apiFetch("/cart/apply_coupon_code", {
|
|
7381
|
+
method: "PATCH",
|
|
7382
|
+
body: JSON.stringify({ coupon_code: code })
|
|
7383
|
+
});
|
|
7384
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7385
|
+
return transformCart5(data, this.currencyCode);
|
|
7386
|
+
}
|
|
7387
|
+
async removeDiscountCode(_cartId, code) {
|
|
7388
|
+
await this.apiFetch(`/cart/remove_coupon_code/${encodeURIComponent(code)}`, { method: "DELETE" });
|
|
7389
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7390
|
+
return transformCart5(data, this.currencyCode);
|
|
7391
|
+
}
|
|
7392
|
+
// ============================================
|
|
7393
|
+
// Customer, Order, Payment, Inventory, Admin
|
|
7394
|
+
// ============================================
|
|
7395
|
+
async getOrders(_options) {
|
|
7396
|
+
try {
|
|
7397
|
+
const { data } = await this.apiFetch("/account/orders");
|
|
7398
|
+
return (Array.isArray(data) ? data : []).map((o) => transformOrder5(o, this.currencyCode));
|
|
7399
|
+
} catch {
|
|
7400
|
+
return [];
|
|
7401
|
+
}
|
|
7402
|
+
}
|
|
7403
|
+
async getPaymentMethods(_options) {
|
|
7404
|
+
try {
|
|
7405
|
+
const { data } = await this.apiFetch("/checkout/payment_methods");
|
|
7406
|
+
return (Array.isArray(data) ? data : []).map((pm) => ({
|
|
7407
|
+
id: String(pm.id),
|
|
7408
|
+
name: pm.attributes?.name ?? "",
|
|
7409
|
+
type: "other",
|
|
7410
|
+
isEnabled: true,
|
|
7411
|
+
requiresSetup: false
|
|
7412
|
+
}));
|
|
7413
|
+
} catch {
|
|
7414
|
+
return [];
|
|
7415
|
+
}
|
|
7416
|
+
}
|
|
7417
|
+
async getStockLocations() {
|
|
7418
|
+
return [];
|
|
7419
|
+
}
|
|
7420
|
+
async checkInventoryLevels() {
|
|
7421
|
+
return [];
|
|
7422
|
+
}
|
|
7423
|
+
async validateCartStock() {
|
|
7424
|
+
return { allAvailable: true, unavailableItems: [] };
|
|
7425
|
+
}
|
|
7426
|
+
async getFulfillmentOptions() {
|
|
7427
|
+
return [];
|
|
7428
|
+
}
|
|
7429
|
+
async cancelOrder(_orderId) {
|
|
7430
|
+
throw new ProviderAPIError("Order cancellation requires Spree Admin API.", "SPREE_ADMIN_REQUIRED");
|
|
7431
|
+
}
|
|
7432
|
+
async createFulfillment(_orderId) {
|
|
7433
|
+
throw new ProviderAPIError("Fulfillment requires Spree Admin API.", "SPREE_ADMIN_REQUIRED");
|
|
7434
|
+
}
|
|
7435
|
+
async createRefund(_orderId) {
|
|
7436
|
+
throw new ProviderAPIError("Refunds require Spree Admin API.", "SPREE_ADMIN_REQUIRED");
|
|
7437
|
+
}
|
|
7438
|
+
async getAdminOrders() {
|
|
7439
|
+
return { orders: [], count: 0 };
|
|
7440
|
+
}
|
|
7441
|
+
async getStorefrontContext() {
|
|
7442
|
+
return {
|
|
7443
|
+
id: this.baseUrl,
|
|
7444
|
+
name: `Spree ${this.baseUrl}`,
|
|
7445
|
+
platformType: "spree",
|
|
7446
|
+
requiresProductScoping: true,
|
|
7447
|
+
currencyCode: this.currencyCode
|
|
7448
|
+
};
|
|
7449
|
+
}
|
|
7450
|
+
isStorefrontRequired() {
|
|
7451
|
+
return true;
|
|
7452
|
+
}
|
|
7453
|
+
};
|
|
7454
|
+
|
|
7455
|
+
// src/providers/magento/transforms.ts
|
|
7456
|
+
function toMoney2(value, currencyCode) {
|
|
7457
|
+
return { amount: (value ?? 0).toFixed(2), currencyCode };
|
|
7458
|
+
}
|
|
7459
|
+
function extractPrice(priceRange, currencyCode) {
|
|
7460
|
+
const minPrice = priceRange?.minimum_price?.regular_price?.value ?? priceRange?.minimum_price?.final_price?.value ?? 0;
|
|
7461
|
+
const maxPrice = priceRange?.maximum_price?.regular_price?.value ?? priceRange?.maximum_price?.final_price?.value ?? minPrice;
|
|
7462
|
+
const currency = priceRange?.minimum_price?.regular_price?.currency ?? currencyCode;
|
|
7463
|
+
return {
|
|
7464
|
+
min: toMoney2(minPrice, currency),
|
|
7465
|
+
max: toMoney2(maxPrice, currency)
|
|
7466
|
+
};
|
|
7467
|
+
}
|
|
7468
|
+
function transformProduct6(data, currencyCode = "USD") {
|
|
7469
|
+
const id = String(data.id ?? data.uid ?? "");
|
|
7470
|
+
const { min, max } = extractPrice(data.price_range, currencyCode);
|
|
7471
|
+
const images = (data.media_gallery ?? data.media_gallery_entries ?? []).map((img) => ({
|
|
7472
|
+
url: img.url ?? (img.file ? `${img.file}` : ""),
|
|
7473
|
+
altText: img.label ?? data.name ?? "",
|
|
7474
|
+
width: void 0,
|
|
7475
|
+
height: void 0
|
|
7476
|
+
}));
|
|
7477
|
+
if (images.length === 0 && data.small_image?.url) {
|
|
7478
|
+
images.push({ url: data.small_image.url, altText: data.small_image.label ?? data.name ?? "" });
|
|
7479
|
+
}
|
|
7480
|
+
if (images.length === 0 && data.image?.url) {
|
|
7481
|
+
images.push({ url: data.image.url, altText: data.image.label ?? data.name ?? "" });
|
|
7482
|
+
}
|
|
7483
|
+
const variants = [];
|
|
7484
|
+
if (data.variants && Array.isArray(data.variants)) {
|
|
7485
|
+
for (const v of data.variants) {
|
|
7486
|
+
const product = v.product ?? {};
|
|
7487
|
+
const vPrice = product.price_range?.minimum_price?.final_price ?? {};
|
|
7488
|
+
variants.push({
|
|
7489
|
+
id: String(product.id ?? product.uid ?? product.sku ?? ""),
|
|
7490
|
+
title: product.name ?? product.sku ?? "",
|
|
7491
|
+
availableForSale: product.stock_status === "IN_STOCK",
|
|
7492
|
+
price: toMoney2(vPrice.value ?? min.amount, vPrice.currency ?? currencyCode),
|
|
7493
|
+
compareAtPrice: void 0,
|
|
7494
|
+
selectedOptions: (v.attributes ?? []).map((attr) => ({
|
|
7495
|
+
name: attr.label ?? "",
|
|
7496
|
+
value: attr.value_index != null ? String(attr.value_index) : ""
|
|
7497
|
+
}))
|
|
7498
|
+
});
|
|
7499
|
+
}
|
|
7500
|
+
}
|
|
7501
|
+
if (variants.length === 0) {
|
|
7502
|
+
variants.push({
|
|
7503
|
+
id: data.sku ?? id,
|
|
7504
|
+
title: data.name ?? "",
|
|
7505
|
+
availableForSale: data.stock_status === "IN_STOCK",
|
|
7506
|
+
price: min,
|
|
7507
|
+
compareAtPrice: void 0,
|
|
7508
|
+
selectedOptions: []
|
|
7509
|
+
});
|
|
7510
|
+
}
|
|
7511
|
+
const options = (data.configurable_options ?? []).map((opt) => ({
|
|
7512
|
+
id: String(opt.attribute_id ?? opt.attribute_code ?? ""),
|
|
7513
|
+
name: opt.label ?? opt.attribute_code ?? "",
|
|
7514
|
+
values: (opt.values ?? []).map((v) => v.label ?? v.store_label ?? String(v.value_index ?? ""))
|
|
7515
|
+
}));
|
|
7516
|
+
return {
|
|
7517
|
+
id,
|
|
7518
|
+
handle: data.url_key ?? data.sku ?? id,
|
|
7519
|
+
title: data.name ?? "",
|
|
7520
|
+
description: data.short_description?.html ?? data.description?.html ?? "",
|
|
7521
|
+
descriptionHtml: data.description?.html ?? "",
|
|
7522
|
+
availableForSale: data.stock_status === "IN_STOCK",
|
|
7523
|
+
priceRange: { minVariantPrice: min, maxVariantPrice: max },
|
|
7524
|
+
variants,
|
|
7525
|
+
options,
|
|
7526
|
+
images,
|
|
7527
|
+
featuredImage: images[0] ?? { url: "", altText: data.name ?? "" },
|
|
7528
|
+
tags: [],
|
|
7529
|
+
seo: {
|
|
7530
|
+
title: data.meta_title ?? data.name ?? "",
|
|
7531
|
+
description: data.meta_description ?? ""
|
|
7532
|
+
},
|
|
7533
|
+
updatedAt: data.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7534
|
+
createdAt: data.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
7535
|
+
};
|
|
7536
|
+
}
|
|
7537
|
+
function transformCollection6(data) {
|
|
7538
|
+
return {
|
|
7539
|
+
id: String(data.id ?? data.uid ?? ""),
|
|
7540
|
+
handle: data.url_key ?? data.url_path ?? String(data.id ?? data.uid ?? ""),
|
|
7541
|
+
title: data.name ?? "",
|
|
7542
|
+
description: data.description ?? "",
|
|
7543
|
+
seo: {
|
|
7544
|
+
title: data.meta_title ?? data.name ?? "",
|
|
7545
|
+
description: data.meta_description ?? ""
|
|
7546
|
+
},
|
|
7547
|
+
updatedAt: data.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7548
|
+
path: `/collections/${data.url_key ?? data.url_path ?? data.id}`
|
|
7549
|
+
};
|
|
7550
|
+
}
|
|
7551
|
+
function transformCart6(data, currencyCode = "USD") {
|
|
7552
|
+
const items = (data.items ?? []).map((item) => {
|
|
7553
|
+
const product = item.product ?? {};
|
|
7554
|
+
const priceVal = item.prices?.price?.value ?? product.price_range?.minimum_price?.final_price?.value ?? 0;
|
|
7555
|
+
const totalVal2 = item.prices?.row_total?.value ?? priceVal * (item.quantity ?? 1);
|
|
7556
|
+
return {
|
|
7557
|
+
id: String(item.id ?? item.uid ?? ""),
|
|
7558
|
+
quantity: item.quantity ?? 1,
|
|
7559
|
+
cost: {
|
|
7560
|
+
totalAmount: toMoney2(totalVal2, item.prices?.price?.currency ?? currencyCode)
|
|
7561
|
+
},
|
|
7562
|
+
merchandise: {
|
|
7563
|
+
id: String(product.sku ?? item.id ?? ""),
|
|
7564
|
+
title: product.name ?? item.product?.name ?? "",
|
|
7565
|
+
selectedOptions: (item.configurable_options ?? []).map((opt) => ({
|
|
7566
|
+
name: opt.option_label ?? "",
|
|
7567
|
+
value: opt.value_label ?? ""
|
|
7568
|
+
})),
|
|
7569
|
+
product: {
|
|
7570
|
+
id: String(product.id ?? product.uid ?? ""),
|
|
7571
|
+
handle: product.url_key ?? "",
|
|
7572
|
+
title: product.name ?? "",
|
|
7573
|
+
featuredImage: {
|
|
7574
|
+
url: product.small_image?.url ?? product.thumbnail?.url ?? "",
|
|
7575
|
+
altText: product.name ?? ""
|
|
7576
|
+
}
|
|
7577
|
+
}
|
|
7578
|
+
}
|
|
7579
|
+
};
|
|
7580
|
+
});
|
|
7581
|
+
const totalVal = data.prices?.grand_total?.value ?? 0;
|
|
7582
|
+
const subtotalVal = data.prices?.subtotal_excluding_tax?.value ?? data.prices?.subtotal_including_tax?.value ?? totalVal;
|
|
7583
|
+
const taxVal = (data.prices?.applied_taxes ?? []).reduce((sum, t) => sum + (t.amount?.value ?? 0), 0);
|
|
7584
|
+
return {
|
|
7585
|
+
id: String(data.id ?? ""),
|
|
7586
|
+
checkoutUrl: "",
|
|
7587
|
+
totalQuantity: items.reduce((sum, i) => sum + i.quantity, 0),
|
|
7588
|
+
cost: {
|
|
7589
|
+
subtotalAmount: toMoney2(subtotalVal, data.prices?.grand_total?.currency ?? currencyCode),
|
|
7590
|
+
totalAmount: toMoney2(totalVal, data.prices?.grand_total?.currency ?? currencyCode),
|
|
7591
|
+
totalTaxAmount: toMoney2(taxVal, currencyCode)
|
|
7592
|
+
},
|
|
7593
|
+
lines: items
|
|
7594
|
+
};
|
|
7595
|
+
}
|
|
7596
|
+
function transformAddress3(addr) {
|
|
7597
|
+
return {
|
|
7598
|
+
id: String(addr.id ?? ""),
|
|
7599
|
+
firstName: addr.firstname ?? "",
|
|
7600
|
+
lastName: addr.lastname ?? "",
|
|
7601
|
+
company: addr.company ?? "",
|
|
7602
|
+
address1: Array.isArray(addr.street) ? addr.street[0] ?? "" : addr.street ?? "",
|
|
7603
|
+
address2: Array.isArray(addr.street) ? addr.street[1] ?? "" : "",
|
|
7604
|
+
city: addr.city ?? "",
|
|
7605
|
+
province: addr.region?.region ?? "",
|
|
7606
|
+
postalCode: addr.postcode ?? "",
|
|
7607
|
+
countryCode: addr.country_code ?? addr.country_id ?? "",
|
|
7608
|
+
phone: addr.telephone ?? ""
|
|
7609
|
+
};
|
|
7610
|
+
}
|
|
7611
|
+
function transformOrder6(data, currencyCode = "USD") {
|
|
7612
|
+
const items = (data.items ?? []).map((item) => ({
|
|
7613
|
+
id: String(item.id ?? ""),
|
|
7614
|
+
title: item.product_name ?? "",
|
|
7615
|
+
quantity: item.quantity_ordered ?? item.quantity ?? 1,
|
|
7616
|
+
price: toMoney2(item.product_sale_price?.value ?? item.price ?? 0, currencyCode),
|
|
7617
|
+
total: toMoney2((item.product_sale_price?.value ?? item.price ?? 0) * (item.quantity_ordered ?? item.quantity ?? 1), currencyCode),
|
|
7618
|
+
sku: item.product_sku ?? item.sku,
|
|
7619
|
+
productId: String(item.product_id ?? "")
|
|
7620
|
+
}));
|
|
7621
|
+
return {
|
|
7622
|
+
id: String(data.id ?? data.order_number ?? ""),
|
|
7623
|
+
orderNumber: data.order_number ?? data.number ?? String(data.id ?? ""),
|
|
7624
|
+
email: data.email ?? "",
|
|
7625
|
+
status: data.status === "complete" ? "completed" : data.status === "canceled" ? "cancelled" : data.status === "closed" ? "refunded" : "pending",
|
|
7626
|
+
paymentStatus: data.payment_status === "paid" ? "paid" : "pending",
|
|
7627
|
+
fulfillmentStatus: data.shipment_status === "shipped" ? "fulfilled" : "unfulfilled",
|
|
7628
|
+
items,
|
|
7629
|
+
total: toMoney2(data.total?.grand_total?.value ?? data.grand_total ?? 0, currencyCode),
|
|
7630
|
+
subtotal: toMoney2(data.total?.subtotal?.value ?? data.subtotal ?? 0, currencyCode),
|
|
7631
|
+
tax: toMoney2(data.total?.total_tax?.value ?? data.tax_amount ?? 0, currencyCode),
|
|
7632
|
+
shipping: toMoney2(data.total?.total_shipping?.value ?? data.shipping_amount ?? 0, currencyCode),
|
|
7633
|
+
shippingAddress: data.shipping_address ? transformAddress3(data.shipping_address) : void 0,
|
|
7634
|
+
billingAddress: data.billing_address ? transformAddress3(data.billing_address) : void 0,
|
|
7635
|
+
createdAt: data.order_date ?? data.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7636
|
+
updatedAt: data.updated_at ?? data.order_date ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
7637
|
+
};
|
|
7638
|
+
}
|
|
7639
|
+
|
|
7640
|
+
// src/providers/magento/queries.ts
|
|
7641
|
+
var PRODUCT_FIELDS = `
|
|
7642
|
+
id
|
|
7643
|
+
uid
|
|
7644
|
+
sku
|
|
7645
|
+
name
|
|
7646
|
+
url_key
|
|
7647
|
+
stock_status
|
|
7648
|
+
short_description { html }
|
|
7649
|
+
description { html }
|
|
7650
|
+
meta_title
|
|
7651
|
+
meta_description
|
|
7652
|
+
created_at
|
|
7653
|
+
updated_at
|
|
7654
|
+
price_range {
|
|
7655
|
+
minimum_price {
|
|
7656
|
+
regular_price { value currency }
|
|
7657
|
+
final_price { value currency }
|
|
7658
|
+
}
|
|
7659
|
+
maximum_price {
|
|
7660
|
+
regular_price { value currency }
|
|
7661
|
+
final_price { value currency }
|
|
7662
|
+
}
|
|
7663
|
+
}
|
|
7664
|
+
small_image { url label }
|
|
7665
|
+
image { url label }
|
|
7666
|
+
media_gallery {
|
|
7667
|
+
url
|
|
7668
|
+
label
|
|
7669
|
+
}
|
|
7670
|
+
... on ConfigurableProduct {
|
|
7671
|
+
configurable_options {
|
|
7672
|
+
attribute_code
|
|
7673
|
+
attribute_id
|
|
7674
|
+
label
|
|
7675
|
+
values {
|
|
7676
|
+
label
|
|
7677
|
+
value_index
|
|
7678
|
+
}
|
|
7679
|
+
}
|
|
7680
|
+
variants {
|
|
7681
|
+
product {
|
|
7682
|
+
id uid sku name
|
|
7683
|
+
stock_status
|
|
7684
|
+
price_range {
|
|
7685
|
+
minimum_price {
|
|
7686
|
+
final_price { value currency }
|
|
7687
|
+
}
|
|
7688
|
+
}
|
|
7689
|
+
small_image { url label }
|
|
7690
|
+
}
|
|
7691
|
+
attributes {
|
|
7692
|
+
label
|
|
7693
|
+
value_index
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7696
|
+
}
|
|
7697
|
+
`;
|
|
7698
|
+
var GET_PRODUCTS2 = `
|
|
7699
|
+
query GetProducts($search: String, $pageSize: Int, $currentPage: Int, $sort: ProductAttributeSortInput) {
|
|
7700
|
+
products(search: $search, pageSize: $pageSize, currentPage: $currentPage, sort: $sort) {
|
|
7701
|
+
items { ${PRODUCT_FIELDS} }
|
|
7702
|
+
total_count
|
|
7703
|
+
}
|
|
7704
|
+
}
|
|
7705
|
+
`;
|
|
7706
|
+
var GET_PRODUCT_BY_URL_KEY = `
|
|
7707
|
+
query GetProductByUrlKey($urlKey: String!) {
|
|
7708
|
+
products(filter: { url_key: { eq: $urlKey } }, pageSize: 1) {
|
|
7709
|
+
items { ${PRODUCT_FIELDS} }
|
|
7710
|
+
}
|
|
7711
|
+
}
|
|
7712
|
+
`;
|
|
7713
|
+
var GET_CATEGORIES = `
|
|
7714
|
+
query GetCategories($pageSize: Int) {
|
|
7715
|
+
categories(pageSize: $pageSize) {
|
|
7716
|
+
items {
|
|
7717
|
+
id uid name url_key url_path description meta_title meta_description
|
|
7718
|
+
}
|
|
7719
|
+
}
|
|
7720
|
+
}
|
|
7721
|
+
`;
|
|
7722
|
+
var GET_CATEGORY_BY_URL_KEY = `
|
|
7723
|
+
query GetCategoryByUrlKey($urlKey: String!) {
|
|
7724
|
+
categories(filters: { url_key: { eq: $urlKey } }, pageSize: 1) {
|
|
7725
|
+
items {
|
|
7726
|
+
id uid name url_key url_path description meta_title meta_description
|
|
7727
|
+
}
|
|
7728
|
+
}
|
|
7729
|
+
}
|
|
7730
|
+
`;
|
|
7731
|
+
var GET_CATEGORY_PRODUCTS = `
|
|
7732
|
+
query GetCategoryProducts($urlKey: String!, $pageSize: Int) {
|
|
7733
|
+
products(filter: { category_url_key: { eq: $urlKey } }, pageSize: $pageSize) {
|
|
7734
|
+
items { ${PRODUCT_FIELDS} }
|
|
7735
|
+
total_count
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
`;
|
|
7739
|
+
var GET_CART2 = `
|
|
7740
|
+
query GetCart($cartId: String!) {
|
|
7741
|
+
cart(cart_id: $cartId) {
|
|
7742
|
+
id
|
|
7743
|
+
items {
|
|
7744
|
+
id uid quantity
|
|
7745
|
+
product {
|
|
7746
|
+
id uid sku name url_key
|
|
7747
|
+
small_image { url }
|
|
7748
|
+
thumbnail { url }
|
|
7749
|
+
price_range {
|
|
7750
|
+
minimum_price {
|
|
7751
|
+
final_price { value currency }
|
|
7752
|
+
}
|
|
7753
|
+
}
|
|
7754
|
+
}
|
|
7755
|
+
prices {
|
|
7756
|
+
price { value currency }
|
|
7757
|
+
row_total { value currency }
|
|
7758
|
+
}
|
|
7759
|
+
... on ConfigurableCartItem {
|
|
7760
|
+
configurable_options {
|
|
7761
|
+
option_label
|
|
7762
|
+
value_label
|
|
7763
|
+
}
|
|
7764
|
+
}
|
|
7765
|
+
}
|
|
7766
|
+
prices {
|
|
7767
|
+
grand_total { value currency }
|
|
7768
|
+
subtotal_excluding_tax { value currency }
|
|
7769
|
+
subtotal_including_tax { value currency }
|
|
7770
|
+
applied_taxes {
|
|
7771
|
+
amount { value currency }
|
|
7772
|
+
label
|
|
7773
|
+
}
|
|
7774
|
+
}
|
|
7775
|
+
}
|
|
7776
|
+
}
|
|
7777
|
+
`;
|
|
7778
|
+
var CREATE_EMPTY_CART = `
|
|
7779
|
+
mutation CreateEmptyCart {
|
|
7780
|
+
createEmptyCart
|
|
7781
|
+
}
|
|
7782
|
+
`;
|
|
7783
|
+
var ADD_TO_CART = `
|
|
7784
|
+
mutation AddToCart($cartId: String!, $cartItems: [CartItemInput!]!) {
|
|
7785
|
+
addProductsToCart(cartId: $cartId, cartItems: $cartItems) {
|
|
7786
|
+
cart {
|
|
7787
|
+
id
|
|
7788
|
+
items { id uid quantity product { sku name } prices { price { value currency } row_total { value currency } } }
|
|
7789
|
+
prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
|
|
7790
|
+
}
|
|
7791
|
+
user_errors { code message }
|
|
7792
|
+
}
|
|
7793
|
+
}
|
|
7794
|
+
`;
|
|
7795
|
+
var REMOVE_FROM_CART = `
|
|
7796
|
+
mutation RemoveFromCart($cartId: String!, $cartItemUid: ID!) {
|
|
7797
|
+
removeItemFromCart(input: { cart_id: $cartId, cart_item_uid: $cartItemUid }) {
|
|
7798
|
+
cart {
|
|
7799
|
+
id
|
|
7800
|
+
items { id uid quantity product { sku name } prices { price { value currency } row_total { value currency } } }
|
|
7801
|
+
prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
|
|
7802
|
+
}
|
|
7803
|
+
}
|
|
7804
|
+
}
|
|
7805
|
+
`;
|
|
7806
|
+
var UPDATE_CART_ITEMS = `
|
|
7807
|
+
mutation UpdateCartItems($cartId: String!, $cartItems: [CartItemUpdateInput!]!) {
|
|
7808
|
+
updateCartItems(input: { cart_id: $cartId, cart_items: $cartItems }) {
|
|
7809
|
+
cart {
|
|
7810
|
+
id
|
|
7811
|
+
items { id uid quantity product { sku name } prices { price { value currency } row_total { value currency } } }
|
|
7812
|
+
prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
|
|
7813
|
+
}
|
|
7814
|
+
}
|
|
7815
|
+
}
|
|
7816
|
+
`;
|
|
7817
|
+
var APPLY_COUPON = `
|
|
7818
|
+
mutation ApplyCoupon($cartId: String!, $couponCode: String!) {
|
|
7819
|
+
applyCouponToCart(input: { cart_id: $cartId, coupon_code: $couponCode }) {
|
|
7820
|
+
cart {
|
|
7821
|
+
id
|
|
7822
|
+
prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
|
|
7823
|
+
}
|
|
7824
|
+
}
|
|
7825
|
+
}
|
|
7826
|
+
`;
|
|
7827
|
+
var REMOVE_COUPON = `
|
|
7828
|
+
mutation RemoveCoupon($cartId: String!) {
|
|
7829
|
+
removeCouponFromCart(input: { cart_id: $cartId }) {
|
|
7830
|
+
cart {
|
|
7831
|
+
id
|
|
7832
|
+
prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
|
|
7833
|
+
}
|
|
7834
|
+
}
|
|
7835
|
+
}
|
|
7836
|
+
`;
|
|
7837
|
+
var GET_CUSTOMER_ORDERS = `
|
|
7838
|
+
query GetCustomerOrders($pageSize: Int, $currentPage: Int) {
|
|
7839
|
+
customer {
|
|
7840
|
+
orders(pageSize: $pageSize, currentPage: $currentPage) {
|
|
7841
|
+
items {
|
|
7842
|
+
id order_number order_date status
|
|
7843
|
+
total {
|
|
7844
|
+
grand_total { value currency }
|
|
7845
|
+
subtotal { value currency }
|
|
7846
|
+
total_tax { value currency }
|
|
7847
|
+
total_shipping { value currency }
|
|
7848
|
+
}
|
|
7849
|
+
items {
|
|
7850
|
+
id product_name product_sku
|
|
7851
|
+
quantity_ordered
|
|
7852
|
+
product_sale_price { value currency }
|
|
7853
|
+
}
|
|
7854
|
+
shipping_address {
|
|
7855
|
+
firstname lastname street city
|
|
7856
|
+
region postcode country_code telephone
|
|
7857
|
+
}
|
|
7858
|
+
billing_address {
|
|
7859
|
+
firstname lastname street city
|
|
7860
|
+
region postcode country_code telephone
|
|
7861
|
+
}
|
|
7862
|
+
}
|
|
7863
|
+
total_count
|
|
7864
|
+
}
|
|
7865
|
+
}
|
|
7866
|
+
}
|
|
7867
|
+
`;
|
|
7868
|
+
|
|
7869
|
+
// src/providers/magento/index.ts
|
|
7870
|
+
var MagentoProvider = class {
|
|
7871
|
+
name = "magento";
|
|
7872
|
+
version = "1.0.0";
|
|
7873
|
+
capabilities = MAGENTO_CAPABILITIES;
|
|
7874
|
+
logger;
|
|
7875
|
+
baseUrl;
|
|
7876
|
+
storeCode;
|
|
7877
|
+
currencyCode;
|
|
7878
|
+
graphqlEndpoint;
|
|
7879
|
+
customerToken;
|
|
7880
|
+
accessToken;
|
|
7881
|
+
constructor(config) {
|
|
7882
|
+
this.logger = createLogger("MagentoProvider", config.debug);
|
|
7883
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
7884
|
+
this.storeCode = config.storeCode ?? "default";
|
|
7885
|
+
this.currencyCode = config.currencyCode ?? "USD";
|
|
7886
|
+
this.graphqlEndpoint = `${this.baseUrl}/graphql`;
|
|
7887
|
+
this.customerToken = config.customerToken;
|
|
7888
|
+
this.accessToken = config.accessToken;
|
|
7889
|
+
this.logger.info("Initializing Magento Provider", {
|
|
7890
|
+
baseUrl: this.baseUrl,
|
|
7891
|
+
storeCode: this.storeCode
|
|
7892
|
+
});
|
|
7893
|
+
}
|
|
7894
|
+
async graphql(query, variables) {
|
|
7895
|
+
const headers = {
|
|
7896
|
+
"Content-Type": "application/json",
|
|
7897
|
+
"Store": this.storeCode
|
|
7898
|
+
};
|
|
7899
|
+
if (this.customerToken) {
|
|
7900
|
+
headers["Authorization"] = `Bearer ${this.customerToken}`;
|
|
7901
|
+
}
|
|
7902
|
+
const response = await fetch(this.graphqlEndpoint, {
|
|
7903
|
+
method: "POST",
|
|
7904
|
+
headers,
|
|
7905
|
+
body: JSON.stringify({ query, variables })
|
|
7906
|
+
});
|
|
7907
|
+
if (!response.ok) {
|
|
7908
|
+
throw new ProviderAPIError(
|
|
7909
|
+
`Magento GraphQL error: ${response.status}`,
|
|
7910
|
+
"MAGENTO_API_ERROR",
|
|
7911
|
+
response.status
|
|
7912
|
+
);
|
|
7913
|
+
}
|
|
7914
|
+
const json = await response.json();
|
|
7915
|
+
if (json.errors?.length) {
|
|
7916
|
+
const message = json.errors.map((e) => e.message).join("; ");
|
|
7917
|
+
throw new ProviderAPIError(`Magento GraphQL error: ${message}`, "MAGENTO_GRAPHQL_ERROR");
|
|
7918
|
+
}
|
|
7919
|
+
return json.data;
|
|
7920
|
+
}
|
|
7921
|
+
// ============================================
|
|
7922
|
+
// Product Operations (GraphQL)
|
|
7923
|
+
// ============================================
|
|
7924
|
+
async getProduct(handle) {
|
|
7925
|
+
try {
|
|
7926
|
+
const data = await this.graphql(GET_PRODUCT_BY_URL_KEY, { urlKey: handle });
|
|
7927
|
+
return data.products.items[0] ? transformProduct6(data.products.items[0], this.currencyCode) : void 0;
|
|
7928
|
+
} catch {
|
|
7929
|
+
return void 0;
|
|
7930
|
+
}
|
|
7931
|
+
}
|
|
7932
|
+
async getProducts(options) {
|
|
7933
|
+
const variables = {
|
|
7934
|
+
pageSize: options?.first ?? 20,
|
|
7935
|
+
currentPage: 1
|
|
7936
|
+
};
|
|
7937
|
+
if (options?.query) variables.search = options.query;
|
|
7938
|
+
if (options?.sortKey) {
|
|
7939
|
+
const sortMap = {
|
|
7940
|
+
"price-asc": { price: "ASC" },
|
|
7941
|
+
"price-desc": { price: "DESC" },
|
|
7942
|
+
"title-asc": { name: "ASC" },
|
|
7943
|
+
"title-desc": { name: "DESC" },
|
|
7944
|
+
"created-desc": { created_at: "DESC" },
|
|
7945
|
+
"relevance": { relevance: "DESC" }
|
|
7946
|
+
};
|
|
7947
|
+
variables.sort = sortMap[options.sortKey] ?? { relevance: "DESC" };
|
|
7948
|
+
}
|
|
7949
|
+
const data = await this.graphql(GET_PRODUCTS2, variables);
|
|
7950
|
+
return (data.products?.items ?? []).map((p) => transformProduct6(p, this.currencyCode));
|
|
7951
|
+
}
|
|
7952
|
+
async getProductRecommendations(_productId) {
|
|
7953
|
+
return [];
|
|
7954
|
+
}
|
|
7955
|
+
// ============================================
|
|
7956
|
+
// Collection/Category Operations (GraphQL)
|
|
7957
|
+
// ============================================
|
|
7958
|
+
async getCollection(handle) {
|
|
7959
|
+
try {
|
|
7960
|
+
const data = await this.graphql(GET_CATEGORY_BY_URL_KEY, { urlKey: handle });
|
|
7961
|
+
return data.categories.items[0] ? transformCollection6(data.categories.items[0]) : void 0;
|
|
7962
|
+
} catch {
|
|
7963
|
+
return void 0;
|
|
7964
|
+
}
|
|
7965
|
+
}
|
|
7966
|
+
async getCollections(options) {
|
|
7967
|
+
const data = await this.graphql(GET_CATEGORIES, {
|
|
7968
|
+
pageSize: options?.limit ?? 50
|
|
7969
|
+
});
|
|
7970
|
+
return (data.categories?.items ?? []).map(transformCollection6);
|
|
7971
|
+
}
|
|
7972
|
+
async getCollectionProducts(handle, options) {
|
|
7973
|
+
const data = await this.graphql(GET_CATEGORY_PRODUCTS, {
|
|
7974
|
+
urlKey: handle,
|
|
7975
|
+
pageSize: options?.first ?? 20
|
|
7976
|
+
});
|
|
7977
|
+
return (data.products?.items ?? []).map((p) => transformProduct6(p, this.currencyCode));
|
|
7978
|
+
}
|
|
7979
|
+
async getCategory(handle) {
|
|
7980
|
+
return this.getCollection(handle);
|
|
7981
|
+
}
|
|
7982
|
+
async getCategories(options) {
|
|
7983
|
+
return this.getCollections(options);
|
|
7984
|
+
}
|
|
7985
|
+
async getCategoryProducts(handle, options) {
|
|
7986
|
+
return this.getCollectionProducts(handle, options);
|
|
5490
7987
|
}
|
|
5491
7988
|
// ============================================
|
|
5492
|
-
//
|
|
7989
|
+
// Cart Operations (GraphQL)
|
|
5493
7990
|
// ============================================
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
* Get per-channel storefront configuration from Thor Commerce plugin.
|
|
5502
|
-
* Calls `GET /store/thor/config`.
|
|
5503
|
-
*
|
|
5504
|
-
* @returns StorefrontConfig or null if plugin is not installed / config not set
|
|
5505
|
-
*/
|
|
5506
|
-
async getStorefrontConfig() {
|
|
5507
|
-
if (this._storefrontConfigCache && Date.now() < this._storefrontConfigCache.expiresAt) {
|
|
5508
|
-
return this._storefrontConfigCache.config;
|
|
5509
|
-
}
|
|
7991
|
+
async createCart() {
|
|
7992
|
+
const data = await this.graphql(CREATE_EMPTY_CART);
|
|
7993
|
+
const cartId = data.createEmptyCart;
|
|
7994
|
+
const cartData = await this.graphql(GET_CART2, { cartId });
|
|
7995
|
+
return transformCart6(cartData.cart ?? { id: cartId, items: [], prices: {} }, this.currencyCode);
|
|
7996
|
+
}
|
|
7997
|
+
async getCart(cartId) {
|
|
5510
7998
|
try {
|
|
5511
|
-
const
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
method: "GET",
|
|
5516
|
-
headers: {
|
|
5517
|
-
"x-publishable-api-key": this.publishableKey,
|
|
5518
|
-
"Content-Type": "application/json"
|
|
5519
|
-
}
|
|
5520
|
-
},
|
|
5521
|
-
this.retryConfig
|
|
5522
|
-
);
|
|
5523
|
-
if (response.status === 404) {
|
|
5524
|
-
this._storefrontConfigCache = { config: null, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5525
|
-
return null;
|
|
5526
|
-
}
|
|
5527
|
-
if (!response.ok) {
|
|
5528
|
-
this.logger.warn("[StorefrontConfig] Non-OK response", { status: response.status });
|
|
5529
|
-
return null;
|
|
5530
|
-
}
|
|
5531
|
-
const data = await response.json();
|
|
5532
|
-
const config = transformStorefrontConfig(data.storefront_config ?? data);
|
|
5533
|
-
this._storefrontConfigCache = { config, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5534
|
-
this.logger.debug("[StorefrontConfig] Loaded from backend", { id: config.id });
|
|
5535
|
-
return config;
|
|
5536
|
-
} catch (error) {
|
|
5537
|
-
this.logger.warn("[StorefrontConfig] Failed to fetch", {
|
|
5538
|
-
error: error instanceof Error ? error.message : String(error)
|
|
5539
|
-
});
|
|
5540
|
-
return null;
|
|
7999
|
+
const data = await this.graphql(GET_CART2, { cartId });
|
|
8000
|
+
return data.cart ? transformCart6(data.cart, this.currencyCode) : void 0;
|
|
8001
|
+
} catch {
|
|
8002
|
+
return void 0;
|
|
5541
8003
|
}
|
|
5542
8004
|
}
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
url,
|
|
5557
|
-
{
|
|
5558
|
-
method: "GET",
|
|
5559
|
-
headers: {
|
|
5560
|
-
"x-publishable-api-key": this.publishableKey,
|
|
5561
|
-
"Content-Type": "application/json"
|
|
5562
|
-
}
|
|
5563
|
-
},
|
|
5564
|
-
this.retryConfig
|
|
8005
|
+
async addToCart(cartId, lines) {
|
|
8006
|
+
const cartItems = lines.map((l) => ({
|
|
8007
|
+
sku: l.merchandiseId,
|
|
8008
|
+
quantity: l.quantity
|
|
8009
|
+
}));
|
|
8010
|
+
const data = await this.graphql(ADD_TO_CART, {
|
|
8011
|
+
cartId,
|
|
8012
|
+
cartItems
|
|
8013
|
+
});
|
|
8014
|
+
if (data.addProductsToCart.user_errors?.length) {
|
|
8015
|
+
throw new ProviderAPIError(
|
|
8016
|
+
`Add to cart failed: ${data.addProductsToCart.user_errors.map((e) => e.message).join("; ")}`,
|
|
8017
|
+
"MAGENTO_CART_ERROR"
|
|
5565
8018
|
);
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
const result = {
|
|
5576
|
-
version: data.version ?? "",
|
|
5577
|
-
config: data.config
|
|
5578
|
-
};
|
|
5579
|
-
this._designerConfigCache = { result, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5580
|
-
this.logger.debug("[DesignerConfig] Loaded from backend", { version: result.version });
|
|
5581
|
-
return result;
|
|
5582
|
-
} catch (error) {
|
|
5583
|
-
this.logger.warn("[DesignerConfig] Failed to fetch", {
|
|
5584
|
-
error: error instanceof Error ? error.message : String(error)
|
|
8019
|
+
}
|
|
8020
|
+
return transformCart6(data.addProductsToCart.cart, this.currencyCode);
|
|
8021
|
+
}
|
|
8022
|
+
async removeFromCart(cartId, lineIds) {
|
|
8023
|
+
let cart;
|
|
8024
|
+
for (const itemUid of lineIds) {
|
|
8025
|
+
const data = await this.graphql(REMOVE_FROM_CART, {
|
|
8026
|
+
cartId,
|
|
8027
|
+
cartItemUid: itemUid
|
|
5585
8028
|
});
|
|
5586
|
-
|
|
8029
|
+
cart = data.removeItemFromCart.cart;
|
|
5587
8030
|
}
|
|
8031
|
+
return transformCart6(cart ?? { id: cartId, items: [], prices: {} }, this.currencyCode);
|
|
8032
|
+
}
|
|
8033
|
+
async updateCart(cartId, lines) {
|
|
8034
|
+
const cartItems = lines.map((l) => ({
|
|
8035
|
+
cart_item_uid: l.id,
|
|
8036
|
+
quantity: l.quantity
|
|
8037
|
+
}));
|
|
8038
|
+
const data = await this.graphql(UPDATE_CART_ITEMS, {
|
|
8039
|
+
cartId,
|
|
8040
|
+
cartItems
|
|
8041
|
+
});
|
|
8042
|
+
return transformCart6(data.updateCartItems.cart, this.currencyCode);
|
|
5588
8043
|
}
|
|
5589
8044
|
// ============================================
|
|
5590
|
-
//
|
|
8045
|
+
// Checkout Operations
|
|
5591
8046
|
// ============================================
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
return this.makeAdminRequest(async () => {
|
|
5598
|
-
const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}`;
|
|
5599
|
-
let data;
|
|
5600
|
-
try {
|
|
5601
|
-
data = await this.sdk.client.fetch(path, { method: "GET" });
|
|
5602
|
-
} catch (error) {
|
|
5603
|
-
if (error?.status === 404) return null;
|
|
5604
|
-
throw new ProviderAPIError(
|
|
5605
|
-
`Failed to fetch admin designer config: ${error?.message ?? error?.status}`,
|
|
5606
|
-
"medusa",
|
|
5607
|
-
error?.status
|
|
5608
|
-
);
|
|
5609
|
-
}
|
|
5610
|
-
return {
|
|
5611
|
-
version: data.version ?? "",
|
|
5612
|
-
config: data.config,
|
|
5613
|
-
history: data.history
|
|
5614
|
-
};
|
|
5615
|
-
});
|
|
8047
|
+
async completeCheckout(_cartId) {
|
|
8048
|
+
throw new ProviderAPIError(
|
|
8049
|
+
"Magento checkout requires multi-step process (set addresses, shipping, payment, place order). Use the Magento checkout flow.",
|
|
8050
|
+
"MAGENTO_CHECKOUT_MULTI_STEP"
|
|
8051
|
+
);
|
|
5616
8052
|
}
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
error?.status
|
|
5636
|
-
);
|
|
5637
|
-
}
|
|
5638
|
-
return {
|
|
5639
|
-
version: data.version ?? "",
|
|
5640
|
-
config: data.config
|
|
5641
|
-
};
|
|
8053
|
+
async getShippingMethods(_cartId) {
|
|
8054
|
+
return [];
|
|
8055
|
+
}
|
|
8056
|
+
async getRegionCountries() {
|
|
8057
|
+
return [];
|
|
8058
|
+
}
|
|
8059
|
+
async addShippingMethod(_cartId, _methodId) {
|
|
8060
|
+
throw new ProviderAPIError(
|
|
8061
|
+
"Magento shipping selection requires set-shipping-methods mutation.",
|
|
8062
|
+
"MAGENTO_CHECKOUT_MULTI_STEP"
|
|
8063
|
+
);
|
|
8064
|
+
}
|
|
8065
|
+
async initiatePaymentSession(_cartId, _providerId) {
|
|
8066
|
+
}
|
|
8067
|
+
async applyDiscountCode(cartId, code) {
|
|
8068
|
+
const data = await this.graphql(APPLY_COUPON, {
|
|
8069
|
+
cartId,
|
|
8070
|
+
couponCode: code
|
|
5642
8071
|
});
|
|
8072
|
+
return transformCart6(data.applyCouponToCart.cart, this.currencyCode);
|
|
5643
8073
|
}
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
*/
|
|
5648
|
-
async restoreAdminDesignerConfig(salesChannelId, version) {
|
|
5649
|
-
return this.makeAdminRequest(async () => {
|
|
5650
|
-
const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}/restore/${encodeURIComponent(version)}`;
|
|
5651
|
-
let data;
|
|
5652
|
-
try {
|
|
5653
|
-
data = await this.sdk.client.fetch(path, { method: "POST" });
|
|
5654
|
-
} catch (error) {
|
|
5655
|
-
throw new ProviderAPIError(
|
|
5656
|
-
`Failed to restore admin designer config: ${error?.message ?? error?.status}`,
|
|
5657
|
-
"medusa",
|
|
5658
|
-
error?.status
|
|
5659
|
-
);
|
|
5660
|
-
}
|
|
5661
|
-
return {
|
|
5662
|
-
version: data.version ?? "",
|
|
5663
|
-
config: data.config
|
|
5664
|
-
};
|
|
8074
|
+
async removeDiscountCode(cartId, _code) {
|
|
8075
|
+
const data = await this.graphql(REMOVE_COUPON, {
|
|
8076
|
+
cartId
|
|
5665
8077
|
});
|
|
8078
|
+
return transformCart6(data.removeCouponFromCart.cart, this.currencyCode);
|
|
8079
|
+
}
|
|
8080
|
+
// ============================================
|
|
8081
|
+
// Customer & Orders (GraphQL — requires customerToken)
|
|
8082
|
+
// ============================================
|
|
8083
|
+
async getOrders(_options) {
|
|
8084
|
+
if (!this.customerToken) return [];
|
|
8085
|
+
try {
|
|
8086
|
+
const data = await this.graphql(GET_CUSTOMER_ORDERS, {
|
|
8087
|
+
pageSize: 20,
|
|
8088
|
+
currentPage: 1
|
|
8089
|
+
});
|
|
8090
|
+
return (data.customer?.orders?.items ?? []).map((o) => transformOrder6(o, this.currencyCode));
|
|
8091
|
+
} catch {
|
|
8092
|
+
return [];
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
async getPaymentMethods(_options) {
|
|
8096
|
+
return [];
|
|
8097
|
+
}
|
|
8098
|
+
// ============================================
|
|
8099
|
+
// Inventory
|
|
8100
|
+
// ============================================
|
|
8101
|
+
async getStockLocations() {
|
|
8102
|
+
return [];
|
|
8103
|
+
}
|
|
8104
|
+
async checkInventoryLevels() {
|
|
8105
|
+
return [];
|
|
8106
|
+
}
|
|
8107
|
+
async validateCartStock() {
|
|
8108
|
+
return { allAvailable: true, unavailableItems: [] };
|
|
8109
|
+
}
|
|
8110
|
+
async getFulfillmentOptions() {
|
|
8111
|
+
return [];
|
|
8112
|
+
}
|
|
8113
|
+
// ============================================
|
|
8114
|
+
// Admin Operations
|
|
8115
|
+
// ============================================
|
|
8116
|
+
async cancelOrder(_orderId) {
|
|
8117
|
+
throw new ProviderAPIError("Order cancellation requires Magento Admin API.", "MAGENTO_ADMIN_REQUIRED");
|
|
8118
|
+
}
|
|
8119
|
+
async createFulfillment(_orderId) {
|
|
8120
|
+
throw new ProviderAPIError("Fulfillment requires Magento Admin API.", "MAGENTO_ADMIN_REQUIRED");
|
|
8121
|
+
}
|
|
8122
|
+
async createRefund(_orderId) {
|
|
8123
|
+
throw new ProviderAPIError("Refunds require Magento Admin API.", "MAGENTO_ADMIN_REQUIRED");
|
|
8124
|
+
}
|
|
8125
|
+
async getAdminOrders() {
|
|
8126
|
+
return { orders: [], count: 0 };
|
|
8127
|
+
}
|
|
8128
|
+
// ============================================
|
|
8129
|
+
// Storefront / Multi-Tenant
|
|
8130
|
+
// ============================================
|
|
8131
|
+
async getStorefrontContext() {
|
|
8132
|
+
return {
|
|
8133
|
+
id: this.storeCode,
|
|
8134
|
+
name: `Magento ${this.storeCode}`,
|
|
8135
|
+
platformType: "magento",
|
|
8136
|
+
requiresProductScoping: true,
|
|
8137
|
+
currencyCode: this.currencyCode
|
|
8138
|
+
};
|
|
8139
|
+
}
|
|
8140
|
+
isStorefrontRequired() {
|
|
8141
|
+
return true;
|
|
5666
8142
|
}
|
|
5667
8143
|
};
|
|
5668
8144
|
|
|
@@ -7697,16 +10173,6 @@ var MockProvider = class {
|
|
|
7697
10173
|
};
|
|
7698
10174
|
|
|
7699
10175
|
// src/factory/index.ts
|
|
7700
|
-
var registry = /* @__PURE__ */ new Map();
|
|
7701
|
-
function registerProvider(type, factory) {
|
|
7702
|
-
registry.set(type, factory);
|
|
7703
|
-
}
|
|
7704
|
-
function getRegisteredProviders() {
|
|
7705
|
-
return Array.from(registry.keys());
|
|
7706
|
-
}
|
|
7707
|
-
registerProvider("mock", (config) => new MockProvider(config));
|
|
7708
|
-
registerProvider("medusa", (config) => new MedusaProvider(config));
|
|
7709
|
-
registerProvider("stelorder", (config) => new StelOrderProvider(config));
|
|
7710
10176
|
function createProvider(options) {
|
|
7711
10177
|
if (!options || !options.provider) {
|
|
7712
10178
|
throw new ConfigurationError("Provider type is required");
|
|
@@ -7714,14 +10180,27 @@ function createProvider(options) {
|
|
|
7714
10180
|
if (!options.config) {
|
|
7715
10181
|
throw new ConfigurationError("Provider configuration is required");
|
|
7716
10182
|
}
|
|
7717
|
-
const
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
10183
|
+
const config = options.config;
|
|
10184
|
+
switch (options.provider) {
|
|
10185
|
+
case "mock":
|
|
10186
|
+
return new MockProvider(config);
|
|
10187
|
+
case "medusa":
|
|
10188
|
+
return new MedusaProvider(config);
|
|
10189
|
+
case "stelorder":
|
|
10190
|
+
return new StelOrderProvider(config);
|
|
10191
|
+
case "shopify":
|
|
10192
|
+
return new ShopifyProvider(config);
|
|
10193
|
+
case "bigcommerce":
|
|
10194
|
+
return new BigCommerceProvider(config);
|
|
10195
|
+
case "woocommerce":
|
|
10196
|
+
return new WooCommerceProvider(config);
|
|
10197
|
+
case "spree":
|
|
10198
|
+
return new SpreeProvider(config);
|
|
10199
|
+
case "magento":
|
|
10200
|
+
return new MagentoProvider(config);
|
|
10201
|
+
default:
|
|
10202
|
+
throw new ProviderNotFoundError(String(options.provider));
|
|
7723
10203
|
}
|
|
7724
|
-
return factory(options.config);
|
|
7725
10204
|
}
|
|
7726
10205
|
function isProviderType(provider, type) {
|
|
7727
10206
|
return provider.name === type;
|
|
@@ -7730,6 +10209,8 @@ function isProviderType(provider, type) {
|
|
|
7730
10209
|
0 && (module.exports = {
|
|
7731
10210
|
AdminAuthService,
|
|
7732
10211
|
AdminSessionService,
|
|
10212
|
+
BIGCOMMERCE_CAPABILITIES,
|
|
10213
|
+
BigCommerceProvider,
|
|
7733
10214
|
CHANNEL_ASSIGNED_AT_KEY,
|
|
7734
10215
|
CHANNEL_ASSIGNED_BY_KEY,
|
|
7735
10216
|
CHANNEL_SCOPE_KEY,
|
|
@@ -7738,8 +10219,10 @@ function isProviderType(provider, type) {
|
|
|
7738
10219
|
CircuitBreakerOpenError,
|
|
7739
10220
|
CommerceError,
|
|
7740
10221
|
ConfigurationError,
|
|
10222
|
+
MAGENTO_CAPABILITIES,
|
|
7741
10223
|
MEDUSA_CAPABILITIES,
|
|
7742
10224
|
MOCK_CAPABILITIES,
|
|
10225
|
+
MagentoProvider,
|
|
7743
10226
|
MedusaMetadataAdapter,
|
|
7744
10227
|
MedusaProvider,
|
|
7745
10228
|
MetricsService,
|
|
@@ -7747,14 +10230,21 @@ function isProviderType(provider, type) {
|
|
|
7747
10230
|
ProviderAPIError,
|
|
7748
10231
|
ProviderNotFoundError,
|
|
7749
10232
|
ResourceNotFoundError,
|
|
10233
|
+
SHOPIFY_CAPABILITIES,
|
|
10234
|
+
SPREE_CAPABILITIES,
|
|
7750
10235
|
STELORDER_CAPABILITIES,
|
|
10236
|
+
ShopifyProvider,
|
|
10237
|
+
SpreeProvider,
|
|
7751
10238
|
StelOrderProvider,
|
|
7752
10239
|
StorefrontConfigError,
|
|
10240
|
+
WOOCOMMERCE_CAPABILITIES,
|
|
10241
|
+
WooCommerceProvider,
|
|
7753
10242
|
assertEntityBelongsToChannel,
|
|
7754
10243
|
backendCircuitBreaker,
|
|
7755
10244
|
buildChannelScopeMetadata,
|
|
7756
10245
|
calculateDistance,
|
|
7757
10246
|
createBackendMetadataAdapter,
|
|
10247
|
+
createFetchWithRetry,
|
|
7758
10248
|
createLogger,
|
|
7759
10249
|
createProvider,
|
|
7760
10250
|
extractErrorMessage,
|
|
@@ -7765,7 +10255,6 @@ function isProviderType(provider, type) {
|
|
|
7765
10255
|
getBackendProviderId,
|
|
7766
10256
|
getCapabilityDescription,
|
|
7767
10257
|
getChannelScope,
|
|
7768
|
-
getRegisteredProviders,
|
|
7769
10258
|
isBackendCapabilities,
|
|
7770
10259
|
isGlobalEntity,
|
|
7771
10260
|
isProviderType,
|
|
@@ -7775,7 +10264,6 @@ function isProviderType(provider, type) {
|
|
|
7775
10264
|
numberToMoney,
|
|
7776
10265
|
partitionByChannel,
|
|
7777
10266
|
planFulfillment,
|
|
7778
|
-
registerProvider,
|
|
7779
10267
|
transformFulfillmentOption,
|
|
7780
10268
|
transformInventoryLevel,
|
|
7781
10269
|
transformStockLocation,
|