@thorprovider/adapters 5.0.0 → 5.0.2
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.mjs
CHANGED
|
@@ -106,6 +106,209 @@ var STELORDER_CAPABILITIES = {
|
|
|
106
106
|
// Loyalty
|
|
107
107
|
loyaltyProgram: false
|
|
108
108
|
};
|
|
109
|
+
var SHOPIFY_CAPABILITIES = {
|
|
110
|
+
// Profile Management
|
|
111
|
+
updateProfile: true,
|
|
112
|
+
updateEmail: true,
|
|
113
|
+
changePasswordDirect: true,
|
|
114
|
+
changePasswordViaEmail: true,
|
|
115
|
+
deleteAccount: false,
|
|
116
|
+
// Requires admin API
|
|
117
|
+
// Avatar
|
|
118
|
+
uploadAvatar: false,
|
|
119
|
+
avatarViaMetadata: true,
|
|
120
|
+
// Custom Attributes
|
|
121
|
+
customAttributes: true,
|
|
122
|
+
// Orders
|
|
123
|
+
fetchOrders: true,
|
|
124
|
+
cancelOrder: true,
|
|
125
|
+
// Addresses
|
|
126
|
+
manageAddresses: true,
|
|
127
|
+
defaultAddress: true,
|
|
128
|
+
// Cart/Checkout
|
|
129
|
+
applyDiscounts: true,
|
|
130
|
+
shippingCalculation: true,
|
|
131
|
+
guestCheckout: true,
|
|
132
|
+
savedPaymentMethods: true,
|
|
133
|
+
listPaymentProviders: true,
|
|
134
|
+
regionSpecificPayments: false,
|
|
135
|
+
// Global payment methods
|
|
136
|
+
// Wishlist
|
|
137
|
+
wishlist: false,
|
|
138
|
+
// Requires app
|
|
139
|
+
// Search
|
|
140
|
+
advancedSearch: true,
|
|
141
|
+
facetedFilters: true,
|
|
142
|
+
// Reviews
|
|
143
|
+
productReviews: false,
|
|
144
|
+
// Requires app
|
|
145
|
+
// Loyalty
|
|
146
|
+
loyaltyProgram: false
|
|
147
|
+
// Requires app
|
|
148
|
+
};
|
|
149
|
+
var BIGCOMMERCE_CAPABILITIES = {
|
|
150
|
+
// Profile Management
|
|
151
|
+
updateProfile: true,
|
|
152
|
+
updateEmail: true,
|
|
153
|
+
changePasswordDirect: true,
|
|
154
|
+
changePasswordViaEmail: true,
|
|
155
|
+
deleteAccount: false,
|
|
156
|
+
// Avatar
|
|
157
|
+
uploadAvatar: false,
|
|
158
|
+
avatarViaMetadata: false,
|
|
159
|
+
// Custom Attributes
|
|
160
|
+
customAttributes: true,
|
|
161
|
+
// Orders
|
|
162
|
+
fetchOrders: true,
|
|
163
|
+
cancelOrder: false,
|
|
164
|
+
// Admin API only
|
|
165
|
+
// Addresses
|
|
166
|
+
manageAddresses: true,
|
|
167
|
+
defaultAddress: false,
|
|
168
|
+
// Cart/Checkout
|
|
169
|
+
applyDiscounts: true,
|
|
170
|
+
shippingCalculation: true,
|
|
171
|
+
guestCheckout: true,
|
|
172
|
+
savedPaymentMethods: true,
|
|
173
|
+
listPaymentProviders: true,
|
|
174
|
+
regionSpecificPayments: false,
|
|
175
|
+
// Wishlist
|
|
176
|
+
wishlist: true,
|
|
177
|
+
// Native support
|
|
178
|
+
// Search
|
|
179
|
+
advancedSearch: true,
|
|
180
|
+
facetedFilters: true,
|
|
181
|
+
// Reviews
|
|
182
|
+
productReviews: true,
|
|
183
|
+
// Native support
|
|
184
|
+
// Loyalty
|
|
185
|
+
loyaltyProgram: false
|
|
186
|
+
};
|
|
187
|
+
var WOOCOMMERCE_CAPABILITIES = {
|
|
188
|
+
// Profile Management
|
|
189
|
+
updateProfile: true,
|
|
190
|
+
updateEmail: true,
|
|
191
|
+
changePasswordDirect: false,
|
|
192
|
+
// WordPress handles auth
|
|
193
|
+
changePasswordViaEmail: true,
|
|
194
|
+
deleteAccount: false,
|
|
195
|
+
// Avatar
|
|
196
|
+
uploadAvatar: false,
|
|
197
|
+
avatarViaMetadata: false,
|
|
198
|
+
// Gravatar-based
|
|
199
|
+
// Custom Attributes
|
|
200
|
+
customAttributes: true,
|
|
201
|
+
// Via meta_data
|
|
202
|
+
// Orders
|
|
203
|
+
fetchOrders: true,
|
|
204
|
+
cancelOrder: true,
|
|
205
|
+
// Addresses
|
|
206
|
+
manageAddresses: true,
|
|
207
|
+
defaultAddress: false,
|
|
208
|
+
// Billing/shipping only
|
|
209
|
+
// Cart/Checkout
|
|
210
|
+
applyDiscounts: true,
|
|
211
|
+
// Coupon codes
|
|
212
|
+
shippingCalculation: true,
|
|
213
|
+
guestCheckout: true,
|
|
214
|
+
savedPaymentMethods: false,
|
|
215
|
+
listPaymentProviders: true,
|
|
216
|
+
regionSpecificPayments: false,
|
|
217
|
+
// Wishlist
|
|
218
|
+
wishlist: false,
|
|
219
|
+
// Requires plugin
|
|
220
|
+
// Search
|
|
221
|
+
advancedSearch: false,
|
|
222
|
+
// Basic WP search
|
|
223
|
+
facetedFilters: false,
|
|
224
|
+
// Requires plugin
|
|
225
|
+
// Reviews
|
|
226
|
+
productReviews: true,
|
|
227
|
+
// Native WP reviews
|
|
228
|
+
// Loyalty
|
|
229
|
+
loyaltyProgram: false
|
|
230
|
+
};
|
|
231
|
+
var SPREE_CAPABILITIES = {
|
|
232
|
+
// Profile Management
|
|
233
|
+
updateProfile: true,
|
|
234
|
+
updateEmail: true,
|
|
235
|
+
changePasswordDirect: true,
|
|
236
|
+
changePasswordViaEmail: true,
|
|
237
|
+
deleteAccount: false,
|
|
238
|
+
// Avatar
|
|
239
|
+
uploadAvatar: false,
|
|
240
|
+
avatarViaMetadata: false,
|
|
241
|
+
// Custom Attributes
|
|
242
|
+
customAttributes: false,
|
|
243
|
+
// Orders
|
|
244
|
+
fetchOrders: true,
|
|
245
|
+
cancelOrder: true,
|
|
246
|
+
// Addresses
|
|
247
|
+
manageAddresses: true,
|
|
248
|
+
defaultAddress: true,
|
|
249
|
+
// Cart/Checkout
|
|
250
|
+
applyDiscounts: true,
|
|
251
|
+
shippingCalculation: true,
|
|
252
|
+
guestCheckout: true,
|
|
253
|
+
savedPaymentMethods: false,
|
|
254
|
+
listPaymentProviders: true,
|
|
255
|
+
regionSpecificPayments: false,
|
|
256
|
+
// Wishlist
|
|
257
|
+
wishlist: true,
|
|
258
|
+
// Native support
|
|
259
|
+
// Search
|
|
260
|
+
advancedSearch: false,
|
|
261
|
+
facetedFilters: true,
|
|
262
|
+
// Taxon-based filtering
|
|
263
|
+
// Reviews
|
|
264
|
+
productReviews: false,
|
|
265
|
+
// Requires extension
|
|
266
|
+
// Loyalty
|
|
267
|
+
loyaltyProgram: false
|
|
268
|
+
};
|
|
269
|
+
var MAGENTO_CAPABILITIES = {
|
|
270
|
+
// Profile Management
|
|
271
|
+
updateProfile: true,
|
|
272
|
+
updateEmail: true,
|
|
273
|
+
changePasswordDirect: true,
|
|
274
|
+
changePasswordViaEmail: true,
|
|
275
|
+
deleteAccount: false,
|
|
276
|
+
// Admin only
|
|
277
|
+
// Avatar
|
|
278
|
+
uploadAvatar: false,
|
|
279
|
+
avatarViaMetadata: false,
|
|
280
|
+
// Custom Attributes
|
|
281
|
+
customAttributes: true,
|
|
282
|
+
// EAV model
|
|
283
|
+
// Orders
|
|
284
|
+
fetchOrders: true,
|
|
285
|
+
cancelOrder: true,
|
|
286
|
+
// Addresses
|
|
287
|
+
manageAddresses: true,
|
|
288
|
+
defaultAddress: true,
|
|
289
|
+
// Cart/Checkout
|
|
290
|
+
applyDiscounts: true,
|
|
291
|
+
shippingCalculation: true,
|
|
292
|
+
guestCheckout: true,
|
|
293
|
+
savedPaymentMethods: true,
|
|
294
|
+
listPaymentProviders: true,
|
|
295
|
+
regionSpecificPayments: true,
|
|
296
|
+
// Store-view scoped
|
|
297
|
+
// Wishlist
|
|
298
|
+
wishlist: true,
|
|
299
|
+
// Native support
|
|
300
|
+
// Search
|
|
301
|
+
advancedSearch: true,
|
|
302
|
+
// Elasticsearch/OpenSearch
|
|
303
|
+
facetedFilters: true,
|
|
304
|
+
// Layered navigation
|
|
305
|
+
// Reviews
|
|
306
|
+
productReviews: true,
|
|
307
|
+
// Native support
|
|
308
|
+
// Loyalty
|
|
309
|
+
loyaltyProgram: false
|
|
310
|
+
// Requires extension
|
|
311
|
+
};
|
|
109
312
|
function isBackendCapabilities(obj) {
|
|
110
313
|
if (typeof obj !== "object" || obj === null) return false;
|
|
111
314
|
const capabilities = obj;
|
|
@@ -320,6 +523,9 @@ async function fetchWithRetry(url, init, retryConfig) {
|
|
|
320
523
|
}
|
|
321
524
|
throw lastError;
|
|
322
525
|
}
|
|
526
|
+
function createFetchWithRetry(retryConfig) {
|
|
527
|
+
return (url, init) => fetchWithRetry(url, init, retryConfig);
|
|
528
|
+
}
|
|
323
529
|
|
|
324
530
|
// src/resilience/circuit-breaker.ts
|
|
325
531
|
var CircuitBreaker = class {
|
|
@@ -1325,7 +1531,13 @@ function createBackendMetadataAdapter(config) {
|
|
|
1325
1531
|
});
|
|
1326
1532
|
return new MedusaMetadataAdapter(medusa);
|
|
1327
1533
|
}
|
|
1328
|
-
|
|
1534
|
+
if (provider === "shopify") {
|
|
1535
|
+
throw new Error("Shopify metadata adapter not yet implemented");
|
|
1536
|
+
}
|
|
1537
|
+
if (provider === "woocommerce") {
|
|
1538
|
+
throw new Error("WooCommerce metadata adapter not yet implemented");
|
|
1539
|
+
}
|
|
1540
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
1329
1541
|
}
|
|
1330
1542
|
|
|
1331
1543
|
// src/metadata/channel-scope.ts
|
|
@@ -1520,17 +1732,11 @@ var MedusaAuthProvider = class {
|
|
|
1520
1732
|
logger;
|
|
1521
1733
|
authConfig;
|
|
1522
1734
|
storageManager;
|
|
1523
|
-
|
|
1524
|
-
baseUrl;
|
|
1525
|
-
/** Publishable API key — sent as x-publishable-api-key on store calls */
|
|
1526
|
-
publishableKey;
|
|
1527
|
-
constructor(sdk, authConfig, debug = false, baseUrl = "", publishableKey = "") {
|
|
1735
|
+
constructor(sdk, authConfig, debug = false) {
|
|
1528
1736
|
this.sdk = sdk;
|
|
1529
1737
|
this.authConfig = authConfig;
|
|
1530
1738
|
this.logger = createLogger("MedusaAuthProvider", debug);
|
|
1531
1739
|
this.storageManager = new AuthStorageManager(authConfig);
|
|
1532
|
-
this.baseUrl = baseUrl;
|
|
1533
|
-
this.publishableKey = publishableKey;
|
|
1534
1740
|
this.logger.info("Medusa Auth Provider initialized", {
|
|
1535
1741
|
method: authConfig.method,
|
|
1536
1742
|
storage: authConfig.storage
|
|
@@ -1542,17 +1748,28 @@ var MedusaAuthProvider = class {
|
|
|
1542
1748
|
async login(credentials) {
|
|
1543
1749
|
this.logger.debug("login", { email: credentials.email });
|
|
1544
1750
|
try {
|
|
1545
|
-
const
|
|
1751
|
+
const loginResult = await this.sdk.auth.login("customer", "emailpass", {
|
|
1546
1752
|
email: credentials.email,
|
|
1547
1753
|
password: credentials.password
|
|
1548
1754
|
});
|
|
1549
|
-
if (typeof
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1755
|
+
if (typeof loginResult !== "string") {
|
|
1756
|
+
if ("verification_required" in loginResult && loginResult.verification_required) {
|
|
1757
|
+
this.logger.warn("Authentication requires email verification", loginResult);
|
|
1758
|
+
return {
|
|
1759
|
+
requiresAction: true,
|
|
1760
|
+
token: loginResult.token
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
if ("location" in loginResult) {
|
|
1764
|
+
this.logger.warn("Authentication requires additional actions", loginResult);
|
|
1765
|
+
return {
|
|
1766
|
+
requiresAction: true,
|
|
1767
|
+
redirectUrl: loginResult.location
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
throw new Error("Unexpected login result type");
|
|
1555
1771
|
}
|
|
1772
|
+
const token = loginResult;
|
|
1556
1773
|
if (this.authConfig.method === "jwt") {
|
|
1557
1774
|
await this.storageManager.setToken(token);
|
|
1558
1775
|
}
|
|
@@ -1574,19 +1791,38 @@ var MedusaAuthProvider = class {
|
|
|
1574
1791
|
async register(data) {
|
|
1575
1792
|
this.logger.debug("register", { email: data.email });
|
|
1576
1793
|
try {
|
|
1577
|
-
|
|
1794
|
+
await this.sdk.auth.register("customer", "emailpass", {
|
|
1578
1795
|
email: data.email,
|
|
1579
1796
|
password: data.password
|
|
1580
1797
|
});
|
|
1581
|
-
if (typeof token !== "string") {
|
|
1582
|
-
throw new Error("Registration returned non-string token");
|
|
1583
|
-
}
|
|
1584
1798
|
const { customer } = await this.sdk.store.customer.create({
|
|
1585
1799
|
email: data.email,
|
|
1586
1800
|
first_name: data.first_name,
|
|
1587
1801
|
last_name: data.last_name,
|
|
1588
1802
|
phone: data.phone
|
|
1589
1803
|
});
|
|
1804
|
+
const loginResult = await this.sdk.auth.login("customer", "emailpass", {
|
|
1805
|
+
email: data.email,
|
|
1806
|
+
password: data.password
|
|
1807
|
+
});
|
|
1808
|
+
if (typeof loginResult !== "string") {
|
|
1809
|
+
if ("verification_required" in loginResult && loginResult.verification_required) {
|
|
1810
|
+
this.logger.warn("Registration requires email verification", loginResult);
|
|
1811
|
+
return {
|
|
1812
|
+
requiresAction: true,
|
|
1813
|
+
token: loginResult.token
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
if ("location" in loginResult) {
|
|
1817
|
+
this.logger.warn("Registration requires additional actions", loginResult);
|
|
1818
|
+
return {
|
|
1819
|
+
requiresAction: true,
|
|
1820
|
+
redirectUrl: loginResult.location
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
throw new Error("Unexpected login result after registration");
|
|
1824
|
+
}
|
|
1825
|
+
const token = loginResult;
|
|
1590
1826
|
if (this.authConfig.method === "jwt") {
|
|
1591
1827
|
await this.storageManager.setToken(token);
|
|
1592
1828
|
}
|
|
@@ -1806,81 +2042,6 @@ var MedusaAuthProvider = class {
|
|
|
1806
2042
|
void 0
|
|
1807
2043
|
);
|
|
1808
2044
|
}
|
|
1809
|
-
// ========================================
|
|
1810
|
-
// Multi-Tenant — Channel Link
|
|
1811
|
-
// ========================================
|
|
1812
|
-
/**
|
|
1813
|
-
* Link the currently authenticated customer to the storefront's sales channel.
|
|
1814
|
-
*
|
|
1815
|
-
* Must be called immediately after a successful `login()` so the customer
|
|
1816
|
-
* appears in the admin panel of the correct storefront channel.
|
|
1817
|
-
*
|
|
1818
|
-
* Implements `POST /store/thor/customers/me/channels`.
|
|
1819
|
-
* Idempotent — returns `{ linked: false, reason: 'already_linked' }` when
|
|
1820
|
-
* the link already exists.
|
|
1821
|
-
*
|
|
1822
|
-
* @throws {ProviderAPIError} When not authenticated (401) or channel cannot
|
|
1823
|
-
* be resolved from the publishable key (400).
|
|
1824
|
-
*/
|
|
1825
|
-
async linkCustomerToChannel() {
|
|
1826
|
-
this.logger.debug("linkCustomerToChannel");
|
|
1827
|
-
if (!this.baseUrl || !this.publishableKey) {
|
|
1828
|
-
throw new ProviderAPIError(
|
|
1829
|
-
"linkCustomerToChannel requires baseUrl and publishableKey to be set on the auth provider.",
|
|
1830
|
-
"CONFIGURATION_ERROR",
|
|
1831
|
-
void 0
|
|
1832
|
-
);
|
|
1833
|
-
}
|
|
1834
|
-
const token = await this.storageManager.getToken();
|
|
1835
|
-
const headers = {
|
|
1836
|
-
"x-publishable-api-key": this.publishableKey,
|
|
1837
|
-
"Content-Type": "application/json"
|
|
1838
|
-
};
|
|
1839
|
-
if (token) {
|
|
1840
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
1841
|
-
}
|
|
1842
|
-
try {
|
|
1843
|
-
const url = `${this.baseUrl}/store/thor/customers/me/channels`;
|
|
1844
|
-
const response = await fetch(url, {
|
|
1845
|
-
method: "POST",
|
|
1846
|
-
headers
|
|
1847
|
-
});
|
|
1848
|
-
if (response.status === 401) {
|
|
1849
|
-
throw new ProviderAPIError(
|
|
1850
|
-
"linkCustomerToChannel: not authenticated. Call login() first.",
|
|
1851
|
-
"NOT_AUTHENTICATED",
|
|
1852
|
-
401
|
|
1853
|
-
);
|
|
1854
|
-
}
|
|
1855
|
-
if (response.status === 400) {
|
|
1856
|
-
const body = await response.json().catch(() => ({}));
|
|
1857
|
-
throw new ProviderAPIError(
|
|
1858
|
-
`linkCustomerToChannel: could not resolve sales channel from publishable key. ${body?.message ?? ""}`.trim(),
|
|
1859
|
-
"CHANNEL_RESOLUTION_FAILED",
|
|
1860
|
-
400
|
|
1861
|
-
);
|
|
1862
|
-
}
|
|
1863
|
-
if (!response.ok) {
|
|
1864
|
-
const body = await response.json().catch(() => ({}));
|
|
1865
|
-
throw new ProviderAPIError(
|
|
1866
|
-
`linkCustomerToChannel failed with status ${response.status}: ${body?.message ?? "Unknown error"}`,
|
|
1867
|
-
"LINK_CHANNEL_FAILED",
|
|
1868
|
-
response.status
|
|
1869
|
-
);
|
|
1870
|
-
}
|
|
1871
|
-
const data = await response.json();
|
|
1872
|
-
this.logger.info("Customer linked to channel", { linked: data.linked, reason: data.reason });
|
|
1873
|
-
return data;
|
|
1874
|
-
} catch (error) {
|
|
1875
|
-
if (error instanceof ProviderAPIError) throw error;
|
|
1876
|
-
this.logger.error("linkCustomerToChannel failed", error);
|
|
1877
|
-
throw new ProviderAPIError(
|
|
1878
|
-
`linkCustomerToChannel failed: ${error.message}`,
|
|
1879
|
-
"LINK_CHANNEL_FAILED",
|
|
1880
|
-
error
|
|
1881
|
-
);
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
2045
|
};
|
|
1885
2046
|
|
|
1886
2047
|
// src/providers/medusa/payment.ts
|
|
@@ -2023,317 +2184,33 @@ async function getPaymentMethods(sdk, options) {
|
|
|
2023
2184
|
}
|
|
2024
2185
|
}
|
|
2025
2186
|
|
|
2026
|
-
// src/providers/medusa/
|
|
2027
|
-
var
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
constructor(config) {
|
|
2032
|
-
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
2033
|
-
this.apiKey = config.apiKey;
|
|
2034
|
-
this.logger = createLogger("MedusaAdminClient", config.debug);
|
|
2035
|
-
}
|
|
2036
|
-
// -----------------------------------------------------------------------
|
|
2037
|
-
// Private — HTTP helper
|
|
2038
|
-
// -----------------------------------------------------------------------
|
|
2039
|
-
async request(method, path, body) {
|
|
2040
|
-
const url = `${this.baseUrl}${path}`;
|
|
2041
|
-
this.logger.debug(`[admin] ${method} ${path}`);
|
|
2042
|
-
const headers = {
|
|
2043
|
-
"x-medusa-access-token": this.apiKey,
|
|
2044
|
-
"Content-Type": "application/json"
|
|
2045
|
-
};
|
|
2046
|
-
const init = {
|
|
2047
|
-
method,
|
|
2048
|
-
headers,
|
|
2049
|
-
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
2050
|
-
};
|
|
2051
|
-
let response;
|
|
2052
|
-
try {
|
|
2053
|
-
response = await fetch(url, init);
|
|
2054
|
-
} catch (networkError) {
|
|
2055
|
-
throw new ProviderAPIError(
|
|
2056
|
-
`[admin] Network error on ${method} ${path}: ${networkError.message}`,
|
|
2057
|
-
"NETWORK_ERROR",
|
|
2058
|
-
networkError
|
|
2059
|
-
);
|
|
2060
|
-
}
|
|
2061
|
-
if (!response.ok) {
|
|
2062
|
-
let message = `HTTP ${response.status}`;
|
|
2063
|
-
try {
|
|
2064
|
-
const json = await response.json();
|
|
2065
|
-
message = json?.message ?? message;
|
|
2066
|
-
} catch {
|
|
2067
|
-
}
|
|
2068
|
-
throw new ProviderAPIError(
|
|
2069
|
-
`[admin] ${method} ${path} failed: ${message}`,
|
|
2070
|
-
`ADMIN_API_${response.status}`,
|
|
2071
|
-
response.status
|
|
2072
|
-
);
|
|
2073
|
-
}
|
|
2074
|
-
return response.json();
|
|
2075
|
-
}
|
|
2076
|
-
// -----------------------------------------------------------------------
|
|
2077
|
-
// 3.1 — Sales Channel: Customers
|
|
2078
|
-
// -----------------------------------------------------------------------
|
|
2187
|
+
// src/providers/medusa/index.ts
|
|
2188
|
+
var MEDUSA_CART_RETRIEVE_FIELDS = "+items,+items.variant,+items.variant.product,+items.variant.product.thumbnail,+items.variant.options,+items.variant.options.option";
|
|
2189
|
+
var MedusaProvider = class {
|
|
2190
|
+
name = "medusa";
|
|
2191
|
+
version = "2.0.0";
|
|
2079
2192
|
/**
|
|
2080
|
-
*
|
|
2081
|
-
*
|
|
2082
|
-
* `GET /
|
|
2193
|
+
* Medusa backend capabilities.
|
|
2194
|
+
* Starts with static defaults, replaced by dynamic values from
|
|
2195
|
+
* `GET /store/thor/capabilities` once the fetch completes.
|
|
2083
2196
|
*/
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
"GET",
|
|
2088
|
-
`/admin/thor/sales-channels/${salesChannelId}/customers?limit=${limit}&offset=${offset}`
|
|
2089
|
-
);
|
|
2090
|
-
}
|
|
2091
|
-
// -----------------------------------------------------------------------
|
|
2092
|
-
// 3.2 — Sales Channel: API Keys
|
|
2093
|
-
// -----------------------------------------------------------------------
|
|
2197
|
+
_dynamicCapabilities = null;
|
|
2198
|
+
/** Promise that resolves when capabilities fetch completes (success or failure) */
|
|
2199
|
+
_capabilitiesPromise = null;
|
|
2094
2200
|
/**
|
|
2095
|
-
*
|
|
2096
|
-
*
|
|
2097
|
-
* `GET /admin/thor/sales-channels/:id/api-keys`
|
|
2201
|
+
* Getter returns dynamic capabilities if available, otherwise static fallback.
|
|
2098
2202
|
*/
|
|
2099
|
-
|
|
2100
|
-
return this.
|
|
2101
|
-
"GET",
|
|
2102
|
-
`/admin/thor/sales-channels/${salesChannelId}/api-keys`
|
|
2103
|
-
);
|
|
2203
|
+
get capabilities() {
|
|
2204
|
+
return this._dynamicCapabilities ?? MEDUSA_CAPABILITIES;
|
|
2104
2205
|
}
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
async getChannelCategories(salesChannelId, options = {}) {
|
|
2114
|
-
const { limit = 20, offset = 0, include_descendants = false } = options;
|
|
2115
|
-
const qs = new URLSearchParams({
|
|
2116
|
-
limit: String(limit),
|
|
2117
|
-
offset: String(offset),
|
|
2118
|
-
include_descendants: String(include_descendants)
|
|
2119
|
-
});
|
|
2120
|
-
return this.request(
|
|
2121
|
-
"GET",
|
|
2122
|
-
`/admin/thor/sales-channels/${salesChannelId}/categories?${qs}`
|
|
2123
|
-
);
|
|
2124
|
-
}
|
|
2125
|
-
// -----------------------------------------------------------------------
|
|
2126
|
-
// 3.4 — Category: Sales Channels
|
|
2127
|
-
// -----------------------------------------------------------------------
|
|
2128
|
-
/**
|
|
2129
|
-
* Sales channels to which a category is assigned.
|
|
2130
|
-
*
|
|
2131
|
-
* `GET /admin/thor/categories/:id/sales-channels`
|
|
2132
|
-
*/
|
|
2133
|
-
async getCategorySalesChannels(categoryId) {
|
|
2134
|
-
return this.request(
|
|
2135
|
-
"GET",
|
|
2136
|
-
`/admin/thor/categories/${categoryId}/sales-channels`
|
|
2137
|
-
);
|
|
2138
|
-
}
|
|
2139
|
-
// -----------------------------------------------------------------------
|
|
2140
|
-
// 3.5 — Category: Assign to Sales Channels
|
|
2141
|
-
// -----------------------------------------------------------------------
|
|
2142
|
-
/**
|
|
2143
|
-
* Assign a category to one or more sales channels (idempotent).
|
|
2144
|
-
*
|
|
2145
|
-
* `POST /admin/thor/categories/:id/sales-channels`
|
|
2146
|
-
*/
|
|
2147
|
-
async assignCategoryToChannels(categoryId, body) {
|
|
2148
|
-
return this.request(
|
|
2149
|
-
"POST",
|
|
2150
|
-
`/admin/thor/categories/${categoryId}/sales-channels`,
|
|
2151
|
-
body
|
|
2152
|
-
);
|
|
2153
|
-
}
|
|
2154
|
-
// -----------------------------------------------------------------------
|
|
2155
|
-
// 3.6 — Category: Unassign from Sales Channels
|
|
2156
|
-
// -----------------------------------------------------------------------
|
|
2157
|
-
/**
|
|
2158
|
-
* Remove category assignment from one or more sales channels (idempotent).
|
|
2159
|
-
*
|
|
2160
|
-
* `DELETE /admin/thor/categories/:id/sales-channels`
|
|
2161
|
-
*/
|
|
2162
|
-
async unassignCategoryFromChannels(categoryId, body) {
|
|
2163
|
-
return this.request(
|
|
2164
|
-
"DELETE",
|
|
2165
|
-
`/admin/thor/categories/${categoryId}/sales-channels`,
|
|
2166
|
-
body
|
|
2167
|
-
);
|
|
2168
|
-
}
|
|
2169
|
-
// -----------------------------------------------------------------------
|
|
2170
|
-
// 3.7 — Collection: Sales Channels
|
|
2171
|
-
// -----------------------------------------------------------------------
|
|
2172
|
-
/**
|
|
2173
|
-
* Sales channels to which a collection is assigned.
|
|
2174
|
-
*
|
|
2175
|
-
* `GET /admin/thor/collections/:id/sales-channels`
|
|
2176
|
-
*/
|
|
2177
|
-
async getCollectionSalesChannels(collectionId) {
|
|
2178
|
-
return this.request(
|
|
2179
|
-
"GET",
|
|
2180
|
-
`/admin/thor/collections/${collectionId}/sales-channels`
|
|
2181
|
-
);
|
|
2182
|
-
}
|
|
2183
|
-
// -----------------------------------------------------------------------
|
|
2184
|
-
// 3.8 — Collection: Assign to Sales Channels
|
|
2185
|
-
// -----------------------------------------------------------------------
|
|
2186
|
-
/**
|
|
2187
|
-
* Assign a collection to one or more sales channels (idempotent).
|
|
2188
|
-
*
|
|
2189
|
-
* `POST /admin/thor/collections/:id/sales-channels`
|
|
2190
|
-
*/
|
|
2191
|
-
async assignCollectionToChannels(collectionId, body) {
|
|
2192
|
-
return this.request(
|
|
2193
|
-
"POST",
|
|
2194
|
-
`/admin/thor/collections/${collectionId}/sales-channels`,
|
|
2195
|
-
body
|
|
2196
|
-
);
|
|
2197
|
-
}
|
|
2198
|
-
// -----------------------------------------------------------------------
|
|
2199
|
-
// 3.9 — Collection: Unassign from Sales Channels
|
|
2200
|
-
// -----------------------------------------------------------------------
|
|
2201
|
-
/**
|
|
2202
|
-
* Remove collection assignment from one or more sales channels (idempotent).
|
|
2203
|
-
*
|
|
2204
|
-
* `DELETE /admin/thor/collections/:id/sales-channels`
|
|
2205
|
-
*/
|
|
2206
|
-
async unassignCollectionFromChannels(collectionId, body) {
|
|
2207
|
-
return this.request(
|
|
2208
|
-
"DELETE",
|
|
2209
|
-
`/admin/thor/collections/${collectionId}/sales-channels`,
|
|
2210
|
-
body
|
|
2211
|
-
);
|
|
2212
|
-
}
|
|
2213
|
-
// -----------------------------------------------------------------------
|
|
2214
|
-
// 3.10 — Storefront Config: Read
|
|
2215
|
-
// -----------------------------------------------------------------------
|
|
2216
|
-
/**
|
|
2217
|
-
* Read the visual storefront configuration for a channel.
|
|
2218
|
-
* `:id` is the `sales_channel_id`.
|
|
2219
|
-
*
|
|
2220
|
-
* `GET /admin/thor/storefront-config/:id`
|
|
2221
|
-
*/
|
|
2222
|
-
async getStorefrontConfig(salesChannelId) {
|
|
2223
|
-
return this.request(
|
|
2224
|
-
"GET",
|
|
2225
|
-
`/admin/thor/storefront-config/${salesChannelId}`
|
|
2226
|
-
);
|
|
2227
|
-
}
|
|
2228
|
-
// -----------------------------------------------------------------------
|
|
2229
|
-
// 3.11 — Storefront Config: Update
|
|
2230
|
-
// -----------------------------------------------------------------------
|
|
2231
|
-
/**
|
|
2232
|
-
* Update the visual storefront configuration for a channel.
|
|
2233
|
-
* Triggers the `storefront_config.updated` event → ISR webhook.
|
|
2234
|
-
*
|
|
2235
|
-
* `PUT /admin/thor/storefront-config/:id`
|
|
2236
|
-
*/
|
|
2237
|
-
async updateStorefrontConfig(salesChannelId, body) {
|
|
2238
|
-
return this.request(
|
|
2239
|
-
"PUT",
|
|
2240
|
-
`/admin/thor/storefront-config/${salesChannelId}`,
|
|
2241
|
-
body
|
|
2242
|
-
);
|
|
2243
|
-
}
|
|
2244
|
-
// -----------------------------------------------------------------------
|
|
2245
|
-
// 3.12 — Site Designer Config: Read
|
|
2246
|
-
// -----------------------------------------------------------------------
|
|
2247
|
-
/**
|
|
2248
|
-
* Read the active Site Designer configuration for a channel, including the
|
|
2249
|
-
* last 10 history entries.
|
|
2250
|
-
*
|
|
2251
|
-
* `GET /admin/thor/site-config/:sales_channel_id`
|
|
2252
|
-
*/
|
|
2253
|
-
async getSiteConfig(salesChannelId) {
|
|
2254
|
-
return this.request(
|
|
2255
|
-
"GET",
|
|
2256
|
-
`/admin/thor/site-config/${salesChannelId}`
|
|
2257
|
-
);
|
|
2258
|
-
}
|
|
2259
|
-
// -----------------------------------------------------------------------
|
|
2260
|
-
// 3.13 — Site Designer Config: Update
|
|
2261
|
-
// -----------------------------------------------------------------------
|
|
2262
|
-
/**
|
|
2263
|
-
* Update the Site Designer configuration for a channel.
|
|
2264
|
-
* Creates a new history entry on every PUT.
|
|
2265
|
-
*
|
|
2266
|
-
* `PUT /admin/thor/site-config/:sales_channel_id`
|
|
2267
|
-
*/
|
|
2268
|
-
async updateSiteConfig(salesChannelId, body) {
|
|
2269
|
-
return this.request(
|
|
2270
|
-
"PUT",
|
|
2271
|
-
`/admin/thor/site-config/${salesChannelId}`,
|
|
2272
|
-
body
|
|
2273
|
-
);
|
|
2274
|
-
}
|
|
2275
|
-
// -----------------------------------------------------------------------
|
|
2276
|
-
// 3.14 — Site Designer Config: History
|
|
2277
|
-
// -----------------------------------------------------------------------
|
|
2278
|
-
/**
|
|
2279
|
-
* Full paginated history of Site Designer versions for a channel.
|
|
2280
|
-
* Entries are immutable — they are never deleted.
|
|
2281
|
-
*
|
|
2282
|
-
* `GET /admin/thor/site-config/:sales_channel_id/history`
|
|
2283
|
-
*/
|
|
2284
|
-
async getSiteConfigHistory(salesChannelId, options = {}) {
|
|
2285
|
-
const { limit = 20, offset = 0 } = options;
|
|
2286
|
-
return this.request(
|
|
2287
|
-
"GET",
|
|
2288
|
-
`/admin/thor/site-config/${salesChannelId}/history?limit=${limit}&offset=${offset}`
|
|
2289
|
-
);
|
|
2290
|
-
}
|
|
2291
|
-
// -----------------------------------------------------------------------
|
|
2292
|
-
// 3.15 — Site Designer Config: Restore
|
|
2293
|
-
// -----------------------------------------------------------------------
|
|
2294
|
-
/**
|
|
2295
|
-
* Restore a previous Site Designer version.
|
|
2296
|
-
* Internally runs the update workflow with the stored config of the target
|
|
2297
|
-
* version — this creates a **new** history entry and does NOT mutate past
|
|
2298
|
-
* entries.
|
|
2299
|
-
*
|
|
2300
|
-
* `POST /admin/thor/site-config/:sales_channel_id/restore/:version`
|
|
2301
|
-
*/
|
|
2302
|
-
async restoreSiteConfig(salesChannelId, version) {
|
|
2303
|
-
return this.request(
|
|
2304
|
-
"POST",
|
|
2305
|
-
`/admin/thor/site-config/${salesChannelId}/restore/${encodeURIComponent(version)}`
|
|
2306
|
-
);
|
|
2307
|
-
}
|
|
2308
|
-
};
|
|
2309
|
-
|
|
2310
|
-
// src/providers/medusa/index.ts
|
|
2311
|
-
var MEDUSA_CART_RETRIEVE_FIELDS = "+items,+items.variant,+items.variant.product,+items.variant.product.thumbnail,+items.variant.options,+items.variant.options.option";
|
|
2312
|
-
var MedusaProvider = class {
|
|
2313
|
-
name = "medusa";
|
|
2314
|
-
version = "2.0.0";
|
|
2315
|
-
/**
|
|
2316
|
-
* Medusa backend capabilities.
|
|
2317
|
-
* Starts with static defaults, replaced by dynamic values from
|
|
2318
|
-
* `GET /store/thor/capabilities` once the fetch completes.
|
|
2319
|
-
*/
|
|
2320
|
-
_dynamicCapabilities = null;
|
|
2321
|
-
/** Promise that resolves when capabilities fetch completes (success or failure) */
|
|
2322
|
-
_capabilitiesPromise = null;
|
|
2323
|
-
/**
|
|
2324
|
-
* Getter returns dynamic capabilities if available, otherwise static fallback.
|
|
2325
|
-
*/
|
|
2326
|
-
get capabilities() {
|
|
2327
|
-
return this._dynamicCapabilities ?? MEDUSA_CAPABILITIES;
|
|
2328
|
-
}
|
|
2329
|
-
sdk;
|
|
2330
|
-
logger;
|
|
2331
|
-
currencyCode;
|
|
2332
|
-
retryConfig;
|
|
2333
|
-
baseUrl;
|
|
2334
|
-
publishableKey;
|
|
2335
|
-
/** Secret API key for admin operations (if configured). */
|
|
2336
|
-
adminApiKey;
|
|
2206
|
+
sdk;
|
|
2207
|
+
logger;
|
|
2208
|
+
currencyCode;
|
|
2209
|
+
retryConfig;
|
|
2210
|
+
baseUrl;
|
|
2211
|
+
publishableKey;
|
|
2212
|
+
/** Secret API key for admin operations (if configured). */
|
|
2213
|
+
adminApiKey;
|
|
2337
2214
|
/**
|
|
2338
2215
|
* Dedicated SDK instance for admin calls.
|
|
2339
2216
|
* Separate from `this.sdk` to prevent the admin API key from being overwritten
|
|
@@ -2341,18 +2218,6 @@ var MedusaProvider = class {
|
|
|
2341
2218
|
* Only created when adminConfig.apiKey is provided.
|
|
2342
2219
|
*/
|
|
2343
2220
|
sdkAdmin;
|
|
2344
|
-
/**
|
|
2345
|
-
* Admin client for Thor multi-tenant endpoints (`/admin/thor/`).
|
|
2346
|
-
* Available when `adminConfig.apiKey` is provided.
|
|
2347
|
-
*
|
|
2348
|
-
* @example
|
|
2349
|
-
* ```typescript
|
|
2350
|
-
* const { customers } = await commerce.admin.getChannelCustomers('sc_01JXXXXX');
|
|
2351
|
-
* const { api_keys } = await commerce.admin.getChannelApiKeys('sc_01JXXXXX');
|
|
2352
|
-
* await commerce.admin.updateStorefrontConfig('sc_01JXXXXX', { theme_accent_color: '#EF4444', ... });
|
|
2353
|
-
* ```
|
|
2354
|
-
*/
|
|
2355
|
-
admin;
|
|
2356
2221
|
/** Cached distinct product status values — rarely changes, cached for process lifetime */
|
|
2357
2222
|
_cachedProductStatuses = null;
|
|
2358
2223
|
/**
|
|
@@ -2423,15 +2288,10 @@ var MedusaProvider = class {
|
|
|
2423
2288
|
debug: config.debug ?? false
|
|
2424
2289
|
// No publishableKey or auth config: this instance is admin-only, no JWT needed
|
|
2425
2290
|
});
|
|
2426
|
-
this.admin = new MedusaAdminClient({
|
|
2427
|
-
baseUrl: config.baseUrl,
|
|
2428
|
-
apiKey: config.adminConfig.apiKey,
|
|
2429
|
-
debug: config.debug
|
|
2430
|
-
});
|
|
2431
2291
|
this.logger.info("[Admin Auth] Dedicated admin SDK instance created with API key");
|
|
2432
2292
|
}
|
|
2433
2293
|
if (config.auth) {
|
|
2434
|
-
this.auth = new MedusaAuthProvider(this.sdk, config.auth, config.debug
|
|
2294
|
+
this.auth = new MedusaAuthProvider(this.sdk, config.auth, config.debug);
|
|
2435
2295
|
this.logger.info("Auth provider initialized");
|
|
2436
2296
|
}
|
|
2437
2297
|
this._capabilitiesPromise = this._fetchCapabilities();
|
|
@@ -5394,187 +5254,2794 @@ var MedusaProvider = class {
|
|
|
5394
5254
|
);
|
|
5395
5255
|
}
|
|
5396
5256
|
}
|
|
5397
|
-
/**
|
|
5398
|
-
* Medusa always requires storefront configuration for multi-tenant support.
|
|
5399
|
-
*/
|
|
5400
|
-
isStorefrontRequired() {
|
|
5401
|
-
return true;
|
|
5257
|
+
/**
|
|
5258
|
+
* Medusa always requires storefront configuration for multi-tenant support.
|
|
5259
|
+
*/
|
|
5260
|
+
isStorefrontRequired() {
|
|
5261
|
+
return true;
|
|
5262
|
+
}
|
|
5263
|
+
// ============================================
|
|
5264
|
+
// Thor Plugin — Storefront Configuration
|
|
5265
|
+
// ============================================
|
|
5266
|
+
/** In-memory cache for storefront config (60s TTL) */
|
|
5267
|
+
_storefrontConfigCache = null;
|
|
5268
|
+
/** In-memory cache for designer config (60s TTL) */
|
|
5269
|
+
_designerConfigCache = null;
|
|
5270
|
+
/** Cache TTL for Thor plugin configs (60 seconds) */
|
|
5271
|
+
THOR_CONFIG_CACHE_TTL = 60 * 1e3;
|
|
5272
|
+
/**
|
|
5273
|
+
* Get per-channel storefront configuration from Thor Commerce plugin.
|
|
5274
|
+
* Calls `GET /store/thor/config`.
|
|
5275
|
+
*
|
|
5276
|
+
* @returns StorefrontConfig or null if plugin is not installed / config not set
|
|
5277
|
+
*/
|
|
5278
|
+
async getStorefrontConfig() {
|
|
5279
|
+
if (this._storefrontConfigCache && Date.now() < this._storefrontConfigCache.expiresAt) {
|
|
5280
|
+
return this._storefrontConfigCache.config;
|
|
5281
|
+
}
|
|
5282
|
+
try {
|
|
5283
|
+
const url = `${this.baseUrl}/store/thor/config`;
|
|
5284
|
+
const response = await fetchWithRetry(
|
|
5285
|
+
url,
|
|
5286
|
+
{
|
|
5287
|
+
method: "GET",
|
|
5288
|
+
headers: {
|
|
5289
|
+
"x-publishable-api-key": this.publishableKey,
|
|
5290
|
+
"Content-Type": "application/json"
|
|
5291
|
+
}
|
|
5292
|
+
},
|
|
5293
|
+
this.retryConfig
|
|
5294
|
+
);
|
|
5295
|
+
if (response.status === 404) {
|
|
5296
|
+
this._storefrontConfigCache = { config: null, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5297
|
+
return null;
|
|
5298
|
+
}
|
|
5299
|
+
if (!response.ok) {
|
|
5300
|
+
this.logger.warn("[StorefrontConfig] Non-OK response", { status: response.status });
|
|
5301
|
+
return null;
|
|
5302
|
+
}
|
|
5303
|
+
const data = await response.json();
|
|
5304
|
+
const config = transformStorefrontConfig(data.storefront_config ?? data);
|
|
5305
|
+
this._storefrontConfigCache = { config, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5306
|
+
this.logger.debug("[StorefrontConfig] Loaded from backend", { id: config.id });
|
|
5307
|
+
return config;
|
|
5308
|
+
} catch (error) {
|
|
5309
|
+
this.logger.warn("[StorefrontConfig] Failed to fetch", {
|
|
5310
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5311
|
+
});
|
|
5312
|
+
return null;
|
|
5313
|
+
}
|
|
5314
|
+
}
|
|
5315
|
+
/**
|
|
5316
|
+
* Get active Site Designer configuration from Thor Commerce plugin.
|
|
5317
|
+
* Calls `GET /store/thor/site-config`.
|
|
5318
|
+
*
|
|
5319
|
+
* @returns Versioned DesignerConfig or null if plugin is not installed / config not set
|
|
5320
|
+
*/
|
|
5321
|
+
async getDesignerConfig() {
|
|
5322
|
+
if (this._designerConfigCache && Date.now() < this._designerConfigCache.expiresAt) {
|
|
5323
|
+
return this._designerConfigCache.result;
|
|
5324
|
+
}
|
|
5325
|
+
try {
|
|
5326
|
+
const url = `${this.baseUrl}/store/thor/site-config`;
|
|
5327
|
+
const response = await fetchWithRetry(
|
|
5328
|
+
url,
|
|
5329
|
+
{
|
|
5330
|
+
method: "GET",
|
|
5331
|
+
headers: {
|
|
5332
|
+
"x-publishable-api-key": this.publishableKey,
|
|
5333
|
+
"Content-Type": "application/json"
|
|
5334
|
+
}
|
|
5335
|
+
},
|
|
5336
|
+
this.retryConfig
|
|
5337
|
+
);
|
|
5338
|
+
if (response.status === 404) {
|
|
5339
|
+
this._designerConfigCache = { result: null, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5340
|
+
return null;
|
|
5341
|
+
}
|
|
5342
|
+
if (!response.ok) {
|
|
5343
|
+
this.logger.warn("[DesignerConfig] Non-OK response", { status: response.status });
|
|
5344
|
+
return null;
|
|
5345
|
+
}
|
|
5346
|
+
const data = await response.json();
|
|
5347
|
+
const result = {
|
|
5348
|
+
version: data.version ?? "",
|
|
5349
|
+
config: data.config
|
|
5350
|
+
};
|
|
5351
|
+
this._designerConfigCache = { result, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5352
|
+
this.logger.debug("[DesignerConfig] Loaded from backend", { version: result.version });
|
|
5353
|
+
return result;
|
|
5354
|
+
} catch (error) {
|
|
5355
|
+
this.logger.warn("[DesignerConfig] Failed to fetch", {
|
|
5356
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5357
|
+
});
|
|
5358
|
+
return null;
|
|
5359
|
+
}
|
|
5360
|
+
}
|
|
5361
|
+
// ============================================
|
|
5362
|
+
// Thor Plugin — Admin Site Designer
|
|
5363
|
+
// ============================================
|
|
5364
|
+
/**
|
|
5365
|
+
* Get admin Site Designer config + history for a sales channel.
|
|
5366
|
+
* Calls `GET /admin/thor/site-config/:salesChannelId`.
|
|
5367
|
+
*/
|
|
5368
|
+
async getAdminDesignerConfig(salesChannelId) {
|
|
5369
|
+
return this.makeAdminRequest(async () => {
|
|
5370
|
+
const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}`;
|
|
5371
|
+
let data;
|
|
5372
|
+
try {
|
|
5373
|
+
data = await this.sdk.client.fetch(path, { method: "GET" });
|
|
5374
|
+
} catch (error) {
|
|
5375
|
+
if (error?.status === 404) return null;
|
|
5376
|
+
throw new ProviderAPIError(
|
|
5377
|
+
`Failed to fetch admin designer config: ${error?.message ?? error?.status}`,
|
|
5378
|
+
"medusa",
|
|
5379
|
+
error?.status
|
|
5380
|
+
);
|
|
5381
|
+
}
|
|
5382
|
+
return {
|
|
5383
|
+
version: data.version ?? "",
|
|
5384
|
+
config: data.config,
|
|
5385
|
+
history: data.history
|
|
5386
|
+
};
|
|
5387
|
+
});
|
|
5388
|
+
}
|
|
5389
|
+
/**
|
|
5390
|
+
* Update the Site Designer config for a sales channel.
|
|
5391
|
+
* Calls `PUT /admin/thor/site-config/:salesChannelId`.
|
|
5392
|
+
*/
|
|
5393
|
+
async updateAdminDesignerConfig(salesChannelId, config) {
|
|
5394
|
+
return this.makeAdminRequest(async () => {
|
|
5395
|
+
const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}`;
|
|
5396
|
+
let data;
|
|
5397
|
+
try {
|
|
5398
|
+
data = await this.sdk.client.fetch(path, {
|
|
5399
|
+
method: "PUT",
|
|
5400
|
+
body: JSON.stringify({ config }),
|
|
5401
|
+
headers: { "Content-Type": "application/json" }
|
|
5402
|
+
});
|
|
5403
|
+
} catch (error) {
|
|
5404
|
+
throw new ProviderAPIError(
|
|
5405
|
+
`Failed to update admin designer config: ${error?.message ?? error?.status}`,
|
|
5406
|
+
"medusa",
|
|
5407
|
+
error?.status
|
|
5408
|
+
);
|
|
5409
|
+
}
|
|
5410
|
+
return {
|
|
5411
|
+
version: data.version ?? "",
|
|
5412
|
+
config: data.config
|
|
5413
|
+
};
|
|
5414
|
+
});
|
|
5415
|
+
}
|
|
5416
|
+
/**
|
|
5417
|
+
* Restore a previous Site Designer config version.
|
|
5418
|
+
* Calls `POST /admin/thor/site-config/:salesChannelId/restore/:version`.
|
|
5419
|
+
*/
|
|
5420
|
+
async restoreAdminDesignerConfig(salesChannelId, version) {
|
|
5421
|
+
return this.makeAdminRequest(async () => {
|
|
5422
|
+
const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}/restore/${encodeURIComponent(version)}`;
|
|
5423
|
+
let data;
|
|
5424
|
+
try {
|
|
5425
|
+
data = await this.sdk.client.fetch(path, { method: "POST" });
|
|
5426
|
+
} catch (error) {
|
|
5427
|
+
throw new ProviderAPIError(
|
|
5428
|
+
`Failed to restore admin designer config: ${error?.message ?? error?.status}`,
|
|
5429
|
+
"medusa",
|
|
5430
|
+
error?.status
|
|
5431
|
+
);
|
|
5432
|
+
}
|
|
5433
|
+
return {
|
|
5434
|
+
version: data.version ?? "",
|
|
5435
|
+
config: data.config
|
|
5436
|
+
};
|
|
5437
|
+
});
|
|
5438
|
+
}
|
|
5439
|
+
};
|
|
5440
|
+
|
|
5441
|
+
// src/providers/shopify/transforms.ts
|
|
5442
|
+
function transformMoney2(shopifyMoney) {
|
|
5443
|
+
return {
|
|
5444
|
+
amount: shopifyMoney?.amount ?? "0.00",
|
|
5445
|
+
currencyCode: shopifyMoney?.currencyCode?.toUpperCase() ?? "USD"
|
|
5446
|
+
};
|
|
5447
|
+
}
|
|
5448
|
+
function transformImage2(shopifyImage) {
|
|
5449
|
+
return {
|
|
5450
|
+
url: shopifyImage?.url ?? "",
|
|
5451
|
+
altText: shopifyImage?.altText ?? "",
|
|
5452
|
+
width: shopifyImage?.width ?? 800,
|
|
5453
|
+
height: shopifyImage?.height ?? 800
|
|
5454
|
+
};
|
|
5455
|
+
}
|
|
5456
|
+
function transformVariant(raw) {
|
|
5457
|
+
return {
|
|
5458
|
+
id: raw.id,
|
|
5459
|
+
title: raw.title ?? "",
|
|
5460
|
+
availableForSale: raw.availableForSale ?? true,
|
|
5461
|
+
price: transformMoney2(raw.price),
|
|
5462
|
+
compareAtPrice: raw.compareAtPrice ? transformMoney2(raw.compareAtPrice) : void 0,
|
|
5463
|
+
selectedOptions: (raw.selectedOptions ?? []).map((opt) => ({
|
|
5464
|
+
name: opt.name,
|
|
5465
|
+
value: opt.value
|
|
5466
|
+
})),
|
|
5467
|
+
image: raw.image ? transformImage2(raw.image) : void 0
|
|
5468
|
+
};
|
|
5469
|
+
}
|
|
5470
|
+
function transformOption(raw) {
|
|
5471
|
+
return {
|
|
5472
|
+
id: raw.id,
|
|
5473
|
+
name: raw.name,
|
|
5474
|
+
values: raw.values ?? []
|
|
5475
|
+
};
|
|
5476
|
+
}
|
|
5477
|
+
function transformProduct2(raw) {
|
|
5478
|
+
const images = (raw.images?.edges ?? []).map((edge) => transformImage2(edge.node));
|
|
5479
|
+
const variants = (raw.variants?.edges ?? []).map((edge) => transformVariant(edge.node));
|
|
5480
|
+
return {
|
|
5481
|
+
id: raw.id,
|
|
5482
|
+
handle: raw.handle,
|
|
5483
|
+
title: raw.title,
|
|
5484
|
+
description: raw.description ?? "",
|
|
5485
|
+
descriptionHtml: raw.descriptionHtml ?? "",
|
|
5486
|
+
availableForSale: raw.availableForSale ?? true,
|
|
5487
|
+
featuredImage: raw.featuredImage ? transformImage2(raw.featuredImage) : images[0] ?? { url: "", altText: "", width: 800, height: 800 },
|
|
5488
|
+
images,
|
|
5489
|
+
options: (raw.options ?? []).map(transformOption),
|
|
5490
|
+
variants,
|
|
5491
|
+
priceRange: {
|
|
5492
|
+
minVariantPrice: transformMoney2(raw.priceRange?.minVariantPrice),
|
|
5493
|
+
maxVariantPrice: transformMoney2(raw.priceRange?.maxVariantPrice)
|
|
5494
|
+
},
|
|
5495
|
+
seo: {
|
|
5496
|
+
title: raw.seo?.title ?? raw.title ?? "",
|
|
5497
|
+
description: raw.seo?.description ?? raw.description ?? ""
|
|
5498
|
+
},
|
|
5499
|
+
tags: raw.tags ?? [],
|
|
5500
|
+
createdAt: raw.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5501
|
+
updatedAt: raw.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5502
|
+
vendor: raw.vendor,
|
|
5503
|
+
productType: raw.productType
|
|
5504
|
+
};
|
|
5505
|
+
}
|
|
5506
|
+
function transformCollection2(raw) {
|
|
5507
|
+
return {
|
|
5508
|
+
id: raw.id ?? "",
|
|
5509
|
+
handle: raw.handle,
|
|
5510
|
+
title: raw.title,
|
|
5511
|
+
description: raw.description ?? "",
|
|
5512
|
+
seo: {
|
|
5513
|
+
title: raw.seo?.title ?? raw.title ?? "",
|
|
5514
|
+
description: raw.seo?.description ?? raw.description ?? ""
|
|
5515
|
+
},
|
|
5516
|
+
updatedAt: raw.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5517
|
+
path: `/collections/${raw.handle}`
|
|
5518
|
+
};
|
|
5519
|
+
}
|
|
5520
|
+
function transformCartItem2(raw) {
|
|
5521
|
+
const merchandise = raw.merchandise ?? {};
|
|
5522
|
+
const product = merchandise.product ?? {};
|
|
5523
|
+
return {
|
|
5524
|
+
id: raw.id,
|
|
5525
|
+
quantity: raw.quantity ?? 1,
|
|
5526
|
+
cost: {
|
|
5527
|
+
totalAmount: transformMoney2(raw.cost?.totalAmount)
|
|
5528
|
+
},
|
|
5529
|
+
merchandise: {
|
|
5530
|
+
id: merchandise.id ?? "",
|
|
5531
|
+
title: merchandise.title ?? "",
|
|
5532
|
+
selectedOptions: (merchandise.selectedOptions ?? []).map((opt) => ({
|
|
5533
|
+
name: opt.name,
|
|
5534
|
+
value: opt.value
|
|
5535
|
+
})),
|
|
5536
|
+
product: {
|
|
5537
|
+
id: product.id ?? "",
|
|
5538
|
+
handle: product.handle ?? "",
|
|
5539
|
+
title: product.title ?? "",
|
|
5540
|
+
featuredImage: product.featuredImage ? transformImage2(product.featuredImage) : { url: "", altText: "", width: 800, height: 800 }
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
};
|
|
5544
|
+
}
|
|
5545
|
+
function transformCart2(raw) {
|
|
5546
|
+
const lines = (raw.lines?.edges ?? []).map((edge) => transformCartItem2(edge.node));
|
|
5547
|
+
return {
|
|
5548
|
+
id: raw.id,
|
|
5549
|
+
checkoutUrl: raw.checkoutUrl ?? "",
|
|
5550
|
+
totalQuantity: raw.totalQuantity ?? 0,
|
|
5551
|
+
cost: {
|
|
5552
|
+
subtotalAmount: transformMoney2(raw.cost?.subtotalAmount),
|
|
5553
|
+
totalAmount: transformMoney2(raw.cost?.totalAmount),
|
|
5554
|
+
totalTaxAmount: raw.cost?.totalTaxAmount ? transformMoney2(raw.cost.totalTaxAmount) : void 0
|
|
5555
|
+
},
|
|
5556
|
+
lines
|
|
5557
|
+
};
|
|
5558
|
+
}
|
|
5559
|
+
|
|
5560
|
+
// src/providers/shopify/queries.ts
|
|
5561
|
+
var PRODUCT_FRAGMENT = `
|
|
5562
|
+
fragment ProductFields on Product {
|
|
5563
|
+
id
|
|
5564
|
+
title
|
|
5565
|
+
handle
|
|
5566
|
+
description
|
|
5567
|
+
descriptionHtml
|
|
5568
|
+
vendor
|
|
5569
|
+
productType
|
|
5570
|
+
tags
|
|
5571
|
+
availableForSale
|
|
5572
|
+
createdAt
|
|
5573
|
+
updatedAt
|
|
5574
|
+
seo {
|
|
5575
|
+
title
|
|
5576
|
+
description
|
|
5577
|
+
}
|
|
5578
|
+
featuredImage {
|
|
5579
|
+
url
|
|
5580
|
+
altText
|
|
5581
|
+
width
|
|
5582
|
+
height
|
|
5583
|
+
}
|
|
5584
|
+
images(first: 10) {
|
|
5585
|
+
edges {
|
|
5586
|
+
node {
|
|
5587
|
+
url
|
|
5588
|
+
altText
|
|
5589
|
+
width
|
|
5590
|
+
height
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
priceRange {
|
|
5595
|
+
minVariantPrice {
|
|
5596
|
+
amount
|
|
5597
|
+
currencyCode
|
|
5598
|
+
}
|
|
5599
|
+
maxVariantPrice {
|
|
5600
|
+
amount
|
|
5601
|
+
currencyCode
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
5604
|
+
options {
|
|
5605
|
+
id
|
|
5606
|
+
name
|
|
5607
|
+
values
|
|
5608
|
+
}
|
|
5609
|
+
variants(first: 100) {
|
|
5610
|
+
edges {
|
|
5611
|
+
node {
|
|
5612
|
+
id
|
|
5613
|
+
title
|
|
5614
|
+
availableForSale
|
|
5615
|
+
price {
|
|
5616
|
+
amount
|
|
5617
|
+
currencyCode
|
|
5618
|
+
}
|
|
5619
|
+
compareAtPrice {
|
|
5620
|
+
amount
|
|
5621
|
+
currencyCode
|
|
5622
|
+
}
|
|
5623
|
+
selectedOptions {
|
|
5624
|
+
name
|
|
5625
|
+
value
|
|
5626
|
+
}
|
|
5627
|
+
image {
|
|
5628
|
+
url
|
|
5629
|
+
altText
|
|
5630
|
+
width
|
|
5631
|
+
height
|
|
5632
|
+
}
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
}
|
|
5637
|
+
`;
|
|
5638
|
+
var GET_PRODUCT_BY_HANDLE = `
|
|
5639
|
+
query GetProductByHandle($handle: String!) {
|
|
5640
|
+
product(handle: $handle) {
|
|
5641
|
+
...ProductFields
|
|
5642
|
+
}
|
|
5643
|
+
}
|
|
5644
|
+
${PRODUCT_FRAGMENT}
|
|
5645
|
+
`;
|
|
5646
|
+
var GET_PRODUCTS = `
|
|
5647
|
+
query GetProducts($first: Int!, $query: String, $sortKey: ProductSortKeys, $reverse: Boolean, $after: String) {
|
|
5648
|
+
products(first: $first, query: $query, sortKey: $sortKey, reverse: $reverse, after: $after) {
|
|
5649
|
+
edges {
|
|
5650
|
+
node {
|
|
5651
|
+
...ProductFields
|
|
5652
|
+
}
|
|
5653
|
+
cursor
|
|
5654
|
+
}
|
|
5655
|
+
pageInfo {
|
|
5656
|
+
hasNextPage
|
|
5657
|
+
hasPreviousPage
|
|
5658
|
+
}
|
|
5659
|
+
}
|
|
5660
|
+
}
|
|
5661
|
+
${PRODUCT_FRAGMENT}
|
|
5662
|
+
`;
|
|
5663
|
+
var GET_PRODUCT_RECOMMENDATIONS = `
|
|
5664
|
+
query GetProductRecommendations($productId: ID!) {
|
|
5665
|
+
productRecommendations(productId: $productId) {
|
|
5666
|
+
...ProductFields
|
|
5667
|
+
}
|
|
5668
|
+
}
|
|
5669
|
+
${PRODUCT_FRAGMENT}
|
|
5670
|
+
`;
|
|
5671
|
+
var COLLECTION_FRAGMENT = `
|
|
5672
|
+
fragment CollectionFields on Collection {
|
|
5673
|
+
id
|
|
5674
|
+
title
|
|
5675
|
+
handle
|
|
5676
|
+
description
|
|
5677
|
+
image {
|
|
5678
|
+
url
|
|
5679
|
+
altText
|
|
5680
|
+
width
|
|
5681
|
+
height
|
|
5682
|
+
}
|
|
5683
|
+
seo {
|
|
5684
|
+
title
|
|
5685
|
+
description
|
|
5686
|
+
}
|
|
5687
|
+
updatedAt
|
|
5688
|
+
}
|
|
5689
|
+
`;
|
|
5690
|
+
var GET_COLLECTION_BY_HANDLE = `
|
|
5691
|
+
query GetCollectionByHandle($handle: String!) {
|
|
5692
|
+
collection(handle: $handle) {
|
|
5693
|
+
...CollectionFields
|
|
5694
|
+
}
|
|
5695
|
+
}
|
|
5696
|
+
${COLLECTION_FRAGMENT}
|
|
5697
|
+
`;
|
|
5698
|
+
var GET_COLLECTIONS = `
|
|
5699
|
+
query GetCollections($first: Int!) {
|
|
5700
|
+
collections(first: $first) {
|
|
5701
|
+
edges {
|
|
5702
|
+
node {
|
|
5703
|
+
...CollectionFields
|
|
5704
|
+
}
|
|
5705
|
+
}
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
5708
|
+
${COLLECTION_FRAGMENT}
|
|
5709
|
+
`;
|
|
5710
|
+
var GET_COLLECTION_PRODUCTS = `
|
|
5711
|
+
query GetCollectionProducts($handle: String!, $first: Int!, $sortKey: ProductCollectionSortKeys, $reverse: Boolean, $after: String) {
|
|
5712
|
+
collection(handle: $handle) {
|
|
5713
|
+
products(first: $first, sortKey: $sortKey, reverse: $reverse, after: $after) {
|
|
5714
|
+
edges {
|
|
5715
|
+
node {
|
|
5716
|
+
...ProductFields
|
|
5717
|
+
}
|
|
5718
|
+
cursor
|
|
5719
|
+
}
|
|
5720
|
+
pageInfo {
|
|
5721
|
+
hasNextPage
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5725
|
+
}
|
|
5726
|
+
${PRODUCT_FRAGMENT}
|
|
5727
|
+
`;
|
|
5728
|
+
var CART_FRAGMENT = `
|
|
5729
|
+
fragment CartFields on Cart {
|
|
5730
|
+
id
|
|
5731
|
+
checkoutUrl
|
|
5732
|
+
totalQuantity
|
|
5733
|
+
cost {
|
|
5734
|
+
subtotalAmount {
|
|
5735
|
+
amount
|
|
5736
|
+
currencyCode
|
|
5737
|
+
}
|
|
5738
|
+
totalAmount {
|
|
5739
|
+
amount
|
|
5740
|
+
currencyCode
|
|
5741
|
+
}
|
|
5742
|
+
totalTaxAmount {
|
|
5743
|
+
amount
|
|
5744
|
+
currencyCode
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5747
|
+
lines(first: 100) {
|
|
5748
|
+
edges {
|
|
5749
|
+
node {
|
|
5750
|
+
id
|
|
5751
|
+
quantity
|
|
5752
|
+
cost {
|
|
5753
|
+
totalAmount {
|
|
5754
|
+
amount
|
|
5755
|
+
currencyCode
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5758
|
+
merchandise {
|
|
5759
|
+
... on ProductVariant {
|
|
5760
|
+
id
|
|
5761
|
+
title
|
|
5762
|
+
selectedOptions {
|
|
5763
|
+
name
|
|
5764
|
+
value
|
|
5765
|
+
}
|
|
5766
|
+
product {
|
|
5767
|
+
id
|
|
5768
|
+
handle
|
|
5769
|
+
title
|
|
5770
|
+
featuredImage {
|
|
5771
|
+
url
|
|
5772
|
+
altText
|
|
5773
|
+
width
|
|
5774
|
+
height
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
}
|
|
5780
|
+
}
|
|
5781
|
+
}
|
|
5782
|
+
}
|
|
5783
|
+
`;
|
|
5784
|
+
var GET_CART = `
|
|
5785
|
+
query GetCart($cartId: ID!) {
|
|
5786
|
+
cart(id: $cartId) {
|
|
5787
|
+
...CartFields
|
|
5788
|
+
}
|
|
5789
|
+
}
|
|
5790
|
+
${CART_FRAGMENT}
|
|
5791
|
+
`;
|
|
5792
|
+
|
|
5793
|
+
// src/providers/shopify/mutations.ts
|
|
5794
|
+
var CART_CREATE = `
|
|
5795
|
+
mutation CartCreate($input: CartInput!) {
|
|
5796
|
+
cartCreate(input: $input) {
|
|
5797
|
+
cart {
|
|
5798
|
+
...CartFields
|
|
5799
|
+
}
|
|
5800
|
+
userErrors {
|
|
5801
|
+
field
|
|
5802
|
+
message
|
|
5803
|
+
code
|
|
5804
|
+
}
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5807
|
+
${CART_FRAGMENT}
|
|
5808
|
+
`;
|
|
5809
|
+
var CART_LINES_ADD = `
|
|
5810
|
+
mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
|
|
5811
|
+
cartLinesAdd(cartId: $cartId, lines: $lines) {
|
|
5812
|
+
cart {
|
|
5813
|
+
...CartFields
|
|
5814
|
+
}
|
|
5815
|
+
userErrors {
|
|
5816
|
+
field
|
|
5817
|
+
message
|
|
5818
|
+
code
|
|
5819
|
+
}
|
|
5820
|
+
}
|
|
5821
|
+
}
|
|
5822
|
+
${CART_FRAGMENT}
|
|
5823
|
+
`;
|
|
5824
|
+
var CART_LINES_UPDATE = `
|
|
5825
|
+
mutation CartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
|
|
5826
|
+
cartLinesUpdate(cartId: $cartId, lines: $lines) {
|
|
5827
|
+
cart {
|
|
5828
|
+
...CartFields
|
|
5829
|
+
}
|
|
5830
|
+
userErrors {
|
|
5831
|
+
field
|
|
5832
|
+
message
|
|
5833
|
+
code
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
${CART_FRAGMENT}
|
|
5838
|
+
`;
|
|
5839
|
+
var CART_LINES_REMOVE = `
|
|
5840
|
+
mutation CartLinesRemove($cartId: ID!, $lineIds: [ID!]!) {
|
|
5841
|
+
cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
|
|
5842
|
+
cart {
|
|
5843
|
+
...CartFields
|
|
5844
|
+
}
|
|
5845
|
+
userErrors {
|
|
5846
|
+
field
|
|
5847
|
+
message
|
|
5848
|
+
code
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
${CART_FRAGMENT}
|
|
5853
|
+
`;
|
|
5854
|
+
var CART_DISCOUNT_CODES_UPDATE = `
|
|
5855
|
+
mutation CartDiscountCodesUpdate($cartId: ID!, $discountCodes: [String!]!) {
|
|
5856
|
+
cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) {
|
|
5857
|
+
cart {
|
|
5858
|
+
...CartFields
|
|
5859
|
+
}
|
|
5860
|
+
userErrors {
|
|
5861
|
+
field
|
|
5862
|
+
message
|
|
5863
|
+
code
|
|
5864
|
+
}
|
|
5865
|
+
}
|
|
5866
|
+
}
|
|
5867
|
+
${CART_FRAGMENT}
|
|
5868
|
+
`;
|
|
5869
|
+
|
|
5870
|
+
// src/providers/shopify/index.ts
|
|
5871
|
+
var ShopifyProvider = class {
|
|
5872
|
+
name = "shopify";
|
|
5873
|
+
version = "1.0.0";
|
|
5874
|
+
capabilities = SHOPIFY_CAPABILITIES;
|
|
5875
|
+
logger;
|
|
5876
|
+
storeDomain;
|
|
5877
|
+
apiVersion;
|
|
5878
|
+
endpoint;
|
|
5879
|
+
headers;
|
|
5880
|
+
constructor(config) {
|
|
5881
|
+
this.logger = createLogger("ShopifyProvider", config.debug);
|
|
5882
|
+
this.storeDomain = config.storeDomain.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
5883
|
+
this.apiVersion = config.apiVersion ?? "2026-04";
|
|
5884
|
+
this.endpoint = `https://${this.storeDomain}/api/${this.apiVersion}/graphql.json`;
|
|
5885
|
+
this.headers = {
|
|
5886
|
+
"Content-Type": "application/json",
|
|
5887
|
+
...config.privateAccessToken ? { "Shopify-Storefront-Private-Token": config.privateAccessToken } : { "X-Shopify-Storefront-Access-Token": config.publicAccessToken }
|
|
5888
|
+
};
|
|
5889
|
+
this.logger.info("Initializing Shopify Provider", {
|
|
5890
|
+
storeDomain: this.storeDomain,
|
|
5891
|
+
apiVersion: this.apiVersion,
|
|
5892
|
+
authType: config.privateAccessToken ? "private" : "public"
|
|
5893
|
+
});
|
|
5894
|
+
}
|
|
5895
|
+
// ============================================
|
|
5896
|
+
// GraphQL Client
|
|
5897
|
+
// ============================================
|
|
5898
|
+
async query(gql, variables) {
|
|
5899
|
+
const response = await fetch(this.endpoint, {
|
|
5900
|
+
method: "POST",
|
|
5901
|
+
headers: this.headers,
|
|
5902
|
+
body: JSON.stringify({ query: gql, variables })
|
|
5903
|
+
});
|
|
5904
|
+
if (!response.ok) {
|
|
5905
|
+
throw new ProviderAPIError(
|
|
5906
|
+
`Shopify API error: ${response.status} ${response.statusText}`,
|
|
5907
|
+
"SHOPIFY_API_ERROR",
|
|
5908
|
+
response.status
|
|
5909
|
+
);
|
|
5910
|
+
}
|
|
5911
|
+
const json = await response.json();
|
|
5912
|
+
if (json.errors?.length) {
|
|
5913
|
+
const message = json.errors.map((e) => e.message).join("; ");
|
|
5914
|
+
throw new ProviderAPIError(`Shopify GraphQL error: ${message}`, "SHOPIFY_GRAPHQL_ERROR");
|
|
5915
|
+
}
|
|
5916
|
+
return json.data;
|
|
5917
|
+
}
|
|
5918
|
+
// ============================================
|
|
5919
|
+
// Product Operations
|
|
5920
|
+
// ============================================
|
|
5921
|
+
async getProduct(handle) {
|
|
5922
|
+
const data = await this.query(GET_PRODUCT_BY_HANDLE, { handle });
|
|
5923
|
+
return data.product ? transformProduct2(data.product) : void 0;
|
|
5924
|
+
}
|
|
5925
|
+
async getProducts(options) {
|
|
5926
|
+
const first = options?.first ?? 20;
|
|
5927
|
+
const sortKeyMap = {
|
|
5928
|
+
"title-asc": "TITLE",
|
|
5929
|
+
"title-desc": "TITLE",
|
|
5930
|
+
"price-asc": "PRICE",
|
|
5931
|
+
"price-desc": "PRICE",
|
|
5932
|
+
"best-selling": "BEST_SELLING",
|
|
5933
|
+
"created-desc": "CREATED_AT",
|
|
5934
|
+
"relevance": "RELEVANCE"
|
|
5935
|
+
};
|
|
5936
|
+
const sortKey = options?.sortKey ? sortKeyMap[options.sortKey] ?? "RELEVANCE" : "RELEVANCE";
|
|
5937
|
+
const reverse = options?.sortKey?.endsWith("-desc") ?? false;
|
|
5938
|
+
const data = await this.query(GET_PRODUCTS, {
|
|
5939
|
+
first,
|
|
5940
|
+
query: options?.query,
|
|
5941
|
+
sortKey,
|
|
5942
|
+
reverse
|
|
5943
|
+
});
|
|
5944
|
+
return data.products.edges.map((edge) => transformProduct2(edge.node));
|
|
5945
|
+
}
|
|
5946
|
+
async getProductRecommendations(productId) {
|
|
5947
|
+
const data = await this.query(GET_PRODUCT_RECOMMENDATIONS, {
|
|
5948
|
+
productId
|
|
5949
|
+
});
|
|
5950
|
+
return (data.productRecommendations ?? []).map(transformProduct2);
|
|
5951
|
+
}
|
|
5952
|
+
// ============================================
|
|
5953
|
+
// Collection Operations
|
|
5954
|
+
// ============================================
|
|
5955
|
+
async getCollection(handle) {
|
|
5956
|
+
const data = await this.query(GET_COLLECTION_BY_HANDLE, { handle });
|
|
5957
|
+
return data.collection ? transformCollection2(data.collection) : void 0;
|
|
5958
|
+
}
|
|
5959
|
+
async getCollections(options) {
|
|
5960
|
+
const data = await this.query(GET_COLLECTIONS, {
|
|
5961
|
+
first: options?.limit ?? 50
|
|
5962
|
+
});
|
|
5963
|
+
return data.collections.edges.map((edge) => transformCollection2(edge.node));
|
|
5964
|
+
}
|
|
5965
|
+
async getCollectionProducts(handle, options) {
|
|
5966
|
+
const data = await this.query(
|
|
5967
|
+
GET_COLLECTION_PRODUCTS,
|
|
5968
|
+
{
|
|
5969
|
+
handle,
|
|
5970
|
+
first: options?.first ?? 20,
|
|
5971
|
+
sortKey: options?.sortKey === "price-asc" || options?.sortKey === "price-desc" ? "PRICE" : "BEST_SELLING",
|
|
5972
|
+
reverse: options?.sortKey?.endsWith("-desc") ?? false
|
|
5973
|
+
}
|
|
5974
|
+
);
|
|
5975
|
+
return (data.collection?.products?.edges ?? []).map((edge) => transformProduct2(edge.node));
|
|
5976
|
+
}
|
|
5977
|
+
// ============================================
|
|
5978
|
+
// Category Operations (Shopify does not have native categories)
|
|
5979
|
+
// ============================================
|
|
5980
|
+
async getCategory() {
|
|
5981
|
+
this.logger.warn("Shopify does not support native categories. Use collections instead.");
|
|
5982
|
+
return void 0;
|
|
5983
|
+
}
|
|
5984
|
+
async getCategories() {
|
|
5985
|
+
return [];
|
|
5986
|
+
}
|
|
5987
|
+
async getCategoryProducts() {
|
|
5988
|
+
return [];
|
|
5989
|
+
}
|
|
5990
|
+
// ============================================
|
|
5991
|
+
// Cart Operations
|
|
5992
|
+
// ============================================
|
|
5993
|
+
async createCart() {
|
|
5994
|
+
const data = await this.query(CART_CREATE, {
|
|
5995
|
+
input: {}
|
|
5996
|
+
});
|
|
5997
|
+
if (data.cartCreate.userErrors?.length) {
|
|
5998
|
+
throw new ProviderAPIError(
|
|
5999
|
+
`Cart creation failed: ${data.cartCreate.userErrors.map((e) => e.message).join("; ")}`,
|
|
6000
|
+
"SHOPIFY_CART_ERROR"
|
|
6001
|
+
);
|
|
6002
|
+
}
|
|
6003
|
+
return transformCart2(data.cartCreate.cart);
|
|
6004
|
+
}
|
|
6005
|
+
async getCart(cartId) {
|
|
6006
|
+
const data = await this.query(GET_CART, { cartId });
|
|
6007
|
+
return data.cart ? transformCart2(data.cart) : void 0;
|
|
6008
|
+
}
|
|
6009
|
+
async addToCart(cartId, lines) {
|
|
6010
|
+
const data = await this.query(CART_LINES_ADD, {
|
|
6011
|
+
cartId,
|
|
6012
|
+
lines: lines.map((l) => ({ merchandiseId: l.merchandiseId, quantity: l.quantity }))
|
|
6013
|
+
});
|
|
6014
|
+
if (data.cartLinesAdd.userErrors?.length) {
|
|
6015
|
+
throw new ProviderAPIError(
|
|
6016
|
+
`Add to cart failed: ${data.cartLinesAdd.userErrors.map((e) => e.message).join("; ")}`,
|
|
6017
|
+
"SHOPIFY_CART_ERROR"
|
|
6018
|
+
);
|
|
6019
|
+
}
|
|
6020
|
+
return transformCart2(data.cartLinesAdd.cart);
|
|
6021
|
+
}
|
|
6022
|
+
async removeFromCart(cartId, lineIds) {
|
|
6023
|
+
const data = await this.query(CART_LINES_REMOVE, {
|
|
6024
|
+
cartId,
|
|
6025
|
+
lineIds
|
|
6026
|
+
});
|
|
6027
|
+
if (data.cartLinesRemove.userErrors?.length) {
|
|
6028
|
+
throw new ProviderAPIError(
|
|
6029
|
+
`Remove from cart failed: ${data.cartLinesRemove.userErrors.map((e) => e.message).join("; ")}`,
|
|
6030
|
+
"SHOPIFY_CART_ERROR"
|
|
6031
|
+
);
|
|
6032
|
+
}
|
|
6033
|
+
return transformCart2(data.cartLinesRemove.cart);
|
|
6034
|
+
}
|
|
6035
|
+
async updateCart(cartId, lines) {
|
|
6036
|
+
const data = await this.query(CART_LINES_UPDATE, {
|
|
6037
|
+
cartId,
|
|
6038
|
+
lines: lines.map((l) => ({ id: l.id, quantity: l.quantity }))
|
|
6039
|
+
});
|
|
6040
|
+
if (data.cartLinesUpdate.userErrors?.length) {
|
|
6041
|
+
throw new ProviderAPIError(
|
|
6042
|
+
`Update cart failed: ${data.cartLinesUpdate.userErrors.map((e) => e.message).join("; ")}`,
|
|
6043
|
+
"SHOPIFY_CART_ERROR"
|
|
6044
|
+
);
|
|
6045
|
+
}
|
|
6046
|
+
return transformCart2(data.cartLinesUpdate.cart);
|
|
6047
|
+
}
|
|
6048
|
+
// ============================================
|
|
6049
|
+
// Checkout Operations
|
|
6050
|
+
// ============================================
|
|
6051
|
+
async completeCheckout(_cartId) {
|
|
6052
|
+
throw new ProviderAPIError(
|
|
6053
|
+
"Shopify uses hosted checkout. Redirect the buyer to cart.checkoutUrl instead of calling completeCheckout().",
|
|
6054
|
+
"SHOPIFY_HOSTED_CHECKOUT"
|
|
6055
|
+
);
|
|
6056
|
+
}
|
|
6057
|
+
async getShippingMethods(_cartId) {
|
|
6058
|
+
return [];
|
|
6059
|
+
}
|
|
6060
|
+
async getRegionCountries() {
|
|
6061
|
+
return [];
|
|
6062
|
+
}
|
|
6063
|
+
async addShippingMethod(_cartId, _methodId) {
|
|
6064
|
+
throw new ProviderAPIError(
|
|
6065
|
+
"Shopify handles shipping selection in hosted checkout.",
|
|
6066
|
+
"SHOPIFY_HOSTED_CHECKOUT"
|
|
6067
|
+
);
|
|
6068
|
+
}
|
|
6069
|
+
async initiatePaymentSession(_cartId, _providerId) {
|
|
6070
|
+
throw new ProviderAPIError(
|
|
6071
|
+
"Shopify handles payment in hosted checkout.",
|
|
6072
|
+
"SHOPIFY_HOSTED_CHECKOUT"
|
|
6073
|
+
);
|
|
6074
|
+
}
|
|
6075
|
+
async applyDiscountCode(cartId, code) {
|
|
6076
|
+
const currentCart = await this.getCart(cartId);
|
|
6077
|
+
const existingCodes = [];
|
|
6078
|
+
existingCodes.push(code);
|
|
6079
|
+
const data = await this.query(
|
|
6080
|
+
CART_DISCOUNT_CODES_UPDATE,
|
|
6081
|
+
{ cartId, discountCodes: existingCodes }
|
|
6082
|
+
);
|
|
6083
|
+
if (data.cartDiscountCodesUpdate.userErrors?.length) {
|
|
6084
|
+
throw new ProviderAPIError(
|
|
6085
|
+
`Discount code failed: ${data.cartDiscountCodesUpdate.userErrors.map((e) => e.message).join("; ")}`,
|
|
6086
|
+
"SHOPIFY_DISCOUNT_ERROR"
|
|
6087
|
+
);
|
|
6088
|
+
}
|
|
6089
|
+
return transformCart2(data.cartDiscountCodesUpdate.cart);
|
|
6090
|
+
}
|
|
6091
|
+
async removeDiscountCode(cartId, _code) {
|
|
6092
|
+
const data = await this.query(
|
|
6093
|
+
CART_DISCOUNT_CODES_UPDATE,
|
|
6094
|
+
{ cartId, discountCodes: [] }
|
|
6095
|
+
);
|
|
6096
|
+
return transformCart2(data.cartDiscountCodesUpdate.cart);
|
|
6097
|
+
}
|
|
6098
|
+
// ============================================
|
|
6099
|
+
// Customer Operations
|
|
6100
|
+
// ============================================
|
|
6101
|
+
async getOrders(options) {
|
|
6102
|
+
this.logger.warn("Shopify orders require Customer Account API (planned upgrade path).");
|
|
6103
|
+
return [];
|
|
6104
|
+
}
|
|
6105
|
+
// ============================================
|
|
6106
|
+
// Payment Operations
|
|
6107
|
+
// ============================================
|
|
6108
|
+
async getPaymentMethods(_options) {
|
|
6109
|
+
return [];
|
|
6110
|
+
}
|
|
6111
|
+
// ============================================
|
|
6112
|
+
// Inventory Operations (not available via Storefront API)
|
|
6113
|
+
// ============================================
|
|
6114
|
+
async getStockLocations() {
|
|
6115
|
+
return [];
|
|
6116
|
+
}
|
|
6117
|
+
async checkInventoryLevels() {
|
|
6118
|
+
return [];
|
|
6119
|
+
}
|
|
6120
|
+
async validateCartStock() {
|
|
6121
|
+
return { allAvailable: true, unavailableItems: [] };
|
|
6122
|
+
}
|
|
6123
|
+
async getFulfillmentOptions() {
|
|
6124
|
+
return [];
|
|
6125
|
+
}
|
|
6126
|
+
// ============================================
|
|
6127
|
+
// Admin Operations (not available via Storefront API)
|
|
6128
|
+
// ============================================
|
|
6129
|
+
async cancelOrder(_orderId) {
|
|
6130
|
+
throw new ProviderAPIError(
|
|
6131
|
+
"Order cancellation requires Shopify Admin API (not available in Storefront API).",
|
|
6132
|
+
"SHOPIFY_ADMIN_REQUIRED"
|
|
6133
|
+
);
|
|
6134
|
+
}
|
|
6135
|
+
async createFulfillment(_orderId) {
|
|
6136
|
+
throw new ProviderAPIError(
|
|
6137
|
+
"Fulfillment requires Shopify Admin API.",
|
|
6138
|
+
"SHOPIFY_ADMIN_REQUIRED"
|
|
6139
|
+
);
|
|
6140
|
+
}
|
|
6141
|
+
async createRefund(_orderId) {
|
|
6142
|
+
throw new ProviderAPIError(
|
|
6143
|
+
"Refunds require Shopify Admin API.",
|
|
6144
|
+
"SHOPIFY_ADMIN_REQUIRED"
|
|
6145
|
+
);
|
|
6146
|
+
}
|
|
6147
|
+
async getAdminOrders() {
|
|
6148
|
+
return { orders: [], count: 0 };
|
|
6149
|
+
}
|
|
6150
|
+
// ============================================
|
|
6151
|
+
// Storefront / Multi-Tenant
|
|
6152
|
+
// ============================================
|
|
6153
|
+
async getStorefrontContext() {
|
|
6154
|
+
return {
|
|
6155
|
+
id: this.storeDomain,
|
|
6156
|
+
name: this.storeDomain,
|
|
6157
|
+
platformType: "shopify",
|
|
6158
|
+
requiresProductScoping: false,
|
|
6159
|
+
currencyCode: "USD"
|
|
6160
|
+
};
|
|
6161
|
+
}
|
|
6162
|
+
isStorefrontRequired() {
|
|
6163
|
+
return false;
|
|
6164
|
+
}
|
|
6165
|
+
};
|
|
6166
|
+
|
|
6167
|
+
// src/providers/bigcommerce/transforms.ts
|
|
6168
|
+
function transformMoney3(amount, currencyCode = "USD") {
|
|
6169
|
+
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount ?? 0;
|
|
6170
|
+
return {
|
|
6171
|
+
amount: numAmount.toFixed(2),
|
|
6172
|
+
currencyCode: currencyCode.toUpperCase()
|
|
6173
|
+
};
|
|
6174
|
+
}
|
|
6175
|
+
function transformImage3(url, alt = "") {
|
|
6176
|
+
return { url, altText: alt, width: 800, height: 800 };
|
|
6177
|
+
}
|
|
6178
|
+
function transformProduct3(raw, currencyCode = "USD") {
|
|
6179
|
+
const images = (raw.images ?? []).map(
|
|
6180
|
+
(img) => transformImage3(img.url_standard ?? img.url_thumbnail ?? "", img.description ?? "")
|
|
6181
|
+
);
|
|
6182
|
+
const variants = (raw.variants ?? []).map((v) => ({
|
|
6183
|
+
id: String(v.id),
|
|
6184
|
+
title: v.option_display_name ?? `Variant ${v.id}`,
|
|
6185
|
+
availableForSale: v.purchasing_disabled !== true,
|
|
6186
|
+
price: transformMoney3(v.price ?? raw.price, currencyCode),
|
|
6187
|
+
compareAtPrice: v.sale_price ? transformMoney3(v.sale_price, currencyCode) : void 0,
|
|
6188
|
+
selectedOptions: (v.option_values ?? []).map((ov) => ({
|
|
6189
|
+
name: ov.option_display_name ?? "",
|
|
6190
|
+
value: ov.label ?? ""
|
|
6191
|
+
}))
|
|
6192
|
+
}));
|
|
6193
|
+
const options = (raw.options ?? []).map((opt) => ({
|
|
6194
|
+
id: String(opt.id),
|
|
6195
|
+
name: opt.display_name ?? "",
|
|
6196
|
+
values: (opt.option_values ?? []).map((val) => val.label ?? "")
|
|
6197
|
+
}));
|
|
6198
|
+
const price = raw.price ?? raw.calculated_price?.as_entered ?? 0;
|
|
6199
|
+
return {
|
|
6200
|
+
id: String(raw.id),
|
|
6201
|
+
handle: raw.custom_url?.url?.replace(/^\//, "").replace(/\/$/, "") ?? String(raw.id),
|
|
6202
|
+
title: raw.name ?? "",
|
|
6203
|
+
description: raw.plain_text_description ?? raw.description ?? "",
|
|
6204
|
+
descriptionHtml: raw.description ?? "",
|
|
6205
|
+
availableForSale: raw.availability === "available" || raw.is_visible !== false,
|
|
6206
|
+
featuredImage: images[0] ?? { url: "", altText: "", width: 800, height: 800 },
|
|
6207
|
+
images,
|
|
6208
|
+
options,
|
|
6209
|
+
variants,
|
|
6210
|
+
priceRange: {
|
|
6211
|
+
minVariantPrice: transformMoney3(price, currencyCode),
|
|
6212
|
+
maxVariantPrice: transformMoney3(price, currencyCode)
|
|
6213
|
+
},
|
|
6214
|
+
seo: {
|
|
6215
|
+
title: raw.page_title ?? raw.name ?? "",
|
|
6216
|
+
description: raw.meta_description ?? ""
|
|
6217
|
+
},
|
|
6218
|
+
tags: [],
|
|
6219
|
+
createdAt: raw.date_created ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6220
|
+
updatedAt: raw.date_modified ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
6221
|
+
};
|
|
6222
|
+
}
|
|
6223
|
+
function transformCollection3(raw) {
|
|
6224
|
+
return {
|
|
6225
|
+
id: String(raw.id ?? ""),
|
|
6226
|
+
handle: raw.custom_url?.url?.replace(/^\//, "").replace(/\/$/, "") ?? String(raw.id),
|
|
6227
|
+
title: raw.name ?? "",
|
|
6228
|
+
description: raw.description ?? "",
|
|
6229
|
+
seo: {
|
|
6230
|
+
title: raw.page_title ?? raw.name ?? "",
|
|
6231
|
+
description: raw.meta_description ?? ""
|
|
6232
|
+
},
|
|
6233
|
+
updatedAt: raw.date_modified ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6234
|
+
path: `/collections/${raw.custom_url?.url?.replace(/^\//, "").replace(/\/$/, "") ?? raw.id}`
|
|
6235
|
+
};
|
|
6236
|
+
}
|
|
6237
|
+
function transformCart3(raw, currencyCode = "USD") {
|
|
6238
|
+
const lineItems = Object.values(raw.line_items ?? {}).flat();
|
|
6239
|
+
const lines = lineItems.map((item) => ({
|
|
6240
|
+
id: item.id,
|
|
6241
|
+
quantity: item.quantity ?? 1,
|
|
6242
|
+
cost: {
|
|
6243
|
+
totalAmount: transformMoney3(item.extended_sale_price ?? item.sale_price ?? 0, currencyCode)
|
|
6244
|
+
},
|
|
6245
|
+
merchandise: {
|
|
6246
|
+
id: String(item.variant_id ?? item.product_id),
|
|
6247
|
+
title: item.name ?? "",
|
|
6248
|
+
selectedOptions: (item.options ?? []).map((opt) => ({
|
|
6249
|
+
name: opt.name ?? "",
|
|
6250
|
+
value: opt.value ?? ""
|
|
6251
|
+
})),
|
|
6252
|
+
product: {
|
|
6253
|
+
id: String(item.product_id),
|
|
6254
|
+
handle: String(item.product_id),
|
|
6255
|
+
title: item.name ?? "",
|
|
6256
|
+
featuredImage: item.image_url ? transformImage3(item.image_url) : { url: "", altText: "", width: 800, height: 800 }
|
|
6257
|
+
}
|
|
6258
|
+
}
|
|
6259
|
+
}));
|
|
6260
|
+
return {
|
|
6261
|
+
id: raw.id,
|
|
6262
|
+
checkoutUrl: raw.redirect_urls?.checkout_url ?? "",
|
|
6263
|
+
totalQuantity: lines.reduce((sum, l) => sum + l.quantity, 0),
|
|
6264
|
+
cost: {
|
|
6265
|
+
subtotalAmount: transformMoney3(raw.base_amount ?? 0, currencyCode),
|
|
6266
|
+
totalAmount: transformMoney3(raw.cart_amount ?? 0, currencyCode)
|
|
6267
|
+
},
|
|
6268
|
+
lines
|
|
6269
|
+
};
|
|
6270
|
+
}
|
|
6271
|
+
|
|
6272
|
+
// src/providers/bigcommerce/index.ts
|
|
6273
|
+
var BigCommerceProvider = class {
|
|
6274
|
+
name = "bigcommerce";
|
|
6275
|
+
version = "1.0.0";
|
|
6276
|
+
capabilities = BIGCOMMERCE_CAPABILITIES;
|
|
6277
|
+
logger;
|
|
6278
|
+
storefrontApiUrl;
|
|
6279
|
+
storeHash;
|
|
6280
|
+
channelId;
|
|
6281
|
+
currencyCode;
|
|
6282
|
+
headers;
|
|
6283
|
+
constructor(config) {
|
|
6284
|
+
this.logger = createLogger("BigCommerceProvider", config.debug);
|
|
6285
|
+
this.storefrontApiUrl = config.storefrontApiUrl.replace(/\/$/, "");
|
|
6286
|
+
this.storeHash = config.storeHash;
|
|
6287
|
+
this.channelId = config.channelId;
|
|
6288
|
+
this.currencyCode = config.currencyCode ?? "USD";
|
|
6289
|
+
this.headers = {
|
|
6290
|
+
"Content-Type": "application/json",
|
|
6291
|
+
"Authorization": `Bearer ${config.storefrontApiToken}`
|
|
6292
|
+
};
|
|
6293
|
+
this.logger.info("Initializing BigCommerce Provider", { storeHash: this.storeHash, channelId: this.channelId });
|
|
6294
|
+
}
|
|
6295
|
+
async fetchApi(path, init) {
|
|
6296
|
+
const url = `${this.storefrontApiUrl}${path}`;
|
|
6297
|
+
const response = await fetch(url, {
|
|
6298
|
+
...init,
|
|
6299
|
+
headers: { ...this.headers, ...init?.headers }
|
|
6300
|
+
});
|
|
6301
|
+
if (!response.ok) {
|
|
6302
|
+
throw new ProviderAPIError(
|
|
6303
|
+
`BigCommerce API error: ${response.status} ${response.statusText}`,
|
|
6304
|
+
"BIGCOMMERCE_API_ERROR",
|
|
6305
|
+
response.status
|
|
6306
|
+
);
|
|
6307
|
+
}
|
|
6308
|
+
return response.json();
|
|
6309
|
+
}
|
|
6310
|
+
// ============================================
|
|
6311
|
+
// Product Operations
|
|
6312
|
+
// ============================================
|
|
6313
|
+
async getProduct(handle) {
|
|
6314
|
+
try {
|
|
6315
|
+
const data = await this.fetchApi(`/api/storefront/products?include=variants,options,images&keyword=${encodeURIComponent(handle)}`);
|
|
6316
|
+
const product = data.data?.[0];
|
|
6317
|
+
return product ? transformProduct3(product, this.currencyCode) : void 0;
|
|
6318
|
+
} catch {
|
|
6319
|
+
return void 0;
|
|
6320
|
+
}
|
|
6321
|
+
}
|
|
6322
|
+
async getProducts(options) {
|
|
6323
|
+
const limit = options?.first ?? 20;
|
|
6324
|
+
let path = `/api/storefront/products?include=variants,options,images&limit=${limit}`;
|
|
6325
|
+
if (options?.query) path += `&keyword=${encodeURIComponent(options.query)}`;
|
|
6326
|
+
const data = await this.fetchApi(path);
|
|
6327
|
+
return (data.data ?? []).map((p) => transformProduct3(p, this.currencyCode));
|
|
6328
|
+
}
|
|
6329
|
+
async getProductRecommendations(_productId) {
|
|
6330
|
+
return [];
|
|
6331
|
+
}
|
|
6332
|
+
// ============================================
|
|
6333
|
+
// Collection Operations (BigCommerce Categories)
|
|
6334
|
+
// ============================================
|
|
6335
|
+
async getCollection(handle) {
|
|
6336
|
+
try {
|
|
6337
|
+
const data = await this.fetchApi(`/api/storefront/categories?url=/${handle}/`);
|
|
6338
|
+
const cat = data.data?.[0];
|
|
6339
|
+
return cat ? transformCollection3(cat) : void 0;
|
|
6340
|
+
} catch {
|
|
6341
|
+
return void 0;
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
async getCollections(options) {
|
|
6345
|
+
const limit = options?.limit ?? 50;
|
|
6346
|
+
const data = await this.fetchApi(`/api/storefront/categories?limit=${limit}`);
|
|
6347
|
+
return (data.data ?? []).map(transformCollection3);
|
|
6348
|
+
}
|
|
6349
|
+
async getCollectionProducts(handle, options) {
|
|
6350
|
+
const limit = options?.first ?? 20;
|
|
6351
|
+
const data = await this.fetchApi(
|
|
6352
|
+
`/api/storefront/products?include=variants,options,images&categories:in=${handle}&limit=${limit}`
|
|
6353
|
+
);
|
|
6354
|
+
return (data.data ?? []).map((p) => transformProduct3(p, this.currencyCode));
|
|
6355
|
+
}
|
|
6356
|
+
// ============================================
|
|
6357
|
+
// Category Operations
|
|
6358
|
+
// ============================================
|
|
6359
|
+
async getCategory(idOrHandle) {
|
|
6360
|
+
return this.getCollection(idOrHandle);
|
|
6361
|
+
}
|
|
6362
|
+
async getCategories(options) {
|
|
6363
|
+
return this.getCollections(options);
|
|
6364
|
+
}
|
|
6365
|
+
async getCategoryProducts(handle, options) {
|
|
6366
|
+
return this.getCollectionProducts(handle, options);
|
|
6367
|
+
}
|
|
6368
|
+
// ============================================
|
|
6369
|
+
// Cart Operations
|
|
6370
|
+
// ============================================
|
|
6371
|
+
async createCart() {
|
|
6372
|
+
const data = await this.fetchApi("/api/storefront/carts", {
|
|
6373
|
+
method: "POST",
|
|
6374
|
+
body: JSON.stringify({ line_items: [] })
|
|
6375
|
+
});
|
|
6376
|
+
return transformCart3(data.data ?? data, this.currencyCode);
|
|
6377
|
+
}
|
|
6378
|
+
async getCart(cartId) {
|
|
6379
|
+
try {
|
|
6380
|
+
const data = await this.fetchApi(`/api/storefront/carts/${cartId}?include=line_items.physical_items.options`);
|
|
6381
|
+
return transformCart3(data.data ?? data, this.currencyCode);
|
|
6382
|
+
} catch {
|
|
6383
|
+
return void 0;
|
|
6384
|
+
}
|
|
6385
|
+
}
|
|
6386
|
+
async addToCart(cartId, lines) {
|
|
6387
|
+
const lineItems = lines.map((l) => ({
|
|
6388
|
+
product_id: parseInt(l.merchandiseId, 10),
|
|
6389
|
+
quantity: l.quantity
|
|
6390
|
+
}));
|
|
6391
|
+
const data = await this.fetchApi(`/api/storefront/carts/${cartId}/items`, {
|
|
6392
|
+
method: "POST",
|
|
6393
|
+
body: JSON.stringify({ line_items: lineItems })
|
|
6394
|
+
});
|
|
6395
|
+
return transformCart3(data.data ?? data, this.currencyCode);
|
|
6396
|
+
}
|
|
6397
|
+
async removeFromCart(cartId, lineIds) {
|
|
6398
|
+
for (const lineId of lineIds) {
|
|
6399
|
+
await this.fetchApi(`/api/storefront/carts/${cartId}/items/${lineId}`, { method: "DELETE" });
|
|
6400
|
+
}
|
|
6401
|
+
const cart = await this.getCart(cartId);
|
|
6402
|
+
if (!cart) throw new ProviderAPIError("Cart not found after removal", "BIGCOMMERCE_CART_ERROR");
|
|
6403
|
+
return cart;
|
|
6404
|
+
}
|
|
6405
|
+
async updateCart(cartId, lines) {
|
|
6406
|
+
for (const line of lines) {
|
|
6407
|
+
await this.fetchApi(`/api/storefront/carts/${cartId}/items/${line.id}`, {
|
|
6408
|
+
method: "PUT",
|
|
6409
|
+
body: JSON.stringify({ line_item: { quantity: line.quantity } })
|
|
6410
|
+
});
|
|
6411
|
+
}
|
|
6412
|
+
const cart = await this.getCart(cartId);
|
|
6413
|
+
if (!cart) throw new ProviderAPIError("Cart not found after update", "BIGCOMMERCE_CART_ERROR");
|
|
6414
|
+
return cart;
|
|
6415
|
+
}
|
|
6416
|
+
// ============================================
|
|
6417
|
+
// Checkout Operations (Hosted)
|
|
6418
|
+
// ============================================
|
|
6419
|
+
async completeCheckout(_cartId) {
|
|
6420
|
+
throw new ProviderAPIError(
|
|
6421
|
+
"BigCommerce uses Embedded Checkout. Redirect the buyer to cart.checkoutUrl.",
|
|
6422
|
+
"BIGCOMMERCE_HOSTED_CHECKOUT"
|
|
6423
|
+
);
|
|
6424
|
+
}
|
|
6425
|
+
async getShippingMethods(_cartId) {
|
|
6426
|
+
return [];
|
|
6427
|
+
}
|
|
6428
|
+
async getRegionCountries() {
|
|
6429
|
+
return [];
|
|
6430
|
+
}
|
|
6431
|
+
async addShippingMethod(_cartId, _methodId) {
|
|
6432
|
+
throw new ProviderAPIError("BigCommerce handles shipping in Embedded Checkout.", "BIGCOMMERCE_HOSTED_CHECKOUT");
|
|
6433
|
+
}
|
|
6434
|
+
async initiatePaymentSession(_cartId, _providerId) {
|
|
6435
|
+
throw new ProviderAPIError("BigCommerce handles payment in Embedded Checkout.", "BIGCOMMERCE_HOSTED_CHECKOUT");
|
|
6436
|
+
}
|
|
6437
|
+
async applyDiscountCode(cartId, code) {
|
|
6438
|
+
const data = await this.fetchApi(`/api/storefront/carts/${cartId}/coupons`, {
|
|
6439
|
+
method: "POST",
|
|
6440
|
+
body: JSON.stringify({ coupon_code: code })
|
|
6441
|
+
});
|
|
6442
|
+
return transformCart3(data.data ?? data, this.currencyCode);
|
|
6443
|
+
}
|
|
6444
|
+
async removeDiscountCode(cartId, code) {
|
|
6445
|
+
await this.fetchApi(`/api/storefront/carts/${cartId}/coupons/${encodeURIComponent(code)}`, { method: "DELETE" });
|
|
6446
|
+
const cart = await this.getCart(cartId);
|
|
6447
|
+
if (!cart) throw new ProviderAPIError("Cart not found after coupon removal", "BIGCOMMERCE_CART_ERROR");
|
|
6448
|
+
return cart;
|
|
6449
|
+
}
|
|
6450
|
+
// ============================================
|
|
6451
|
+
// Customer, Order, Payment, Inventory, Admin
|
|
6452
|
+
// ============================================
|
|
6453
|
+
async getOrders(_options) {
|
|
6454
|
+
return [];
|
|
6455
|
+
}
|
|
6456
|
+
async getPaymentMethods(_options) {
|
|
6457
|
+
return [];
|
|
6458
|
+
}
|
|
6459
|
+
async getStockLocations() {
|
|
6460
|
+
return [];
|
|
6461
|
+
}
|
|
6462
|
+
async checkInventoryLevels() {
|
|
6463
|
+
return [];
|
|
6464
|
+
}
|
|
6465
|
+
async validateCartStock() {
|
|
6466
|
+
return { allAvailable: true, unavailableItems: [] };
|
|
6467
|
+
}
|
|
6468
|
+
async getFulfillmentOptions() {
|
|
6469
|
+
return [];
|
|
6470
|
+
}
|
|
6471
|
+
async cancelOrder(_orderId) {
|
|
6472
|
+
throw new ProviderAPIError("Order cancellation requires BigCommerce Management API.", "BIGCOMMERCE_ADMIN_REQUIRED");
|
|
6473
|
+
}
|
|
6474
|
+
async createFulfillment(_orderId) {
|
|
6475
|
+
throw new ProviderAPIError("Fulfillment requires BigCommerce Management API.", "BIGCOMMERCE_ADMIN_REQUIRED");
|
|
6476
|
+
}
|
|
6477
|
+
async createRefund(_orderId) {
|
|
6478
|
+
throw new ProviderAPIError("Refunds require BigCommerce Management API.", "BIGCOMMERCE_ADMIN_REQUIRED");
|
|
6479
|
+
}
|
|
6480
|
+
async getAdminOrders() {
|
|
6481
|
+
return { orders: [], count: 0 };
|
|
6482
|
+
}
|
|
6483
|
+
async getStorefrontContext() {
|
|
6484
|
+
return {
|
|
6485
|
+
id: this.channelId,
|
|
6486
|
+
name: `BigCommerce Store ${this.storeHash}`,
|
|
6487
|
+
platformType: "bigcommerce",
|
|
6488
|
+
requiresProductScoping: false,
|
|
6489
|
+
currencyCode: this.currencyCode
|
|
6490
|
+
};
|
|
6491
|
+
}
|
|
6492
|
+
isStorefrontRequired() {
|
|
6493
|
+
return false;
|
|
6494
|
+
}
|
|
6495
|
+
};
|
|
6496
|
+
|
|
6497
|
+
// src/providers/woocommerce/transforms.ts
|
|
6498
|
+
function transformMoney4(amount, currencyCode = "USD") {
|
|
6499
|
+
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount ?? 0;
|
|
6500
|
+
return {
|
|
6501
|
+
amount: isNaN(numAmount) ? "0.00" : numAmount.toFixed(2),
|
|
6502
|
+
currencyCode: currencyCode.toUpperCase()
|
|
6503
|
+
};
|
|
6504
|
+
}
|
|
6505
|
+
function transformImage4(url, alt = "") {
|
|
6506
|
+
return { url, altText: alt, width: 800, height: 800 };
|
|
6507
|
+
}
|
|
6508
|
+
function transformProduct4(raw, currencyCode = "USD") {
|
|
6509
|
+
const images = (raw.images ?? []).map(
|
|
6510
|
+
(img) => transformImage4(img.src ?? "", img.alt ?? img.name ?? "")
|
|
6511
|
+
);
|
|
6512
|
+
const variants = (raw.variations ?? []).map((v) => ({
|
|
6513
|
+
id: String(v.id),
|
|
6514
|
+
title: v.name ?? "",
|
|
6515
|
+
availableForSale: v.in_stock !== false,
|
|
6516
|
+
price: transformMoney4(v.prices?.price ?? v.price, currencyCode),
|
|
6517
|
+
compareAtPrice: v.prices?.regular_price ? transformMoney4(v.prices.regular_price, currencyCode) : void 0,
|
|
6518
|
+
selectedOptions: (v.attributes ?? []).map((attr) => ({
|
|
6519
|
+
name: attr.name ?? "",
|
|
6520
|
+
value: attr.value ?? ""
|
|
6521
|
+
}))
|
|
6522
|
+
}));
|
|
6523
|
+
const options = (raw.attributes ?? []).filter((attr) => attr.variation === true || attr.has_options === true).map((attr) => ({
|
|
6524
|
+
id: String(attr.id),
|
|
6525
|
+
name: attr.name ?? "",
|
|
6526
|
+
values: (attr.terms ?? attr.options ?? []).map(
|
|
6527
|
+
(t) => typeof t === "string" ? t : t.name ?? t.value ?? ""
|
|
6528
|
+
)
|
|
6529
|
+
}));
|
|
6530
|
+
const priceMinor = raw.prices?.price ?? "0";
|
|
6531
|
+
const priceDecimalPlaces = raw.prices?.currency_minor_unit ?? 2;
|
|
6532
|
+
const priceMajor = parseInt(priceMinor, 10) / Math.pow(10, priceDecimalPlaces);
|
|
6533
|
+
return {
|
|
6534
|
+
id: String(raw.id),
|
|
6535
|
+
handle: raw.slug ?? String(raw.id),
|
|
6536
|
+
title: raw.name ?? "",
|
|
6537
|
+
description: raw.short_description ?? raw.description ?? "",
|
|
6538
|
+
descriptionHtml: raw.description ?? "",
|
|
6539
|
+
availableForSale: raw.is_purchasable !== false && raw.is_in_stock !== false,
|
|
6540
|
+
featuredImage: images[0] ?? { url: "", altText: "", width: 800, height: 800 },
|
|
6541
|
+
images,
|
|
6542
|
+
options,
|
|
6543
|
+
variants,
|
|
6544
|
+
priceRange: {
|
|
6545
|
+
minVariantPrice: transformMoney4(priceMajor, currencyCode),
|
|
6546
|
+
maxVariantPrice: transformMoney4(priceMajor, currencyCode)
|
|
6547
|
+
},
|
|
6548
|
+
seo: {
|
|
6549
|
+
title: raw.name ?? "",
|
|
6550
|
+
description: raw.short_description?.replace(/<[^>]*>/g, "") ?? ""
|
|
6551
|
+
},
|
|
6552
|
+
tags: [],
|
|
6553
|
+
createdAt: raw.date_created ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6554
|
+
updatedAt: raw.date_modified ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
6555
|
+
};
|
|
6556
|
+
}
|
|
6557
|
+
function transformCollection4(raw) {
|
|
6558
|
+
return {
|
|
6559
|
+
id: String(raw.id ?? ""),
|
|
6560
|
+
handle: raw.slug ?? String(raw.id),
|
|
6561
|
+
title: raw.name ?? "",
|
|
6562
|
+
description: raw.description ?? "",
|
|
6563
|
+
seo: {
|
|
6564
|
+
title: raw.name ?? "",
|
|
6565
|
+
description: raw.description?.replace(/<[^>]*>/g, "") ?? ""
|
|
6566
|
+
},
|
|
6567
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6568
|
+
path: `/collections/${raw.slug ?? raw.id}`
|
|
6569
|
+
};
|
|
6570
|
+
}
|
|
6571
|
+
function transformCart4(raw, currencyCode = "USD") {
|
|
6572
|
+
const lines = (raw.items ?? []).map((item) => {
|
|
6573
|
+
const minorUnit2 = item.prices?.currency_minor_unit ?? 2;
|
|
6574
|
+
const divisor2 = Math.pow(10, minorUnit2);
|
|
6575
|
+
const lineTotal = parseInt(item.totals?.line_total ?? "0", 10) / divisor2;
|
|
6576
|
+
return {
|
|
6577
|
+
id: item.key ?? String(item.id),
|
|
6578
|
+
quantity: item.quantity ?? 1,
|
|
6579
|
+
cost: {
|
|
6580
|
+
totalAmount: transformMoney4(lineTotal, currencyCode)
|
|
6581
|
+
},
|
|
6582
|
+
merchandise: {
|
|
6583
|
+
id: String(item.id),
|
|
6584
|
+
title: item.name ?? "",
|
|
6585
|
+
selectedOptions: (item.variation ?? []).map((v) => ({
|
|
6586
|
+
name: v.attribute ?? "",
|
|
6587
|
+
value: v.value ?? ""
|
|
6588
|
+
})),
|
|
6589
|
+
product: {
|
|
6590
|
+
id: String(item.id),
|
|
6591
|
+
handle: item.slug ?? String(item.id),
|
|
6592
|
+
title: item.name ?? "",
|
|
6593
|
+
featuredImage: item.images?.[0] ? transformImage4(item.images[0].src, item.images[0].alt ?? "") : { url: "", altText: "", width: 800, height: 800 }
|
|
6594
|
+
}
|
|
6595
|
+
}
|
|
6596
|
+
};
|
|
6597
|
+
});
|
|
6598
|
+
const minorUnit = raw.totals?.currency_minor_unit ?? 2;
|
|
6599
|
+
const divisor = Math.pow(10, minorUnit);
|
|
6600
|
+
const subtotal = parseInt(raw.totals?.total_items ?? "0", 10) / divisor;
|
|
6601
|
+
const total = parseInt(raw.totals?.total_price ?? "0", 10) / divisor;
|
|
6602
|
+
return {
|
|
6603
|
+
id: raw.cart_key ?? void 0,
|
|
6604
|
+
checkoutUrl: "",
|
|
6605
|
+
// WooCommerce handles checkout via its own /checkout page
|
|
6606
|
+
totalQuantity: raw.items_count ?? lines.reduce((sum, l) => sum + l.quantity, 0),
|
|
6607
|
+
cost: {
|
|
6608
|
+
subtotalAmount: transformMoney4(subtotal, currencyCode),
|
|
6609
|
+
totalAmount: transformMoney4(total, currencyCode)
|
|
6610
|
+
},
|
|
6611
|
+
lines
|
|
6612
|
+
};
|
|
6613
|
+
}
|
|
6614
|
+
function transformWooAddress(raw) {
|
|
6615
|
+
return {
|
|
6616
|
+
id: void 0,
|
|
6617
|
+
firstName: raw.first_name ?? "",
|
|
6618
|
+
lastName: raw.last_name ?? "",
|
|
6619
|
+
address1: raw.address_1 ?? "",
|
|
6620
|
+
address2: raw.address_2 ?? "",
|
|
6621
|
+
city: raw.city ?? "",
|
|
6622
|
+
province: raw.state ?? "",
|
|
6623
|
+
countryCode: raw.country ?? "",
|
|
6624
|
+
postalCode: raw.postcode ?? "",
|
|
6625
|
+
phone: raw.phone ?? "",
|
|
6626
|
+
company: raw.company ?? ""
|
|
6627
|
+
};
|
|
6628
|
+
}
|
|
6629
|
+
function transformOrder4(raw, currencyCode = "USD") {
|
|
6630
|
+
const items = (raw.line_items ?? []).map((item) => ({
|
|
6631
|
+
id: String(item.id),
|
|
6632
|
+
title: item.name ?? "",
|
|
6633
|
+
quantity: item.quantity ?? 1,
|
|
6634
|
+
price: transformMoney4(item.price ?? 0, currencyCode),
|
|
6635
|
+
total: transformMoney4(item.total ?? 0, currencyCode),
|
|
6636
|
+
productId: String(item.product_id),
|
|
6637
|
+
variantId: item.variation_id ? String(item.variation_id) : void 0
|
|
6638
|
+
}));
|
|
6639
|
+
const statusMap = {
|
|
6640
|
+
pending: "pending",
|
|
6641
|
+
processing: "processing",
|
|
6642
|
+
"on-hold": "pending",
|
|
6643
|
+
completed: "completed",
|
|
6644
|
+
cancelled: "cancelled",
|
|
6645
|
+
refunded: "refunded",
|
|
6646
|
+
failed: "cancelled"
|
|
6647
|
+
};
|
|
6648
|
+
const paymentMap = {
|
|
6649
|
+
pending: "pending",
|
|
6650
|
+
processing: "authorized",
|
|
6651
|
+
completed: "paid",
|
|
6652
|
+
refunded: "refunded"
|
|
6653
|
+
};
|
|
6654
|
+
return {
|
|
6655
|
+
id: String(raw.id),
|
|
6656
|
+
orderNumber: String(raw.number ?? raw.id),
|
|
6657
|
+
email: raw.billing?.email ?? "",
|
|
6658
|
+
status: statusMap[raw.status] ?? "pending",
|
|
6659
|
+
paymentStatus: paymentMap[raw.status] ?? "pending",
|
|
6660
|
+
fulfillmentStatus: raw.status === "completed" ? "fulfilled" : "unfulfilled",
|
|
6661
|
+
items,
|
|
6662
|
+
total: transformMoney4(raw.total ?? 0, currencyCode),
|
|
6663
|
+
subtotal: transformMoney4(raw.subtotal ?? 0, currencyCode),
|
|
6664
|
+
tax: transformMoney4(raw.total_tax ?? 0, currencyCode),
|
|
6665
|
+
shipping: transformMoney4(raw.shipping_total ?? 0, currencyCode),
|
|
6666
|
+
shippingAddress: raw.shipping ? transformWooAddress(raw.shipping) : void 0,
|
|
6667
|
+
billingAddress: raw.billing ? transformWooAddress(raw.billing) : void 0,
|
|
6668
|
+
createdAt: raw.date_created ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6669
|
+
updatedAt: raw.date_modified ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
6670
|
+
};
|
|
6671
|
+
}
|
|
6672
|
+
|
|
6673
|
+
// src/providers/woocommerce/index.ts
|
|
6674
|
+
var WooCommerceProvider = class {
|
|
6675
|
+
name = "woocommerce";
|
|
6676
|
+
version = "1.0.0";
|
|
6677
|
+
capabilities = WOOCOMMERCE_CAPABILITIES;
|
|
6678
|
+
logger;
|
|
6679
|
+
baseUrl;
|
|
6680
|
+
storeApiBase;
|
|
6681
|
+
restApiBase;
|
|
6682
|
+
currencyCode;
|
|
6683
|
+
authHeader;
|
|
6684
|
+
constructor(config) {
|
|
6685
|
+
this.logger = createLogger("WooCommerceProvider", config.debug);
|
|
6686
|
+
this.baseUrl = config.url.replace(/\/$/, "");
|
|
6687
|
+
this.storeApiBase = `${this.baseUrl}/wp-json/wc/store/v1`;
|
|
6688
|
+
this.restApiBase = `${this.baseUrl}/wp-json/wc/v3`;
|
|
6689
|
+
this.currencyCode = config.currencyCode ?? "USD";
|
|
6690
|
+
if (config.consumerKey && config.consumerSecret) {
|
|
6691
|
+
this.authHeader = "Basic " + btoa(`${config.consumerKey}:${config.consumerSecret}`);
|
|
6692
|
+
}
|
|
6693
|
+
this.logger.info("Initializing WooCommerce Provider", { baseUrl: this.baseUrl });
|
|
6694
|
+
}
|
|
6695
|
+
async storeApiFetch(path, init) {
|
|
6696
|
+
const response = await fetch(`${this.storeApiBase}${path}`, {
|
|
6697
|
+
...init,
|
|
6698
|
+
headers: { "Content-Type": "application/json", ...init?.headers }
|
|
6699
|
+
});
|
|
6700
|
+
if (!response.ok) {
|
|
6701
|
+
throw new ProviderAPIError(`WooCommerce Store API error: ${response.status}`, "WOOCOMMERCE_API_ERROR", response.status);
|
|
6702
|
+
}
|
|
6703
|
+
return response.json();
|
|
6704
|
+
}
|
|
6705
|
+
async restApiFetch(path, init) {
|
|
6706
|
+
if (!this.authHeader) {
|
|
6707
|
+
throw new ProviderAPIError("WooCommerce REST API requires consumer key/secret.", "WOOCOMMERCE_AUTH_REQUIRED");
|
|
6708
|
+
}
|
|
6709
|
+
const response = await fetch(`${this.restApiBase}${path}`, {
|
|
6710
|
+
...init,
|
|
6711
|
+
headers: { "Content-Type": "application/json", "Authorization": this.authHeader, ...init?.headers }
|
|
6712
|
+
});
|
|
6713
|
+
if (!response.ok) {
|
|
6714
|
+
throw new ProviderAPIError(`WooCommerce REST API error: ${response.status}`, "WOOCOMMERCE_API_ERROR", response.status);
|
|
6715
|
+
}
|
|
6716
|
+
return response.json();
|
|
6717
|
+
}
|
|
6718
|
+
// ============================================
|
|
6719
|
+
// Product Operations (Store API)
|
|
6720
|
+
// ============================================
|
|
6721
|
+
async getProduct(handle) {
|
|
6722
|
+
try {
|
|
6723
|
+
const products = await this.storeApiFetch(`/products?slug=${encodeURIComponent(handle)}`);
|
|
6724
|
+
return products[0] ? transformProduct4(products[0], this.currencyCode) : void 0;
|
|
6725
|
+
} catch {
|
|
6726
|
+
return void 0;
|
|
6727
|
+
}
|
|
6728
|
+
}
|
|
6729
|
+
async getProducts(options) {
|
|
6730
|
+
let path = `/products?per_page=${options?.first ?? 20}`;
|
|
6731
|
+
if (options?.query) path += `&search=${encodeURIComponent(options.query)}`;
|
|
6732
|
+
if (options?.sortKey) {
|
|
6733
|
+
const [orderby, order] = options.sortKey.includes("-desc") ? [options.sortKey.replace("-desc", ""), "desc"] : [options.sortKey.replace("-asc", ""), "asc"];
|
|
6734
|
+
path += `&orderby=${orderby}&order=${order}`;
|
|
6735
|
+
}
|
|
6736
|
+
const products = await this.storeApiFetch(path);
|
|
6737
|
+
return products.map((p) => transformProduct4(p, this.currencyCode));
|
|
6738
|
+
}
|
|
6739
|
+
async getProductRecommendations(_productId) {
|
|
6740
|
+
return [];
|
|
6741
|
+
}
|
|
6742
|
+
// ============================================
|
|
6743
|
+
// Collection/Category Operations (Store API)
|
|
6744
|
+
// ============================================
|
|
6745
|
+
async getCollection(handle) {
|
|
6746
|
+
try {
|
|
6747
|
+
const cats = await this.storeApiFetch(`/products/categories?slug=${encodeURIComponent(handle)}`);
|
|
6748
|
+
return cats[0] ? transformCollection4(cats[0]) : void 0;
|
|
6749
|
+
} catch {
|
|
6750
|
+
return void 0;
|
|
6751
|
+
}
|
|
6752
|
+
}
|
|
6753
|
+
async getCollections(options) {
|
|
6754
|
+
const cats = await this.storeApiFetch(`/products/categories?per_page=${options?.limit ?? 50}`);
|
|
6755
|
+
return cats.map(transformCollection4);
|
|
6756
|
+
}
|
|
6757
|
+
async getCollectionProducts(handle, options) {
|
|
6758
|
+
const products = await this.storeApiFetch(
|
|
6759
|
+
`/products?category=${encodeURIComponent(handle)}&per_page=${options?.first ?? 20}`
|
|
6760
|
+
);
|
|
6761
|
+
return products.map((p) => transformProduct4(p, this.currencyCode));
|
|
6762
|
+
}
|
|
6763
|
+
async getCategory(idOrHandle) {
|
|
6764
|
+
return this.getCollection(idOrHandle);
|
|
6765
|
+
}
|
|
6766
|
+
async getCategories(options) {
|
|
6767
|
+
return this.getCollections(options);
|
|
6768
|
+
}
|
|
6769
|
+
async getCategoryProducts(handle, options) {
|
|
6770
|
+
return this.getCollectionProducts(handle, options);
|
|
6771
|
+
}
|
|
6772
|
+
// ============================================
|
|
6773
|
+
// Cart Operations (Store API)
|
|
6774
|
+
// ============================================
|
|
6775
|
+
async createCart() {
|
|
6776
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6777
|
+
return transformCart4(cart, this.currencyCode);
|
|
6778
|
+
}
|
|
6779
|
+
async getCart(_cartId) {
|
|
6780
|
+
try {
|
|
6781
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6782
|
+
return transformCart4(cart, this.currencyCode);
|
|
6783
|
+
} catch {
|
|
6784
|
+
return void 0;
|
|
6785
|
+
}
|
|
6786
|
+
}
|
|
6787
|
+
async addToCart(_cartId, lines) {
|
|
6788
|
+
for (const line of lines) {
|
|
6789
|
+
await this.storeApiFetch("/cart/add-item", {
|
|
6790
|
+
method: "POST",
|
|
6791
|
+
body: JSON.stringify({ id: parseInt(line.merchandiseId, 10), quantity: line.quantity })
|
|
6792
|
+
});
|
|
6793
|
+
}
|
|
6794
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6795
|
+
return transformCart4(cart, this.currencyCode);
|
|
6796
|
+
}
|
|
6797
|
+
async removeFromCart(_cartId, lineIds) {
|
|
6798
|
+
for (const key of lineIds) {
|
|
6799
|
+
await this.storeApiFetch("/cart/remove-item", {
|
|
6800
|
+
method: "POST",
|
|
6801
|
+
body: JSON.stringify({ key })
|
|
6802
|
+
});
|
|
6803
|
+
}
|
|
6804
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6805
|
+
return transformCart4(cart, this.currencyCode);
|
|
6806
|
+
}
|
|
6807
|
+
async updateCart(_cartId, lines) {
|
|
6808
|
+
for (const line of lines) {
|
|
6809
|
+
await this.storeApiFetch("/cart/update-item", {
|
|
6810
|
+
method: "POST",
|
|
6811
|
+
body: JSON.stringify({ key: line.id, quantity: line.quantity })
|
|
6812
|
+
});
|
|
6813
|
+
}
|
|
6814
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6815
|
+
return transformCart4(cart, this.currencyCode);
|
|
6816
|
+
}
|
|
6817
|
+
// ============================================
|
|
6818
|
+
// Checkout Operations (Store API)
|
|
6819
|
+
// ============================================
|
|
6820
|
+
async completeCheckout(_cartId) {
|
|
6821
|
+
const result = await this.storeApiFetch("/checkout", { method: "POST", body: JSON.stringify({}) });
|
|
6822
|
+
return transformOrder4(result, this.currencyCode);
|
|
6823
|
+
}
|
|
6824
|
+
async getShippingMethods(_cartId) {
|
|
6825
|
+
return [];
|
|
6826
|
+
}
|
|
6827
|
+
async getRegionCountries() {
|
|
6828
|
+
return [];
|
|
6829
|
+
}
|
|
6830
|
+
async addShippingMethod(_cartId, methodId) {
|
|
6831
|
+
await this.storeApiFetch("/cart/select-shipping-rate", {
|
|
6832
|
+
method: "POST",
|
|
6833
|
+
body: JSON.stringify({ rate_id: methodId })
|
|
6834
|
+
});
|
|
6835
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6836
|
+
return transformCart4(cart, this.currencyCode);
|
|
6837
|
+
}
|
|
6838
|
+
async initiatePaymentSession(_cartId, _providerId) {
|
|
6839
|
+
}
|
|
6840
|
+
async applyDiscountCode(_cartId, code) {
|
|
6841
|
+
await this.storeApiFetch("/cart/apply-coupon", {
|
|
6842
|
+
method: "POST",
|
|
6843
|
+
body: JSON.stringify({ code })
|
|
6844
|
+
});
|
|
6845
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6846
|
+
return transformCart4(cart, this.currencyCode);
|
|
6847
|
+
}
|
|
6848
|
+
async removeDiscountCode(_cartId, code) {
|
|
6849
|
+
await this.storeApiFetch("/cart/remove-coupon", {
|
|
6850
|
+
method: "POST",
|
|
6851
|
+
body: JSON.stringify({ code })
|
|
6852
|
+
});
|
|
6853
|
+
const cart = await this.storeApiFetch("/cart");
|
|
6854
|
+
return transformCart4(cart, this.currencyCode);
|
|
6855
|
+
}
|
|
6856
|
+
// ============================================
|
|
6857
|
+
// Customer, Order, Payment, Inventory, Admin
|
|
6858
|
+
// ============================================
|
|
6859
|
+
async getOrders(_options) {
|
|
6860
|
+
return [];
|
|
6861
|
+
}
|
|
6862
|
+
async getPaymentMethods(_options) {
|
|
6863
|
+
return [];
|
|
6864
|
+
}
|
|
6865
|
+
async getStockLocations() {
|
|
6866
|
+
return [];
|
|
6867
|
+
}
|
|
6868
|
+
async checkInventoryLevels() {
|
|
6869
|
+
return [];
|
|
6870
|
+
}
|
|
6871
|
+
async validateCartStock() {
|
|
6872
|
+
return { allAvailable: true, unavailableItems: [] };
|
|
6873
|
+
}
|
|
6874
|
+
async getFulfillmentOptions() {
|
|
6875
|
+
return [];
|
|
6876
|
+
}
|
|
6877
|
+
async cancelOrder(_orderId) {
|
|
6878
|
+
throw new ProviderAPIError("Order cancellation requires WooCommerce REST API v3.", "WOOCOMMERCE_AUTH_REQUIRED");
|
|
6879
|
+
}
|
|
6880
|
+
async createFulfillment(_orderId) {
|
|
6881
|
+
throw new ProviderAPIError("Fulfillment requires WooCommerce REST API v3.", "WOOCOMMERCE_AUTH_REQUIRED");
|
|
6882
|
+
}
|
|
6883
|
+
async createRefund(_orderId) {
|
|
6884
|
+
throw new ProviderAPIError("Refunds require WooCommerce REST API v3.", "WOOCOMMERCE_AUTH_REQUIRED");
|
|
6885
|
+
}
|
|
6886
|
+
async getAdminOrders() {
|
|
6887
|
+
return { orders: [], count: 0 };
|
|
6888
|
+
}
|
|
6889
|
+
async getStorefrontContext() {
|
|
6890
|
+
return {
|
|
6891
|
+
id: this.baseUrl,
|
|
6892
|
+
name: `WooCommerce ${this.baseUrl}`,
|
|
6893
|
+
platformType: "woocommerce",
|
|
6894
|
+
requiresProductScoping: false,
|
|
6895
|
+
currencyCode: this.currencyCode
|
|
6896
|
+
};
|
|
6897
|
+
}
|
|
6898
|
+
isStorefrontRequired() {
|
|
6899
|
+
return false;
|
|
6900
|
+
}
|
|
6901
|
+
};
|
|
6902
|
+
|
|
6903
|
+
// src/providers/spree/transforms.ts
|
|
6904
|
+
function toMoney(amount, currencyCode) {
|
|
6905
|
+
const num = typeof amount === "string" ? parseFloat(amount) || 0 : amount;
|
|
6906
|
+
return { amount: num.toFixed(2), currencyCode };
|
|
6907
|
+
}
|
|
6908
|
+
function transformProduct5(data, included = [], currencyCode = "USD") {
|
|
6909
|
+
const attrs = data.attributes ?? data;
|
|
6910
|
+
const id = String(data.id ?? "");
|
|
6911
|
+
const images = [];
|
|
6912
|
+
const imageRels = data.relationships?.images?.data ?? [];
|
|
6913
|
+
for (const imgRef of imageRels) {
|
|
6914
|
+
const imgIncluded = included.find((i) => i.type === "image" && String(i.id) === String(imgRef.id));
|
|
6915
|
+
if (imgIncluded) {
|
|
6916
|
+
images.push({
|
|
6917
|
+
url: imgIncluded.attributes?.original_url ?? imgIncluded.attributes?.styles?.[0]?.url ?? "",
|
|
6918
|
+
altText: imgIncluded.attributes?.alt ?? attrs.name ?? "",
|
|
6919
|
+
width: void 0,
|
|
6920
|
+
height: void 0
|
|
6921
|
+
});
|
|
6922
|
+
}
|
|
6923
|
+
}
|
|
6924
|
+
if (images.length === 0 && Array.isArray(attrs.images)) {
|
|
6925
|
+
for (const img of attrs.images) {
|
|
6926
|
+
images.push({
|
|
6927
|
+
url: img.original_url ?? img.url ?? "",
|
|
6928
|
+
altText: img.alt ?? attrs.name ?? "",
|
|
6929
|
+
width: void 0,
|
|
6930
|
+
height: void 0
|
|
6931
|
+
});
|
|
6932
|
+
}
|
|
6933
|
+
}
|
|
6934
|
+
const variants = [];
|
|
6935
|
+
const variantRels = data.relationships?.variants?.data ?? [];
|
|
6936
|
+
for (const varRef of variantRels) {
|
|
6937
|
+
const varIncluded = included.find((i) => i.type === "variant" && String(i.id) === String(varRef.id));
|
|
6938
|
+
if (varIncluded) {
|
|
6939
|
+
const vAttrs = varIncluded.attributes ?? {};
|
|
6940
|
+
variants.push({
|
|
6941
|
+
id: String(varIncluded.id),
|
|
6942
|
+
title: vAttrs.name ?? vAttrs.sku ?? `Variant ${varIncluded.id}`,
|
|
6943
|
+
availableForSale: vAttrs.in_stock !== false && vAttrs.purchasable !== false,
|
|
6944
|
+
price: toMoney(vAttrs.price ?? vAttrs.display_price ?? "0", currencyCode),
|
|
6945
|
+
compareAtPrice: vAttrs.compare_at_price ? toMoney(vAttrs.compare_at_price, currencyCode) : void 0,
|
|
6946
|
+
selectedOptions: (vAttrs.option_values ?? []).map((ov) => ({
|
|
6947
|
+
name: ov.option_type_name ?? "Option",
|
|
6948
|
+
value: ov.name ?? ov.presentation ?? ""
|
|
6949
|
+
}))
|
|
6950
|
+
});
|
|
6951
|
+
}
|
|
6952
|
+
}
|
|
6953
|
+
if (variants.length === 0) {
|
|
6954
|
+
variants.push({
|
|
6955
|
+
id: attrs.default_variant_id ? String(attrs.default_variant_id) : id,
|
|
6956
|
+
title: "Default",
|
|
6957
|
+
availableForSale: attrs.in_stock !== false && attrs.purchasable !== false,
|
|
6958
|
+
price: toMoney(attrs.price ?? attrs.display_price ?? "0", currencyCode),
|
|
6959
|
+
compareAtPrice: attrs.compare_at_price ? toMoney(attrs.compare_at_price, currencyCode) : void 0,
|
|
6960
|
+
selectedOptions: []
|
|
6961
|
+
});
|
|
6962
|
+
}
|
|
6963
|
+
const options = (attrs.option_types ?? []).map((ot) => ({
|
|
6964
|
+
id: String(ot.id ?? ot.name ?? ""),
|
|
6965
|
+
name: ot.presentation ?? ot.name ?? "",
|
|
6966
|
+
values: (ot.option_values ?? []).map((ov) => ov.presentation ?? ov.name ?? "")
|
|
6967
|
+
}));
|
|
6968
|
+
return {
|
|
6969
|
+
id,
|
|
6970
|
+
handle: attrs.slug ?? id,
|
|
6971
|
+
title: attrs.name ?? "",
|
|
6972
|
+
description: attrs.description ?? "",
|
|
6973
|
+
descriptionHtml: attrs.description ?? "",
|
|
6974
|
+
availableForSale: attrs.in_stock !== false && attrs.purchasable !== false,
|
|
6975
|
+
priceRange: {
|
|
6976
|
+
minVariantPrice: variants.reduce(
|
|
6977
|
+
(min, v) => v.price.amount < min.amount ? v.price : min,
|
|
6978
|
+
variants[0]?.price ?? toMoney("0", currencyCode)
|
|
6979
|
+
),
|
|
6980
|
+
maxVariantPrice: variants.reduce(
|
|
6981
|
+
(max, v) => v.price.amount > max.amount ? v.price : max,
|
|
6982
|
+
variants[0]?.price ?? toMoney("0", currencyCode)
|
|
6983
|
+
)
|
|
6984
|
+
},
|
|
6985
|
+
variants,
|
|
6986
|
+
options,
|
|
6987
|
+
images,
|
|
6988
|
+
featuredImage: images[0] ?? { url: "", altText: attrs.name ?? "" },
|
|
6989
|
+
tags: [],
|
|
6990
|
+
seo: {
|
|
6991
|
+
title: attrs.meta_title ?? attrs.name ?? "",
|
|
6992
|
+
description: attrs.meta_description ?? ""
|
|
6993
|
+
},
|
|
6994
|
+
updatedAt: attrs.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6995
|
+
createdAt: attrs.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
6996
|
+
};
|
|
6997
|
+
}
|
|
6998
|
+
function transformCollection5(data) {
|
|
6999
|
+
const attrs = data.attributes ?? data;
|
|
7000
|
+
return {
|
|
7001
|
+
id: String(data.id ?? ""),
|
|
7002
|
+
handle: attrs.permalink ?? attrs.slug ?? String(data.id ?? ""),
|
|
7003
|
+
title: attrs.name ?? "",
|
|
7004
|
+
description: attrs.description ?? "",
|
|
7005
|
+
seo: {
|
|
7006
|
+
title: attrs.meta_title ?? attrs.name ?? "",
|
|
7007
|
+
description: attrs.meta_description ?? ""
|
|
7008
|
+
},
|
|
7009
|
+
updatedAt: attrs.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7010
|
+
path: `/collections/${attrs.permalink ?? attrs.slug ?? data.id}`
|
|
7011
|
+
};
|
|
7012
|
+
}
|
|
7013
|
+
function transformCart5(data, currencyCode = "USD") {
|
|
7014
|
+
const attrs = data.attributes ?? data;
|
|
7015
|
+
const items = (attrs.line_items ?? data.included?.filter((i) => i.type === "line_item") ?? []).map(
|
|
7016
|
+
(item) => {
|
|
7017
|
+
const itemAttrs = item.attributes ?? item;
|
|
7018
|
+
return {
|
|
7019
|
+
id: String(item.id ?? itemAttrs.id ?? ""),
|
|
7020
|
+
quantity: itemAttrs.quantity ?? 1,
|
|
7021
|
+
cost: {
|
|
7022
|
+
totalAmount: toMoney(itemAttrs.total ?? itemAttrs.display_total ?? "0", currencyCode)
|
|
7023
|
+
},
|
|
7024
|
+
merchandise: {
|
|
7025
|
+
id: String(itemAttrs.variant_id ?? item.id ?? ""),
|
|
7026
|
+
title: itemAttrs.name ?? "",
|
|
7027
|
+
selectedOptions: [],
|
|
7028
|
+
product: {
|
|
7029
|
+
id: String(itemAttrs.product_id ?? ""),
|
|
7030
|
+
handle: itemAttrs.slug ?? "",
|
|
7031
|
+
title: itemAttrs.name ?? "",
|
|
7032
|
+
featuredImage: { url: itemAttrs.image_url ?? "", altText: itemAttrs.name ?? "" }
|
|
7033
|
+
}
|
|
7034
|
+
}
|
|
7035
|
+
};
|
|
7036
|
+
}
|
|
7037
|
+
);
|
|
7038
|
+
return {
|
|
7039
|
+
id: String(data.id ?? attrs.token ?? ""),
|
|
7040
|
+
checkoutUrl: "",
|
|
7041
|
+
totalQuantity: items.reduce((sum, i) => sum + i.quantity, 0),
|
|
7042
|
+
cost: {
|
|
7043
|
+
subtotalAmount: toMoney(attrs.item_total ?? attrs.display_item_total ?? "0", currencyCode),
|
|
7044
|
+
totalAmount: toMoney(attrs.total ?? attrs.display_total ?? "0", currencyCode),
|
|
7045
|
+
totalTaxAmount: toMoney(attrs.tax_total ?? attrs.display_tax_total ?? "0", currencyCode)
|
|
7046
|
+
},
|
|
7047
|
+
lines: items
|
|
7048
|
+
};
|
|
7049
|
+
}
|
|
7050
|
+
function transformAddress2(addr) {
|
|
7051
|
+
return {
|
|
7052
|
+
id: String(addr.id ?? ""),
|
|
7053
|
+
firstName: addr.first_name ?? addr.firstname ?? "",
|
|
7054
|
+
lastName: addr.last_name ?? addr.lastname ?? "",
|
|
7055
|
+
company: addr.company ?? "",
|
|
7056
|
+
address1: addr.address1 ?? "",
|
|
7057
|
+
address2: addr.address2 ?? "",
|
|
7058
|
+
city: addr.city ?? "",
|
|
7059
|
+
province: addr.state_name ?? addr.state ?? "",
|
|
7060
|
+
postalCode: addr.zipcode ?? addr.zip ?? "",
|
|
7061
|
+
countryCode: addr.country_iso ?? addr.country_code ?? "",
|
|
7062
|
+
phone: addr.phone ?? ""
|
|
7063
|
+
};
|
|
7064
|
+
}
|
|
7065
|
+
function transformOrder5(data, currencyCode = "USD") {
|
|
7066
|
+
const attrs = data.attributes ?? data;
|
|
7067
|
+
const items = (attrs.line_items ?? []).map((item) => ({
|
|
7068
|
+
id: String(item.id ?? ""),
|
|
7069
|
+
title: item.name ?? "",
|
|
7070
|
+
quantity: item.quantity ?? 1,
|
|
7071
|
+
price: toMoney(item.price ?? "0", currencyCode),
|
|
7072
|
+
total: toMoney(item.total ?? "0", currencyCode),
|
|
7073
|
+
productId: String(item.product_id ?? ""),
|
|
7074
|
+
variantId: String(item.variant_id ?? "")
|
|
7075
|
+
}));
|
|
7076
|
+
return {
|
|
7077
|
+
id: String(data.id ?? attrs.number ?? ""),
|
|
7078
|
+
orderNumber: attrs.number ?? String(data.id ?? ""),
|
|
7079
|
+
email: attrs.email ?? "",
|
|
7080
|
+
status: attrs.state === "complete" ? "completed" : attrs.state === "canceled" ? "cancelled" : "pending",
|
|
7081
|
+
paymentStatus: attrs.payment_state === "paid" ? "paid" : attrs.payment_state === "balance_due" ? "pending" : "pending",
|
|
7082
|
+
fulfillmentStatus: attrs.shipment_state === "shipped" ? "fulfilled" : attrs.shipment_state === "partial" ? "partially_fulfilled" : "unfulfilled",
|
|
7083
|
+
items,
|
|
7084
|
+
total: toMoney(attrs.total ?? "0", currencyCode),
|
|
7085
|
+
subtotal: toMoney(attrs.item_total ?? "0", currencyCode),
|
|
7086
|
+
tax: toMoney(attrs.tax_total ?? "0", currencyCode),
|
|
7087
|
+
shipping: toMoney(attrs.ship_total ?? "0", currencyCode),
|
|
7088
|
+
shippingAddress: attrs.ship_address ? transformAddress2(attrs.ship_address) : void 0,
|
|
7089
|
+
billingAddress: attrs.bill_address ? transformAddress2(attrs.bill_address) : void 0,
|
|
7090
|
+
createdAt: attrs.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7091
|
+
updatedAt: attrs.updated_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
7092
|
+
};
|
|
7093
|
+
}
|
|
7094
|
+
|
|
7095
|
+
// src/providers/spree/index.ts
|
|
7096
|
+
var SpreeProvider = class {
|
|
7097
|
+
name = "spree";
|
|
7098
|
+
version = "1.0.0";
|
|
7099
|
+
capabilities = SPREE_CAPABILITIES;
|
|
7100
|
+
logger;
|
|
7101
|
+
baseUrl;
|
|
7102
|
+
apiBase;
|
|
7103
|
+
currencyCode;
|
|
7104
|
+
bearerToken;
|
|
7105
|
+
orderToken;
|
|
7106
|
+
// guest cart token
|
|
7107
|
+
constructor(config) {
|
|
7108
|
+
this.logger = createLogger("SpreeProvider", config.debug);
|
|
7109
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
7110
|
+
this.apiBase = `${this.baseUrl}/api/v2/storefront`;
|
|
7111
|
+
this.currencyCode = config.currencyCode ?? "USD";
|
|
7112
|
+
this.bearerToken = config.bearerToken;
|
|
7113
|
+
this.logger.info("Initializing Spree Provider", { baseUrl: this.baseUrl });
|
|
7114
|
+
}
|
|
7115
|
+
async apiFetch(path, init) {
|
|
7116
|
+
const headers = {
|
|
7117
|
+
"Content-Type": "application/vnd.api+json",
|
|
7118
|
+
"Accept": "application/vnd.api+json",
|
|
7119
|
+
...init?.headers ?? {}
|
|
7120
|
+
};
|
|
7121
|
+
if (this.bearerToken) {
|
|
7122
|
+
headers["Authorization"] = `Bearer ${this.bearerToken}`;
|
|
7123
|
+
}
|
|
7124
|
+
if (this.orderToken) {
|
|
7125
|
+
headers["X-Spree-Order-Token"] = this.orderToken;
|
|
7126
|
+
}
|
|
7127
|
+
const response = await fetch(`${this.apiBase}${path}`, {
|
|
7128
|
+
...init,
|
|
7129
|
+
headers
|
|
7130
|
+
});
|
|
7131
|
+
if (!response.ok) {
|
|
7132
|
+
throw new ProviderAPIError(
|
|
7133
|
+
`Spree API error: ${response.status}`,
|
|
7134
|
+
"SPREE_API_ERROR",
|
|
7135
|
+
response.status
|
|
7136
|
+
);
|
|
7137
|
+
}
|
|
7138
|
+
return response.json();
|
|
7139
|
+
}
|
|
7140
|
+
// ============================================
|
|
7141
|
+
// Product Operations
|
|
7142
|
+
// ============================================
|
|
7143
|
+
async getProduct(handle) {
|
|
7144
|
+
try {
|
|
7145
|
+
const { data, included } = await this.apiFetch(
|
|
7146
|
+
`/products/${encodeURIComponent(handle)}?include=images,variants,option_types`
|
|
7147
|
+
);
|
|
7148
|
+
return transformProduct5(data, included ?? [], this.currencyCode);
|
|
7149
|
+
} catch {
|
|
7150
|
+
return void 0;
|
|
7151
|
+
}
|
|
7152
|
+
}
|
|
7153
|
+
async getProducts(options) {
|
|
7154
|
+
let path = `/products?include=images,variants&per_page=${options?.first ?? 20}`;
|
|
7155
|
+
if (options?.query) path += `&filter[name]=${encodeURIComponent(options.query)}`;
|
|
7156
|
+
if (options?.sortKey) {
|
|
7157
|
+
const sortMap = {
|
|
7158
|
+
"price-asc": "price",
|
|
7159
|
+
"price-desc": "-price",
|
|
7160
|
+
"title-asc": "name",
|
|
7161
|
+
"title-desc": "-name",
|
|
7162
|
+
"created-desc": "-created_at"
|
|
7163
|
+
};
|
|
7164
|
+
if (sortMap[options.sortKey]) path += `&sort=${sortMap[options.sortKey]}`;
|
|
7165
|
+
}
|
|
7166
|
+
const { data, included } = await this.apiFetch(path);
|
|
7167
|
+
return (Array.isArray(data) ? data : []).map((p) => transformProduct5(p, included ?? [], this.currencyCode));
|
|
7168
|
+
}
|
|
7169
|
+
async getProductRecommendations(_productId) {
|
|
7170
|
+
return [];
|
|
7171
|
+
}
|
|
7172
|
+
// ============================================
|
|
7173
|
+
// Collection/Taxon Operations
|
|
7174
|
+
// ============================================
|
|
7175
|
+
async getCollection(handle) {
|
|
7176
|
+
try {
|
|
7177
|
+
const { data } = await this.apiFetch(`/taxons/${encodeURIComponent(handle)}`);
|
|
7178
|
+
return transformCollection5(data);
|
|
7179
|
+
} catch {
|
|
7180
|
+
return void 0;
|
|
7181
|
+
}
|
|
7182
|
+
}
|
|
7183
|
+
async getCollections(options) {
|
|
7184
|
+
const { data } = await this.apiFetch(`/taxons?per_page=${options?.limit ?? 50}`);
|
|
7185
|
+
return (Array.isArray(data) ? data : []).map(transformCollection5);
|
|
7186
|
+
}
|
|
7187
|
+
async getCollectionProducts(handle, options) {
|
|
7188
|
+
const { data, included } = await this.apiFetch(
|
|
7189
|
+
`/products?include=images,variants&filter[taxons]=${encodeURIComponent(handle)}&per_page=${options?.first ?? 20}`
|
|
7190
|
+
);
|
|
7191
|
+
return (Array.isArray(data) ? data : []).map((p) => transformProduct5(p, included ?? [], this.currencyCode));
|
|
7192
|
+
}
|
|
7193
|
+
async getCategory(handle) {
|
|
7194
|
+
return this.getCollection(handle);
|
|
7195
|
+
}
|
|
7196
|
+
async getCategories(options) {
|
|
7197
|
+
return this.getCollections(options);
|
|
7198
|
+
}
|
|
7199
|
+
async getCategoryProducts(handle, options) {
|
|
7200
|
+
return this.getCollectionProducts(handle, options);
|
|
7201
|
+
}
|
|
7202
|
+
// ============================================
|
|
7203
|
+
// Cart Operations
|
|
7204
|
+
// ============================================
|
|
7205
|
+
async createCart() {
|
|
7206
|
+
const { data } = await this.apiFetch("/cart", {
|
|
7207
|
+
method: "POST",
|
|
7208
|
+
body: JSON.stringify({})
|
|
7209
|
+
});
|
|
7210
|
+
if (data.attributes?.token) {
|
|
7211
|
+
this.orderToken = data.attributes.token;
|
|
7212
|
+
}
|
|
7213
|
+
return transformCart5(data, this.currencyCode);
|
|
7214
|
+
}
|
|
7215
|
+
async getCart(_cartId) {
|
|
7216
|
+
try {
|
|
7217
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7218
|
+
return transformCart5(data, this.currencyCode);
|
|
7219
|
+
} catch {
|
|
7220
|
+
return void 0;
|
|
7221
|
+
}
|
|
7222
|
+
}
|
|
7223
|
+
async addToCart(_cartId, lines) {
|
|
7224
|
+
for (const line of lines) {
|
|
7225
|
+
await this.apiFetch("/cart/add_item", {
|
|
7226
|
+
method: "POST",
|
|
7227
|
+
body: JSON.stringify({ variant_id: line.merchandiseId, quantity: line.quantity })
|
|
7228
|
+
});
|
|
7229
|
+
}
|
|
7230
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7231
|
+
return transformCart5(data, this.currencyCode);
|
|
7232
|
+
}
|
|
7233
|
+
async removeFromCart(_cartId, lineIds) {
|
|
7234
|
+
for (const id of lineIds) {
|
|
7235
|
+
await this.apiFetch(`/cart/remove_line_item/${id}`, { method: "DELETE" });
|
|
7236
|
+
}
|
|
7237
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7238
|
+
return transformCart5(data, this.currencyCode);
|
|
7239
|
+
}
|
|
7240
|
+
async updateCart(_cartId, lines) {
|
|
7241
|
+
for (const line of lines) {
|
|
7242
|
+
await this.apiFetch(`/cart/set_quantity`, {
|
|
7243
|
+
method: "PATCH",
|
|
7244
|
+
body: JSON.stringify({ line_item_id: line.id, quantity: line.quantity })
|
|
7245
|
+
});
|
|
7246
|
+
}
|
|
7247
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7248
|
+
return transformCart5(data, this.currencyCode);
|
|
7249
|
+
}
|
|
7250
|
+
// ============================================
|
|
7251
|
+
// Checkout Operations
|
|
7252
|
+
// ============================================
|
|
7253
|
+
async completeCheckout(_cartId) {
|
|
7254
|
+
const { data } = await this.apiFetch("/checkout/complete", { method: "PATCH" });
|
|
7255
|
+
return transformOrder5(data, this.currencyCode);
|
|
7256
|
+
}
|
|
7257
|
+
async getShippingMethods(_cartId) {
|
|
7258
|
+
try {
|
|
7259
|
+
const { data } = await this.apiFetch("/checkout/shipping_rates");
|
|
7260
|
+
return (Array.isArray(data) ? data : []).map((rate) => ({
|
|
7261
|
+
id: String(rate.id),
|
|
7262
|
+
name: rate.attributes?.name ?? rate.name ?? "",
|
|
7263
|
+
price: { amount: String(parseFloat(rate.attributes?.final_price ?? rate.attributes?.cost ?? "0").toFixed(2)), currencyCode: this.currencyCode }
|
|
7264
|
+
}));
|
|
7265
|
+
} catch {
|
|
7266
|
+
return [];
|
|
7267
|
+
}
|
|
7268
|
+
}
|
|
7269
|
+
async getRegionCountries() {
|
|
7270
|
+
return [];
|
|
7271
|
+
}
|
|
7272
|
+
async addShippingMethod(_cartId, methodId) {
|
|
7273
|
+
await this.apiFetch("/checkout", {
|
|
7274
|
+
method: "PATCH",
|
|
7275
|
+
body: JSON.stringify({ order: { shipments_attributes: [{ selected_shipping_rate_id: methodId }] } })
|
|
7276
|
+
});
|
|
7277
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7278
|
+
return transformCart5(data, this.currencyCode);
|
|
7279
|
+
}
|
|
7280
|
+
async initiatePaymentSession(_cartId, _providerId) {
|
|
7281
|
+
}
|
|
7282
|
+
async applyDiscountCode(_cartId, code) {
|
|
7283
|
+
await this.apiFetch("/cart/apply_coupon_code", {
|
|
7284
|
+
method: "PATCH",
|
|
7285
|
+
body: JSON.stringify({ coupon_code: code })
|
|
7286
|
+
});
|
|
7287
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7288
|
+
return transformCart5(data, this.currencyCode);
|
|
7289
|
+
}
|
|
7290
|
+
async removeDiscountCode(_cartId, code) {
|
|
7291
|
+
await this.apiFetch(`/cart/remove_coupon_code/${encodeURIComponent(code)}`, { method: "DELETE" });
|
|
7292
|
+
const { data } = await this.apiFetch("/cart?include=line_items");
|
|
7293
|
+
return transformCart5(data, this.currencyCode);
|
|
7294
|
+
}
|
|
7295
|
+
// ============================================
|
|
7296
|
+
// Customer, Order, Payment, Inventory, Admin
|
|
7297
|
+
// ============================================
|
|
7298
|
+
async getOrders(_options) {
|
|
7299
|
+
try {
|
|
7300
|
+
const { data } = await this.apiFetch("/account/orders");
|
|
7301
|
+
return (Array.isArray(data) ? data : []).map((o) => transformOrder5(o, this.currencyCode));
|
|
7302
|
+
} catch {
|
|
7303
|
+
return [];
|
|
7304
|
+
}
|
|
7305
|
+
}
|
|
7306
|
+
async getPaymentMethods(_options) {
|
|
7307
|
+
try {
|
|
7308
|
+
const { data } = await this.apiFetch("/checkout/payment_methods");
|
|
7309
|
+
return (Array.isArray(data) ? data : []).map((pm) => ({
|
|
7310
|
+
id: String(pm.id),
|
|
7311
|
+
name: pm.attributes?.name ?? "",
|
|
7312
|
+
type: "other",
|
|
7313
|
+
isEnabled: true,
|
|
7314
|
+
requiresSetup: false
|
|
7315
|
+
}));
|
|
7316
|
+
} catch {
|
|
7317
|
+
return [];
|
|
7318
|
+
}
|
|
7319
|
+
}
|
|
7320
|
+
async getStockLocations() {
|
|
7321
|
+
return [];
|
|
7322
|
+
}
|
|
7323
|
+
async checkInventoryLevels() {
|
|
7324
|
+
return [];
|
|
7325
|
+
}
|
|
7326
|
+
async validateCartStock() {
|
|
7327
|
+
return { allAvailable: true, unavailableItems: [] };
|
|
7328
|
+
}
|
|
7329
|
+
async getFulfillmentOptions() {
|
|
7330
|
+
return [];
|
|
7331
|
+
}
|
|
7332
|
+
async cancelOrder(_orderId) {
|
|
7333
|
+
throw new ProviderAPIError("Order cancellation requires Spree Admin API.", "SPREE_ADMIN_REQUIRED");
|
|
7334
|
+
}
|
|
7335
|
+
async createFulfillment(_orderId) {
|
|
7336
|
+
throw new ProviderAPIError("Fulfillment requires Spree Admin API.", "SPREE_ADMIN_REQUIRED");
|
|
7337
|
+
}
|
|
7338
|
+
async createRefund(_orderId) {
|
|
7339
|
+
throw new ProviderAPIError("Refunds require Spree Admin API.", "SPREE_ADMIN_REQUIRED");
|
|
7340
|
+
}
|
|
7341
|
+
async getAdminOrders() {
|
|
7342
|
+
return { orders: [], count: 0 };
|
|
7343
|
+
}
|
|
7344
|
+
async getStorefrontContext() {
|
|
7345
|
+
return {
|
|
7346
|
+
id: this.baseUrl,
|
|
7347
|
+
name: `Spree ${this.baseUrl}`,
|
|
7348
|
+
platformType: "spree",
|
|
7349
|
+
requiresProductScoping: true,
|
|
7350
|
+
currencyCode: this.currencyCode
|
|
7351
|
+
};
|
|
7352
|
+
}
|
|
7353
|
+
isStorefrontRequired() {
|
|
7354
|
+
return true;
|
|
7355
|
+
}
|
|
7356
|
+
};
|
|
7357
|
+
|
|
7358
|
+
// src/providers/magento/transforms.ts
|
|
7359
|
+
function toMoney2(value, currencyCode) {
|
|
7360
|
+
return { amount: (value ?? 0).toFixed(2), currencyCode };
|
|
7361
|
+
}
|
|
7362
|
+
function extractPrice(priceRange, currencyCode) {
|
|
7363
|
+
const minPrice = priceRange?.minimum_price?.regular_price?.value ?? priceRange?.minimum_price?.final_price?.value ?? 0;
|
|
7364
|
+
const maxPrice = priceRange?.maximum_price?.regular_price?.value ?? priceRange?.maximum_price?.final_price?.value ?? minPrice;
|
|
7365
|
+
const currency = priceRange?.minimum_price?.regular_price?.currency ?? currencyCode;
|
|
7366
|
+
return {
|
|
7367
|
+
min: toMoney2(minPrice, currency),
|
|
7368
|
+
max: toMoney2(maxPrice, currency)
|
|
7369
|
+
};
|
|
7370
|
+
}
|
|
7371
|
+
function transformProduct6(data, currencyCode = "USD") {
|
|
7372
|
+
const id = String(data.id ?? data.uid ?? "");
|
|
7373
|
+
const { min, max } = extractPrice(data.price_range, currencyCode);
|
|
7374
|
+
const images = (data.media_gallery ?? data.media_gallery_entries ?? []).map((img) => ({
|
|
7375
|
+
url: img.url ?? (img.file ? `${img.file}` : ""),
|
|
7376
|
+
altText: img.label ?? data.name ?? "",
|
|
7377
|
+
width: void 0,
|
|
7378
|
+
height: void 0
|
|
7379
|
+
}));
|
|
7380
|
+
if (images.length === 0 && data.small_image?.url) {
|
|
7381
|
+
images.push({ url: data.small_image.url, altText: data.small_image.label ?? data.name ?? "" });
|
|
7382
|
+
}
|
|
7383
|
+
if (images.length === 0 && data.image?.url) {
|
|
7384
|
+
images.push({ url: data.image.url, altText: data.image.label ?? data.name ?? "" });
|
|
7385
|
+
}
|
|
7386
|
+
const variants = [];
|
|
7387
|
+
if (data.variants && Array.isArray(data.variants)) {
|
|
7388
|
+
for (const v of data.variants) {
|
|
7389
|
+
const product = v.product ?? {};
|
|
7390
|
+
const vPrice = product.price_range?.minimum_price?.final_price ?? {};
|
|
7391
|
+
variants.push({
|
|
7392
|
+
id: String(product.id ?? product.uid ?? product.sku ?? ""),
|
|
7393
|
+
title: product.name ?? product.sku ?? "",
|
|
7394
|
+
availableForSale: product.stock_status === "IN_STOCK",
|
|
7395
|
+
price: toMoney2(vPrice.value ?? min.amount, vPrice.currency ?? currencyCode),
|
|
7396
|
+
compareAtPrice: void 0,
|
|
7397
|
+
selectedOptions: (v.attributes ?? []).map((attr) => ({
|
|
7398
|
+
name: attr.label ?? "",
|
|
7399
|
+
value: attr.value_index != null ? String(attr.value_index) : ""
|
|
7400
|
+
}))
|
|
7401
|
+
});
|
|
7402
|
+
}
|
|
7403
|
+
}
|
|
7404
|
+
if (variants.length === 0) {
|
|
7405
|
+
variants.push({
|
|
7406
|
+
id: data.sku ?? id,
|
|
7407
|
+
title: data.name ?? "",
|
|
7408
|
+
availableForSale: data.stock_status === "IN_STOCK",
|
|
7409
|
+
price: min,
|
|
7410
|
+
compareAtPrice: void 0,
|
|
7411
|
+
selectedOptions: []
|
|
7412
|
+
});
|
|
7413
|
+
}
|
|
7414
|
+
const options = (data.configurable_options ?? []).map((opt) => ({
|
|
7415
|
+
id: String(opt.attribute_id ?? opt.attribute_code ?? ""),
|
|
7416
|
+
name: opt.label ?? opt.attribute_code ?? "",
|
|
7417
|
+
values: (opt.values ?? []).map((v) => v.label ?? v.store_label ?? String(v.value_index ?? ""))
|
|
7418
|
+
}));
|
|
7419
|
+
return {
|
|
7420
|
+
id,
|
|
7421
|
+
handle: data.url_key ?? data.sku ?? id,
|
|
7422
|
+
title: data.name ?? "",
|
|
7423
|
+
description: data.short_description?.html ?? data.description?.html ?? "",
|
|
7424
|
+
descriptionHtml: data.description?.html ?? "",
|
|
7425
|
+
availableForSale: data.stock_status === "IN_STOCK",
|
|
7426
|
+
priceRange: { minVariantPrice: min, maxVariantPrice: max },
|
|
7427
|
+
variants,
|
|
7428
|
+
options,
|
|
7429
|
+
images,
|
|
7430
|
+
featuredImage: images[0] ?? { url: "", altText: data.name ?? "" },
|
|
7431
|
+
tags: [],
|
|
7432
|
+
seo: {
|
|
7433
|
+
title: data.meta_title ?? data.name ?? "",
|
|
7434
|
+
description: data.meta_description ?? ""
|
|
7435
|
+
},
|
|
7436
|
+
updatedAt: data.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7437
|
+
createdAt: data.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
7438
|
+
};
|
|
7439
|
+
}
|
|
7440
|
+
function transformCollection6(data) {
|
|
7441
|
+
return {
|
|
7442
|
+
id: String(data.id ?? data.uid ?? ""),
|
|
7443
|
+
handle: data.url_key ?? data.url_path ?? String(data.id ?? data.uid ?? ""),
|
|
7444
|
+
title: data.name ?? "",
|
|
7445
|
+
description: data.description ?? "",
|
|
7446
|
+
seo: {
|
|
7447
|
+
title: data.meta_title ?? data.name ?? "",
|
|
7448
|
+
description: data.meta_description ?? ""
|
|
7449
|
+
},
|
|
7450
|
+
updatedAt: data.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7451
|
+
path: `/collections/${data.url_key ?? data.url_path ?? data.id}`
|
|
7452
|
+
};
|
|
7453
|
+
}
|
|
7454
|
+
function transformCart6(data, currencyCode = "USD") {
|
|
7455
|
+
const items = (data.items ?? []).map((item) => {
|
|
7456
|
+
const product = item.product ?? {};
|
|
7457
|
+
const priceVal = item.prices?.price?.value ?? product.price_range?.minimum_price?.final_price?.value ?? 0;
|
|
7458
|
+
const totalVal2 = item.prices?.row_total?.value ?? priceVal * (item.quantity ?? 1);
|
|
7459
|
+
return {
|
|
7460
|
+
id: String(item.id ?? item.uid ?? ""),
|
|
7461
|
+
quantity: item.quantity ?? 1,
|
|
7462
|
+
cost: {
|
|
7463
|
+
totalAmount: toMoney2(totalVal2, item.prices?.price?.currency ?? currencyCode)
|
|
7464
|
+
},
|
|
7465
|
+
merchandise: {
|
|
7466
|
+
id: String(product.sku ?? item.id ?? ""),
|
|
7467
|
+
title: product.name ?? item.product?.name ?? "",
|
|
7468
|
+
selectedOptions: (item.configurable_options ?? []).map((opt) => ({
|
|
7469
|
+
name: opt.option_label ?? "",
|
|
7470
|
+
value: opt.value_label ?? ""
|
|
7471
|
+
})),
|
|
7472
|
+
product: {
|
|
7473
|
+
id: String(product.id ?? product.uid ?? ""),
|
|
7474
|
+
handle: product.url_key ?? "",
|
|
7475
|
+
title: product.name ?? "",
|
|
7476
|
+
featuredImage: {
|
|
7477
|
+
url: product.small_image?.url ?? product.thumbnail?.url ?? "",
|
|
7478
|
+
altText: product.name ?? ""
|
|
7479
|
+
}
|
|
7480
|
+
}
|
|
7481
|
+
}
|
|
7482
|
+
};
|
|
7483
|
+
});
|
|
7484
|
+
const totalVal = data.prices?.grand_total?.value ?? 0;
|
|
7485
|
+
const subtotalVal = data.prices?.subtotal_excluding_tax?.value ?? data.prices?.subtotal_including_tax?.value ?? totalVal;
|
|
7486
|
+
const taxVal = (data.prices?.applied_taxes ?? []).reduce((sum, t) => sum + (t.amount?.value ?? 0), 0);
|
|
7487
|
+
return {
|
|
7488
|
+
id: String(data.id ?? ""),
|
|
7489
|
+
checkoutUrl: "",
|
|
7490
|
+
totalQuantity: items.reduce((sum, i) => sum + i.quantity, 0),
|
|
7491
|
+
cost: {
|
|
7492
|
+
subtotalAmount: toMoney2(subtotalVal, data.prices?.grand_total?.currency ?? currencyCode),
|
|
7493
|
+
totalAmount: toMoney2(totalVal, data.prices?.grand_total?.currency ?? currencyCode),
|
|
7494
|
+
totalTaxAmount: toMoney2(taxVal, currencyCode)
|
|
7495
|
+
},
|
|
7496
|
+
lines: items
|
|
7497
|
+
};
|
|
7498
|
+
}
|
|
7499
|
+
function transformAddress3(addr) {
|
|
7500
|
+
return {
|
|
7501
|
+
id: String(addr.id ?? ""),
|
|
7502
|
+
firstName: addr.firstname ?? "",
|
|
7503
|
+
lastName: addr.lastname ?? "",
|
|
7504
|
+
company: addr.company ?? "",
|
|
7505
|
+
address1: Array.isArray(addr.street) ? addr.street[0] ?? "" : addr.street ?? "",
|
|
7506
|
+
address2: Array.isArray(addr.street) ? addr.street[1] ?? "" : "",
|
|
7507
|
+
city: addr.city ?? "",
|
|
7508
|
+
province: addr.region?.region ?? "",
|
|
7509
|
+
postalCode: addr.postcode ?? "",
|
|
7510
|
+
countryCode: addr.country_code ?? addr.country_id ?? "",
|
|
7511
|
+
phone: addr.telephone ?? ""
|
|
7512
|
+
};
|
|
7513
|
+
}
|
|
7514
|
+
function transformOrder6(data, currencyCode = "USD") {
|
|
7515
|
+
const items = (data.items ?? []).map((item) => ({
|
|
7516
|
+
id: String(item.id ?? ""),
|
|
7517
|
+
title: item.product_name ?? "",
|
|
7518
|
+
quantity: item.quantity_ordered ?? item.quantity ?? 1,
|
|
7519
|
+
price: toMoney2(item.product_sale_price?.value ?? item.price ?? 0, currencyCode),
|
|
7520
|
+
total: toMoney2((item.product_sale_price?.value ?? item.price ?? 0) * (item.quantity_ordered ?? item.quantity ?? 1), currencyCode),
|
|
7521
|
+
sku: item.product_sku ?? item.sku,
|
|
7522
|
+
productId: String(item.product_id ?? "")
|
|
7523
|
+
}));
|
|
7524
|
+
return {
|
|
7525
|
+
id: String(data.id ?? data.order_number ?? ""),
|
|
7526
|
+
orderNumber: data.order_number ?? data.number ?? String(data.id ?? ""),
|
|
7527
|
+
email: data.email ?? "",
|
|
7528
|
+
status: data.status === "complete" ? "completed" : data.status === "canceled" ? "cancelled" : data.status === "closed" ? "refunded" : "pending",
|
|
7529
|
+
paymentStatus: data.payment_status === "paid" ? "paid" : "pending",
|
|
7530
|
+
fulfillmentStatus: data.shipment_status === "shipped" ? "fulfilled" : "unfulfilled",
|
|
7531
|
+
items,
|
|
7532
|
+
total: toMoney2(data.total?.grand_total?.value ?? data.grand_total ?? 0, currencyCode),
|
|
7533
|
+
subtotal: toMoney2(data.total?.subtotal?.value ?? data.subtotal ?? 0, currencyCode),
|
|
7534
|
+
tax: toMoney2(data.total?.total_tax?.value ?? data.tax_amount ?? 0, currencyCode),
|
|
7535
|
+
shipping: toMoney2(data.total?.total_shipping?.value ?? data.shipping_amount ?? 0, currencyCode),
|
|
7536
|
+
shippingAddress: data.shipping_address ? transformAddress3(data.shipping_address) : void 0,
|
|
7537
|
+
billingAddress: data.billing_address ? transformAddress3(data.billing_address) : void 0,
|
|
7538
|
+
createdAt: data.order_date ?? data.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7539
|
+
updatedAt: data.updated_at ?? data.order_date ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
7540
|
+
};
|
|
7541
|
+
}
|
|
7542
|
+
|
|
7543
|
+
// src/providers/magento/queries.ts
|
|
7544
|
+
var PRODUCT_FIELDS = `
|
|
7545
|
+
id
|
|
7546
|
+
uid
|
|
7547
|
+
sku
|
|
7548
|
+
name
|
|
7549
|
+
url_key
|
|
7550
|
+
stock_status
|
|
7551
|
+
short_description { html }
|
|
7552
|
+
description { html }
|
|
7553
|
+
meta_title
|
|
7554
|
+
meta_description
|
|
7555
|
+
created_at
|
|
7556
|
+
updated_at
|
|
7557
|
+
price_range {
|
|
7558
|
+
minimum_price {
|
|
7559
|
+
regular_price { value currency }
|
|
7560
|
+
final_price { value currency }
|
|
7561
|
+
}
|
|
7562
|
+
maximum_price {
|
|
7563
|
+
regular_price { value currency }
|
|
7564
|
+
final_price { value currency }
|
|
7565
|
+
}
|
|
7566
|
+
}
|
|
7567
|
+
small_image { url label }
|
|
7568
|
+
image { url label }
|
|
7569
|
+
media_gallery {
|
|
7570
|
+
url
|
|
7571
|
+
label
|
|
7572
|
+
}
|
|
7573
|
+
... on ConfigurableProduct {
|
|
7574
|
+
configurable_options {
|
|
7575
|
+
attribute_code
|
|
7576
|
+
attribute_id
|
|
7577
|
+
label
|
|
7578
|
+
values {
|
|
7579
|
+
label
|
|
7580
|
+
value_index
|
|
7581
|
+
}
|
|
7582
|
+
}
|
|
7583
|
+
variants {
|
|
7584
|
+
product {
|
|
7585
|
+
id uid sku name
|
|
7586
|
+
stock_status
|
|
7587
|
+
price_range {
|
|
7588
|
+
minimum_price {
|
|
7589
|
+
final_price { value currency }
|
|
7590
|
+
}
|
|
7591
|
+
}
|
|
7592
|
+
small_image { url label }
|
|
7593
|
+
}
|
|
7594
|
+
attributes {
|
|
7595
|
+
label
|
|
7596
|
+
value_index
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7599
|
+
}
|
|
7600
|
+
`;
|
|
7601
|
+
var GET_PRODUCTS2 = `
|
|
7602
|
+
query GetProducts($search: String, $pageSize: Int, $currentPage: Int, $sort: ProductAttributeSortInput) {
|
|
7603
|
+
products(search: $search, pageSize: $pageSize, currentPage: $currentPage, sort: $sort) {
|
|
7604
|
+
items { ${PRODUCT_FIELDS} }
|
|
7605
|
+
total_count
|
|
7606
|
+
}
|
|
7607
|
+
}
|
|
7608
|
+
`;
|
|
7609
|
+
var GET_PRODUCT_BY_URL_KEY = `
|
|
7610
|
+
query GetProductByUrlKey($urlKey: String!) {
|
|
7611
|
+
products(filter: { url_key: { eq: $urlKey } }, pageSize: 1) {
|
|
7612
|
+
items { ${PRODUCT_FIELDS} }
|
|
7613
|
+
}
|
|
7614
|
+
}
|
|
7615
|
+
`;
|
|
7616
|
+
var GET_CATEGORIES = `
|
|
7617
|
+
query GetCategories($pageSize: Int) {
|
|
7618
|
+
categories(pageSize: $pageSize) {
|
|
7619
|
+
items {
|
|
7620
|
+
id uid name url_key url_path description meta_title meta_description
|
|
7621
|
+
}
|
|
7622
|
+
}
|
|
7623
|
+
}
|
|
7624
|
+
`;
|
|
7625
|
+
var GET_CATEGORY_BY_URL_KEY = `
|
|
7626
|
+
query GetCategoryByUrlKey($urlKey: String!) {
|
|
7627
|
+
categories(filters: { url_key: { eq: $urlKey } }, pageSize: 1) {
|
|
7628
|
+
items {
|
|
7629
|
+
id uid name url_key url_path description meta_title meta_description
|
|
7630
|
+
}
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7633
|
+
`;
|
|
7634
|
+
var GET_CATEGORY_PRODUCTS = `
|
|
7635
|
+
query GetCategoryProducts($urlKey: String!, $pageSize: Int) {
|
|
7636
|
+
products(filter: { category_url_key: { eq: $urlKey } }, pageSize: $pageSize) {
|
|
7637
|
+
items { ${PRODUCT_FIELDS} }
|
|
7638
|
+
total_count
|
|
7639
|
+
}
|
|
7640
|
+
}
|
|
7641
|
+
`;
|
|
7642
|
+
var GET_CART2 = `
|
|
7643
|
+
query GetCart($cartId: String!) {
|
|
7644
|
+
cart(cart_id: $cartId) {
|
|
7645
|
+
id
|
|
7646
|
+
items {
|
|
7647
|
+
id uid quantity
|
|
7648
|
+
product {
|
|
7649
|
+
id uid sku name url_key
|
|
7650
|
+
small_image { url }
|
|
7651
|
+
thumbnail { url }
|
|
7652
|
+
price_range {
|
|
7653
|
+
minimum_price {
|
|
7654
|
+
final_price { value currency }
|
|
7655
|
+
}
|
|
7656
|
+
}
|
|
7657
|
+
}
|
|
7658
|
+
prices {
|
|
7659
|
+
price { value currency }
|
|
7660
|
+
row_total { value currency }
|
|
7661
|
+
}
|
|
7662
|
+
... on ConfigurableCartItem {
|
|
7663
|
+
configurable_options {
|
|
7664
|
+
option_label
|
|
7665
|
+
value_label
|
|
7666
|
+
}
|
|
7667
|
+
}
|
|
7668
|
+
}
|
|
7669
|
+
prices {
|
|
7670
|
+
grand_total { value currency }
|
|
7671
|
+
subtotal_excluding_tax { value currency }
|
|
7672
|
+
subtotal_including_tax { value currency }
|
|
7673
|
+
applied_taxes {
|
|
7674
|
+
amount { value currency }
|
|
7675
|
+
label
|
|
7676
|
+
}
|
|
7677
|
+
}
|
|
7678
|
+
}
|
|
7679
|
+
}
|
|
7680
|
+
`;
|
|
7681
|
+
var CREATE_EMPTY_CART = `
|
|
7682
|
+
mutation CreateEmptyCart {
|
|
7683
|
+
createEmptyCart
|
|
7684
|
+
}
|
|
7685
|
+
`;
|
|
7686
|
+
var ADD_TO_CART = `
|
|
7687
|
+
mutation AddToCart($cartId: String!, $cartItems: [CartItemInput!]!) {
|
|
7688
|
+
addProductsToCart(cartId: $cartId, cartItems: $cartItems) {
|
|
7689
|
+
cart {
|
|
7690
|
+
id
|
|
7691
|
+
items { id uid quantity product { sku name } prices { price { value currency } row_total { value currency } } }
|
|
7692
|
+
prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
|
|
7693
|
+
}
|
|
7694
|
+
user_errors { code message }
|
|
7695
|
+
}
|
|
7696
|
+
}
|
|
7697
|
+
`;
|
|
7698
|
+
var REMOVE_FROM_CART = `
|
|
7699
|
+
mutation RemoveFromCart($cartId: String!, $cartItemUid: ID!) {
|
|
7700
|
+
removeItemFromCart(input: { cart_id: $cartId, cart_item_uid: $cartItemUid }) {
|
|
7701
|
+
cart {
|
|
7702
|
+
id
|
|
7703
|
+
items { id uid quantity product { sku name } prices { price { value currency } row_total { value currency } } }
|
|
7704
|
+
prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
|
|
7705
|
+
}
|
|
7706
|
+
}
|
|
7707
|
+
}
|
|
7708
|
+
`;
|
|
7709
|
+
var UPDATE_CART_ITEMS = `
|
|
7710
|
+
mutation UpdateCartItems($cartId: String!, $cartItems: [CartItemUpdateInput!]!) {
|
|
7711
|
+
updateCartItems(input: { cart_id: $cartId, cart_items: $cartItems }) {
|
|
7712
|
+
cart {
|
|
7713
|
+
id
|
|
7714
|
+
items { id uid quantity product { sku name } prices { price { value currency } row_total { value currency } } }
|
|
7715
|
+
prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
|
|
7716
|
+
}
|
|
7717
|
+
}
|
|
7718
|
+
}
|
|
7719
|
+
`;
|
|
7720
|
+
var APPLY_COUPON = `
|
|
7721
|
+
mutation ApplyCoupon($cartId: String!, $couponCode: String!) {
|
|
7722
|
+
applyCouponToCart(input: { cart_id: $cartId, coupon_code: $couponCode }) {
|
|
7723
|
+
cart {
|
|
7724
|
+
id
|
|
7725
|
+
prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
|
|
7726
|
+
}
|
|
7727
|
+
}
|
|
7728
|
+
}
|
|
7729
|
+
`;
|
|
7730
|
+
var REMOVE_COUPON = `
|
|
7731
|
+
mutation RemoveCoupon($cartId: String!) {
|
|
7732
|
+
removeCouponFromCart(input: { cart_id: $cartId }) {
|
|
7733
|
+
cart {
|
|
7734
|
+
id
|
|
7735
|
+
prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
}
|
|
7739
|
+
`;
|
|
7740
|
+
var GET_CUSTOMER_ORDERS = `
|
|
7741
|
+
query GetCustomerOrders($pageSize: Int, $currentPage: Int) {
|
|
7742
|
+
customer {
|
|
7743
|
+
orders(pageSize: $pageSize, currentPage: $currentPage) {
|
|
7744
|
+
items {
|
|
7745
|
+
id order_number order_date status
|
|
7746
|
+
total {
|
|
7747
|
+
grand_total { value currency }
|
|
7748
|
+
subtotal { value currency }
|
|
7749
|
+
total_tax { value currency }
|
|
7750
|
+
total_shipping { value currency }
|
|
7751
|
+
}
|
|
7752
|
+
items {
|
|
7753
|
+
id product_name product_sku
|
|
7754
|
+
quantity_ordered
|
|
7755
|
+
product_sale_price { value currency }
|
|
7756
|
+
}
|
|
7757
|
+
shipping_address {
|
|
7758
|
+
firstname lastname street city
|
|
7759
|
+
region postcode country_code telephone
|
|
7760
|
+
}
|
|
7761
|
+
billing_address {
|
|
7762
|
+
firstname lastname street city
|
|
7763
|
+
region postcode country_code telephone
|
|
7764
|
+
}
|
|
7765
|
+
}
|
|
7766
|
+
total_count
|
|
7767
|
+
}
|
|
7768
|
+
}
|
|
7769
|
+
}
|
|
7770
|
+
`;
|
|
7771
|
+
|
|
7772
|
+
// src/providers/magento/index.ts
|
|
7773
|
+
var MagentoProvider = class {
|
|
7774
|
+
name = "magento";
|
|
7775
|
+
version = "1.0.0";
|
|
7776
|
+
capabilities = MAGENTO_CAPABILITIES;
|
|
7777
|
+
logger;
|
|
7778
|
+
baseUrl;
|
|
7779
|
+
storeCode;
|
|
7780
|
+
currencyCode;
|
|
7781
|
+
graphqlEndpoint;
|
|
7782
|
+
customerToken;
|
|
7783
|
+
accessToken;
|
|
7784
|
+
constructor(config) {
|
|
7785
|
+
this.logger = createLogger("MagentoProvider", config.debug);
|
|
7786
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
7787
|
+
this.storeCode = config.storeCode ?? "default";
|
|
7788
|
+
this.currencyCode = config.currencyCode ?? "USD";
|
|
7789
|
+
this.graphqlEndpoint = `${this.baseUrl}/graphql`;
|
|
7790
|
+
this.customerToken = config.customerToken;
|
|
7791
|
+
this.accessToken = config.accessToken;
|
|
7792
|
+
this.logger.info("Initializing Magento Provider", {
|
|
7793
|
+
baseUrl: this.baseUrl,
|
|
7794
|
+
storeCode: this.storeCode
|
|
7795
|
+
});
|
|
7796
|
+
}
|
|
7797
|
+
async graphql(query, variables) {
|
|
7798
|
+
const headers = {
|
|
7799
|
+
"Content-Type": "application/json",
|
|
7800
|
+
"Store": this.storeCode
|
|
7801
|
+
};
|
|
7802
|
+
if (this.customerToken) {
|
|
7803
|
+
headers["Authorization"] = `Bearer ${this.customerToken}`;
|
|
7804
|
+
}
|
|
7805
|
+
const response = await fetch(this.graphqlEndpoint, {
|
|
7806
|
+
method: "POST",
|
|
7807
|
+
headers,
|
|
7808
|
+
body: JSON.stringify({ query, variables })
|
|
7809
|
+
});
|
|
7810
|
+
if (!response.ok) {
|
|
7811
|
+
throw new ProviderAPIError(
|
|
7812
|
+
`Magento GraphQL error: ${response.status}`,
|
|
7813
|
+
"MAGENTO_API_ERROR",
|
|
7814
|
+
response.status
|
|
7815
|
+
);
|
|
7816
|
+
}
|
|
7817
|
+
const json = await response.json();
|
|
7818
|
+
if (json.errors?.length) {
|
|
7819
|
+
const message = json.errors.map((e) => e.message).join("; ");
|
|
7820
|
+
throw new ProviderAPIError(`Magento GraphQL error: ${message}`, "MAGENTO_GRAPHQL_ERROR");
|
|
7821
|
+
}
|
|
7822
|
+
return json.data;
|
|
7823
|
+
}
|
|
7824
|
+
// ============================================
|
|
7825
|
+
// Product Operations (GraphQL)
|
|
7826
|
+
// ============================================
|
|
7827
|
+
async getProduct(handle) {
|
|
7828
|
+
try {
|
|
7829
|
+
const data = await this.graphql(GET_PRODUCT_BY_URL_KEY, { urlKey: handle });
|
|
7830
|
+
return data.products.items[0] ? transformProduct6(data.products.items[0], this.currencyCode) : void 0;
|
|
7831
|
+
} catch {
|
|
7832
|
+
return void 0;
|
|
7833
|
+
}
|
|
7834
|
+
}
|
|
7835
|
+
async getProducts(options) {
|
|
7836
|
+
const variables = {
|
|
7837
|
+
pageSize: options?.first ?? 20,
|
|
7838
|
+
currentPage: 1
|
|
7839
|
+
};
|
|
7840
|
+
if (options?.query) variables.search = options.query;
|
|
7841
|
+
if (options?.sortKey) {
|
|
7842
|
+
const sortMap = {
|
|
7843
|
+
"price-asc": { price: "ASC" },
|
|
7844
|
+
"price-desc": { price: "DESC" },
|
|
7845
|
+
"title-asc": { name: "ASC" },
|
|
7846
|
+
"title-desc": { name: "DESC" },
|
|
7847
|
+
"created-desc": { created_at: "DESC" },
|
|
7848
|
+
"relevance": { relevance: "DESC" }
|
|
7849
|
+
};
|
|
7850
|
+
variables.sort = sortMap[options.sortKey] ?? { relevance: "DESC" };
|
|
7851
|
+
}
|
|
7852
|
+
const data = await this.graphql(GET_PRODUCTS2, variables);
|
|
7853
|
+
return (data.products?.items ?? []).map((p) => transformProduct6(p, this.currencyCode));
|
|
7854
|
+
}
|
|
7855
|
+
async getProductRecommendations(_productId) {
|
|
7856
|
+
return [];
|
|
7857
|
+
}
|
|
7858
|
+
// ============================================
|
|
7859
|
+
// Collection/Category Operations (GraphQL)
|
|
7860
|
+
// ============================================
|
|
7861
|
+
async getCollection(handle) {
|
|
7862
|
+
try {
|
|
7863
|
+
const data = await this.graphql(GET_CATEGORY_BY_URL_KEY, { urlKey: handle });
|
|
7864
|
+
return data.categories.items[0] ? transformCollection6(data.categories.items[0]) : void 0;
|
|
7865
|
+
} catch {
|
|
7866
|
+
return void 0;
|
|
7867
|
+
}
|
|
7868
|
+
}
|
|
7869
|
+
async getCollections(options) {
|
|
7870
|
+
const data = await this.graphql(GET_CATEGORIES, {
|
|
7871
|
+
pageSize: options?.limit ?? 50
|
|
7872
|
+
});
|
|
7873
|
+
return (data.categories?.items ?? []).map(transformCollection6);
|
|
7874
|
+
}
|
|
7875
|
+
async getCollectionProducts(handle, options) {
|
|
7876
|
+
const data = await this.graphql(GET_CATEGORY_PRODUCTS, {
|
|
7877
|
+
urlKey: handle,
|
|
7878
|
+
pageSize: options?.first ?? 20
|
|
7879
|
+
});
|
|
7880
|
+
return (data.products?.items ?? []).map((p) => transformProduct6(p, this.currencyCode));
|
|
7881
|
+
}
|
|
7882
|
+
async getCategory(handle) {
|
|
7883
|
+
return this.getCollection(handle);
|
|
7884
|
+
}
|
|
7885
|
+
async getCategories(options) {
|
|
7886
|
+
return this.getCollections(options);
|
|
7887
|
+
}
|
|
7888
|
+
async getCategoryProducts(handle, options) {
|
|
7889
|
+
return this.getCollectionProducts(handle, options);
|
|
5402
7890
|
}
|
|
5403
7891
|
// ============================================
|
|
5404
|
-
//
|
|
7892
|
+
// Cart Operations (GraphQL)
|
|
5405
7893
|
// ============================================
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
* Get per-channel storefront configuration from Thor Commerce plugin.
|
|
5414
|
-
* Calls `GET /store/thor/config`.
|
|
5415
|
-
*
|
|
5416
|
-
* @returns StorefrontConfig or null if plugin is not installed / config not set
|
|
5417
|
-
*/
|
|
5418
|
-
async getStorefrontConfig() {
|
|
5419
|
-
if (this._storefrontConfigCache && Date.now() < this._storefrontConfigCache.expiresAt) {
|
|
5420
|
-
return this._storefrontConfigCache.config;
|
|
5421
|
-
}
|
|
7894
|
+
async createCart() {
|
|
7895
|
+
const data = await this.graphql(CREATE_EMPTY_CART);
|
|
7896
|
+
const cartId = data.createEmptyCart;
|
|
7897
|
+
const cartData = await this.graphql(GET_CART2, { cartId });
|
|
7898
|
+
return transformCart6(cartData.cart ?? { id: cartId, items: [], prices: {} }, this.currencyCode);
|
|
7899
|
+
}
|
|
7900
|
+
async getCart(cartId) {
|
|
5422
7901
|
try {
|
|
5423
|
-
const
|
|
5424
|
-
|
|
5425
|
-
|
|
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._storefrontConfigCache = { config: null, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5437
|
-
return null;
|
|
5438
|
-
}
|
|
5439
|
-
if (!response.ok) {
|
|
5440
|
-
this.logger.warn("[StorefrontConfig] Non-OK response", { status: response.status });
|
|
5441
|
-
return null;
|
|
5442
|
-
}
|
|
5443
|
-
const data = await response.json();
|
|
5444
|
-
const config = transformStorefrontConfig(data.storefront_config ?? data);
|
|
5445
|
-
this._storefrontConfigCache = { config, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5446
|
-
this.logger.debug("[StorefrontConfig] Loaded from backend", { id: config.id });
|
|
5447
|
-
return config;
|
|
5448
|
-
} catch (error) {
|
|
5449
|
-
this.logger.warn("[StorefrontConfig] Failed to fetch", {
|
|
5450
|
-
error: error instanceof Error ? error.message : String(error)
|
|
5451
|
-
});
|
|
5452
|
-
return null;
|
|
7902
|
+
const data = await this.graphql(GET_CART2, { cartId });
|
|
7903
|
+
return data.cart ? transformCart6(data.cart, this.currencyCode) : void 0;
|
|
7904
|
+
} catch {
|
|
7905
|
+
return void 0;
|
|
5453
7906
|
}
|
|
5454
7907
|
}
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
url,
|
|
5469
|
-
{
|
|
5470
|
-
method: "GET",
|
|
5471
|
-
headers: {
|
|
5472
|
-
"x-publishable-api-key": this.publishableKey,
|
|
5473
|
-
"Content-Type": "application/json"
|
|
5474
|
-
}
|
|
5475
|
-
},
|
|
5476
|
-
this.retryConfig
|
|
7908
|
+
async addToCart(cartId, lines) {
|
|
7909
|
+
const cartItems = lines.map((l) => ({
|
|
7910
|
+
sku: l.merchandiseId,
|
|
7911
|
+
quantity: l.quantity
|
|
7912
|
+
}));
|
|
7913
|
+
const data = await this.graphql(ADD_TO_CART, {
|
|
7914
|
+
cartId,
|
|
7915
|
+
cartItems
|
|
7916
|
+
});
|
|
7917
|
+
if (data.addProductsToCart.user_errors?.length) {
|
|
7918
|
+
throw new ProviderAPIError(
|
|
7919
|
+
`Add to cart failed: ${data.addProductsToCart.user_errors.map((e) => e.message).join("; ")}`,
|
|
7920
|
+
"MAGENTO_CART_ERROR"
|
|
5477
7921
|
);
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
const result = {
|
|
5488
|
-
version: data.version ?? "",
|
|
5489
|
-
config: data.config
|
|
5490
|
-
};
|
|
5491
|
-
this._designerConfigCache = { result, expiresAt: Date.now() + this.THOR_CONFIG_CACHE_TTL };
|
|
5492
|
-
this.logger.debug("[DesignerConfig] Loaded from backend", { version: result.version });
|
|
5493
|
-
return result;
|
|
5494
|
-
} catch (error) {
|
|
5495
|
-
this.logger.warn("[DesignerConfig] Failed to fetch", {
|
|
5496
|
-
error: error instanceof Error ? error.message : String(error)
|
|
7922
|
+
}
|
|
7923
|
+
return transformCart6(data.addProductsToCart.cart, this.currencyCode);
|
|
7924
|
+
}
|
|
7925
|
+
async removeFromCart(cartId, lineIds) {
|
|
7926
|
+
let cart;
|
|
7927
|
+
for (const itemUid of lineIds) {
|
|
7928
|
+
const data = await this.graphql(REMOVE_FROM_CART, {
|
|
7929
|
+
cartId,
|
|
7930
|
+
cartItemUid: itemUid
|
|
5497
7931
|
});
|
|
5498
|
-
|
|
7932
|
+
cart = data.removeItemFromCart.cart;
|
|
5499
7933
|
}
|
|
7934
|
+
return transformCart6(cart ?? { id: cartId, items: [], prices: {} }, this.currencyCode);
|
|
7935
|
+
}
|
|
7936
|
+
async updateCart(cartId, lines) {
|
|
7937
|
+
const cartItems = lines.map((l) => ({
|
|
7938
|
+
cart_item_uid: l.id,
|
|
7939
|
+
quantity: l.quantity
|
|
7940
|
+
}));
|
|
7941
|
+
const data = await this.graphql(UPDATE_CART_ITEMS, {
|
|
7942
|
+
cartId,
|
|
7943
|
+
cartItems
|
|
7944
|
+
});
|
|
7945
|
+
return transformCart6(data.updateCartItems.cart, this.currencyCode);
|
|
5500
7946
|
}
|
|
5501
7947
|
// ============================================
|
|
5502
|
-
//
|
|
7948
|
+
// Checkout Operations
|
|
5503
7949
|
// ============================================
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
return this.makeAdminRequest(async () => {
|
|
5510
|
-
const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}`;
|
|
5511
|
-
let data;
|
|
5512
|
-
try {
|
|
5513
|
-
data = await this.sdk.client.fetch(path, { method: "GET" });
|
|
5514
|
-
} catch (error) {
|
|
5515
|
-
if (error?.status === 404) return null;
|
|
5516
|
-
throw new ProviderAPIError(
|
|
5517
|
-
`Failed to fetch admin designer config: ${error?.message ?? error?.status}`,
|
|
5518
|
-
"medusa",
|
|
5519
|
-
error?.status
|
|
5520
|
-
);
|
|
5521
|
-
}
|
|
5522
|
-
return {
|
|
5523
|
-
version: data.version ?? "",
|
|
5524
|
-
config: data.config,
|
|
5525
|
-
history: data.history
|
|
5526
|
-
};
|
|
5527
|
-
});
|
|
7950
|
+
async completeCheckout(_cartId) {
|
|
7951
|
+
throw new ProviderAPIError(
|
|
7952
|
+
"Magento checkout requires multi-step process (set addresses, shipping, payment, place order). Use the Magento checkout flow.",
|
|
7953
|
+
"MAGENTO_CHECKOUT_MULTI_STEP"
|
|
7954
|
+
);
|
|
5528
7955
|
}
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
error?.status
|
|
5548
|
-
);
|
|
5549
|
-
}
|
|
5550
|
-
return {
|
|
5551
|
-
version: data.version ?? "",
|
|
5552
|
-
config: data.config
|
|
5553
|
-
};
|
|
7956
|
+
async getShippingMethods(_cartId) {
|
|
7957
|
+
return [];
|
|
7958
|
+
}
|
|
7959
|
+
async getRegionCountries() {
|
|
7960
|
+
return [];
|
|
7961
|
+
}
|
|
7962
|
+
async addShippingMethod(_cartId, _methodId) {
|
|
7963
|
+
throw new ProviderAPIError(
|
|
7964
|
+
"Magento shipping selection requires set-shipping-methods mutation.",
|
|
7965
|
+
"MAGENTO_CHECKOUT_MULTI_STEP"
|
|
7966
|
+
);
|
|
7967
|
+
}
|
|
7968
|
+
async initiatePaymentSession(_cartId, _providerId) {
|
|
7969
|
+
}
|
|
7970
|
+
async applyDiscountCode(cartId, code) {
|
|
7971
|
+
const data = await this.graphql(APPLY_COUPON, {
|
|
7972
|
+
cartId,
|
|
7973
|
+
couponCode: code
|
|
5554
7974
|
});
|
|
7975
|
+
return transformCart6(data.applyCouponToCart.cart, this.currencyCode);
|
|
5555
7976
|
}
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
*/
|
|
5560
|
-
async restoreAdminDesignerConfig(salesChannelId, version) {
|
|
5561
|
-
return this.makeAdminRequest(async () => {
|
|
5562
|
-
const path = `/admin/thor/site-config/${encodeURIComponent(salesChannelId)}/restore/${encodeURIComponent(version)}`;
|
|
5563
|
-
let data;
|
|
5564
|
-
try {
|
|
5565
|
-
data = await this.sdk.client.fetch(path, { method: "POST" });
|
|
5566
|
-
} catch (error) {
|
|
5567
|
-
throw new ProviderAPIError(
|
|
5568
|
-
`Failed to restore admin designer config: ${error?.message ?? error?.status}`,
|
|
5569
|
-
"medusa",
|
|
5570
|
-
error?.status
|
|
5571
|
-
);
|
|
5572
|
-
}
|
|
5573
|
-
return {
|
|
5574
|
-
version: data.version ?? "",
|
|
5575
|
-
config: data.config
|
|
5576
|
-
};
|
|
7977
|
+
async removeDiscountCode(cartId, _code) {
|
|
7978
|
+
const data = await this.graphql(REMOVE_COUPON, {
|
|
7979
|
+
cartId
|
|
5577
7980
|
});
|
|
7981
|
+
return transformCart6(data.removeCouponFromCart.cart, this.currencyCode);
|
|
7982
|
+
}
|
|
7983
|
+
// ============================================
|
|
7984
|
+
// Customer & Orders (GraphQL — requires customerToken)
|
|
7985
|
+
// ============================================
|
|
7986
|
+
async getOrders(_options) {
|
|
7987
|
+
if (!this.customerToken) return [];
|
|
7988
|
+
try {
|
|
7989
|
+
const data = await this.graphql(GET_CUSTOMER_ORDERS, {
|
|
7990
|
+
pageSize: 20,
|
|
7991
|
+
currentPage: 1
|
|
7992
|
+
});
|
|
7993
|
+
return (data.customer?.orders?.items ?? []).map((o) => transformOrder6(o, this.currencyCode));
|
|
7994
|
+
} catch {
|
|
7995
|
+
return [];
|
|
7996
|
+
}
|
|
7997
|
+
}
|
|
7998
|
+
async getPaymentMethods(_options) {
|
|
7999
|
+
return [];
|
|
8000
|
+
}
|
|
8001
|
+
// ============================================
|
|
8002
|
+
// Inventory
|
|
8003
|
+
// ============================================
|
|
8004
|
+
async getStockLocations() {
|
|
8005
|
+
return [];
|
|
8006
|
+
}
|
|
8007
|
+
async checkInventoryLevels() {
|
|
8008
|
+
return [];
|
|
8009
|
+
}
|
|
8010
|
+
async validateCartStock() {
|
|
8011
|
+
return { allAvailable: true, unavailableItems: [] };
|
|
8012
|
+
}
|
|
8013
|
+
async getFulfillmentOptions() {
|
|
8014
|
+
return [];
|
|
8015
|
+
}
|
|
8016
|
+
// ============================================
|
|
8017
|
+
// Admin Operations
|
|
8018
|
+
// ============================================
|
|
8019
|
+
async cancelOrder(_orderId) {
|
|
8020
|
+
throw new ProviderAPIError("Order cancellation requires Magento Admin API.", "MAGENTO_ADMIN_REQUIRED");
|
|
8021
|
+
}
|
|
8022
|
+
async createFulfillment(_orderId) {
|
|
8023
|
+
throw new ProviderAPIError("Fulfillment requires Magento Admin API.", "MAGENTO_ADMIN_REQUIRED");
|
|
8024
|
+
}
|
|
8025
|
+
async createRefund(_orderId) {
|
|
8026
|
+
throw new ProviderAPIError("Refunds require Magento Admin API.", "MAGENTO_ADMIN_REQUIRED");
|
|
8027
|
+
}
|
|
8028
|
+
async getAdminOrders() {
|
|
8029
|
+
return { orders: [], count: 0 };
|
|
8030
|
+
}
|
|
8031
|
+
// ============================================
|
|
8032
|
+
// Storefront / Multi-Tenant
|
|
8033
|
+
// ============================================
|
|
8034
|
+
async getStorefrontContext() {
|
|
8035
|
+
return {
|
|
8036
|
+
id: this.storeCode,
|
|
8037
|
+
name: `Magento ${this.storeCode}`,
|
|
8038
|
+
platformType: "magento",
|
|
8039
|
+
requiresProductScoping: true,
|
|
8040
|
+
currencyCode: this.currencyCode
|
|
8041
|
+
};
|
|
8042
|
+
}
|
|
8043
|
+
isStorefrontRequired() {
|
|
8044
|
+
return true;
|
|
5578
8045
|
}
|
|
5579
8046
|
};
|
|
5580
8047
|
|
|
@@ -7609,16 +10076,6 @@ var MockProvider = class {
|
|
|
7609
10076
|
};
|
|
7610
10077
|
|
|
7611
10078
|
// src/factory/index.ts
|
|
7612
|
-
var registry = /* @__PURE__ */ new Map();
|
|
7613
|
-
function registerProvider(type, factory) {
|
|
7614
|
-
registry.set(type, factory);
|
|
7615
|
-
}
|
|
7616
|
-
function getRegisteredProviders() {
|
|
7617
|
-
return Array.from(registry.keys());
|
|
7618
|
-
}
|
|
7619
|
-
registerProvider("mock", (config) => new MockProvider(config));
|
|
7620
|
-
registerProvider("medusa", (config) => new MedusaProvider(config));
|
|
7621
|
-
registerProvider("stelorder", (config) => new StelOrderProvider(config));
|
|
7622
10079
|
function createProvider(options) {
|
|
7623
10080
|
if (!options || !options.provider) {
|
|
7624
10081
|
throw new ConfigurationError("Provider type is required");
|
|
@@ -7626,14 +10083,27 @@ function createProvider(options) {
|
|
|
7626
10083
|
if (!options.config) {
|
|
7627
10084
|
throw new ConfigurationError("Provider configuration is required");
|
|
7628
10085
|
}
|
|
7629
|
-
const
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
10086
|
+
const config = options.config;
|
|
10087
|
+
switch (options.provider) {
|
|
10088
|
+
case "mock":
|
|
10089
|
+
return new MockProvider(config);
|
|
10090
|
+
case "medusa":
|
|
10091
|
+
return new MedusaProvider(config);
|
|
10092
|
+
case "stelorder":
|
|
10093
|
+
return new StelOrderProvider(config);
|
|
10094
|
+
case "shopify":
|
|
10095
|
+
return new ShopifyProvider(config);
|
|
10096
|
+
case "bigcommerce":
|
|
10097
|
+
return new BigCommerceProvider(config);
|
|
10098
|
+
case "woocommerce":
|
|
10099
|
+
return new WooCommerceProvider(config);
|
|
10100
|
+
case "spree":
|
|
10101
|
+
return new SpreeProvider(config);
|
|
10102
|
+
case "magento":
|
|
10103
|
+
return new MagentoProvider(config);
|
|
10104
|
+
default:
|
|
10105
|
+
throw new ProviderNotFoundError(String(options.provider));
|
|
7635
10106
|
}
|
|
7636
|
-
return factory(options.config);
|
|
7637
10107
|
}
|
|
7638
10108
|
function isProviderType(provider, type) {
|
|
7639
10109
|
return provider.name === type;
|
|
@@ -7641,6 +10111,8 @@ function isProviderType(provider, type) {
|
|
|
7641
10111
|
export {
|
|
7642
10112
|
AdminAuthService,
|
|
7643
10113
|
AdminSessionService,
|
|
10114
|
+
BIGCOMMERCE_CAPABILITIES,
|
|
10115
|
+
BigCommerceProvider,
|
|
7644
10116
|
CHANNEL_ASSIGNED_AT_KEY,
|
|
7645
10117
|
CHANNEL_ASSIGNED_BY_KEY,
|
|
7646
10118
|
CHANNEL_SCOPE_KEY,
|
|
@@ -7649,8 +10121,10 @@ export {
|
|
|
7649
10121
|
CircuitBreakerOpenError,
|
|
7650
10122
|
CommerceError,
|
|
7651
10123
|
ConfigurationError,
|
|
10124
|
+
MAGENTO_CAPABILITIES,
|
|
7652
10125
|
MEDUSA_CAPABILITIES,
|
|
7653
10126
|
MOCK_CAPABILITIES,
|
|
10127
|
+
MagentoProvider,
|
|
7654
10128
|
MedusaMetadataAdapter,
|
|
7655
10129
|
MedusaProvider,
|
|
7656
10130
|
MetricsService,
|
|
@@ -7658,14 +10132,21 @@ export {
|
|
|
7658
10132
|
ProviderAPIError,
|
|
7659
10133
|
ProviderNotFoundError,
|
|
7660
10134
|
ResourceNotFoundError,
|
|
10135
|
+
SHOPIFY_CAPABILITIES,
|
|
10136
|
+
SPREE_CAPABILITIES,
|
|
7661
10137
|
STELORDER_CAPABILITIES,
|
|
10138
|
+
ShopifyProvider,
|
|
10139
|
+
SpreeProvider,
|
|
7662
10140
|
StelOrderProvider,
|
|
7663
10141
|
StorefrontConfigError2 as StorefrontConfigError,
|
|
10142
|
+
WOOCOMMERCE_CAPABILITIES,
|
|
10143
|
+
WooCommerceProvider,
|
|
7664
10144
|
assertEntityBelongsToChannel,
|
|
7665
10145
|
backendCircuitBreaker,
|
|
7666
10146
|
buildChannelScopeMetadata,
|
|
7667
10147
|
calculateDistance,
|
|
7668
10148
|
createBackendMetadataAdapter,
|
|
10149
|
+
createFetchWithRetry,
|
|
7669
10150
|
createLogger,
|
|
7670
10151
|
createProvider,
|
|
7671
10152
|
extractErrorMessage,
|
|
@@ -7676,7 +10157,6 @@ export {
|
|
|
7676
10157
|
getBackendProviderId,
|
|
7677
10158
|
getCapabilityDescription,
|
|
7678
10159
|
getChannelScope,
|
|
7679
|
-
getRegisteredProviders,
|
|
7680
10160
|
isBackendCapabilities,
|
|
7681
10161
|
isGlobalEntity,
|
|
7682
10162
|
isProviderType,
|
|
@@ -7686,7 +10166,6 @@ export {
|
|
|
7686
10166
|
numberToMoney,
|
|
7687
10167
|
partitionByChannel,
|
|
7688
10168
|
planFulfillment,
|
|
7689
|
-
registerProvider,
|
|
7690
10169
|
transformFulfillmentOption,
|
|
7691
10170
|
transformInventoryLevel,
|
|
7692
10171
|
transformStockLocation,
|