@thorprovider/adapters 4.2.0 → 4.3.0
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/CHANGELOG.md +6 -0
- package/PROVIDER_IMPLEMENTATION_ANALYSIS.md +659 -0
- package/README.md +19 -0
- package/dist/index.d.mts +249 -2
- package/dist/index.d.ts +249 -2
- package/dist/index.js +1450 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1448 -2
- package/dist/index.mjs.map +1 -1
- package/docs/RETRY_SOLUTION_SUMMARY.md +26 -11
- package/docs/authentication.md +115 -62
- package/docs/capabilities-system.md +34 -2
- package/docs/category-customization.md +37 -29
- package/docs/fetch-reliability-solution.md +127 -278
- package/docs/getting-started.md +43 -29
- package/docs/profile-management.md +68 -38
- package/docs/providers-overview.md +14 -0
- package/docs/services-overview.md +20 -2
- package/package.json +10 -6
- package/quick-reference.md +34 -0
- package/src/factory/index.ts +4 -0
- package/src/index.ts +3 -0
- package/src/providers/index.ts +3 -0
- package/src/providers/medusa/admin.test.ts +404 -0
- package/src/providers/medusa/admin.ts +442 -0
- package/src/providers/medusa/auth.test.ts +137 -0
- package/src/providers/medusa/auth.ts +96 -1
- package/src/providers/medusa/index.ts +21 -1
- package/src/providers/shopify/auth.ts +6 -0
- package/src/providers/stelorder/client.test-helpers.ts +3 -0
- package/src/providers/stelorder/client.test.ts +58 -0
- package/src/providers/stelorder/client.ts +326 -0
- package/src/providers/stelorder/index.test.ts +48 -0
- package/src/providers/stelorder/index.ts +685 -0
- package/src/providers/stelorder/live-smoke.test.ts +54 -0
- package/src/providers/stelorder/transforms.test.ts +103 -0
- package/src/providers/stelorder/transforms.ts +631 -0
- package/src/resilience/README.md +70 -48
- package/src/types/capabilities.types.ts +51 -0
- package/vitest.config.ts +13 -0
- package/vitest.setup.ts +6 -0
- package/.turbo/turbo-build.log +0 -18
- package/dist/.tsbuildinfo +0 -1
- package/src/core/errors.js +0 -161
- package/src/core/fetch-utils.js +0 -240
- package/src/core/logger.js +0 -63
- package/src/metadata/channel-scope-validation.js +0 -136
- package/src/metadata/channel-scope.js +0 -174
- package/src/metadata/index.js +0 -31
- package/src/providers/medusa/auth.js +0 -601
- package/src/providers/medusa/index.js +0 -2485
- package/src/providers/medusa/payment.js +0 -265
- package/src/providers/medusa/transforms.js +0 -505
- package/src/types/capabilities.types.js +0 -141
- package/src/types/provider.js +0 -9
- package/src/utils/payment-utils.js +0 -152
package/dist/index.js
CHANGED
|
@@ -55,8 +55,10 @@ __export(src_exports, {
|
|
|
55
55
|
ResourceNotFoundError: () => ResourceNotFoundError,
|
|
56
56
|
SHOPIFY_CAPABILITIES: () => SHOPIFY_CAPABILITIES,
|
|
57
57
|
SPREE_CAPABILITIES: () => SPREE_CAPABILITIES,
|
|
58
|
+
STELORDER_CAPABILITIES: () => STELORDER_CAPABILITIES,
|
|
58
59
|
ShopifyProvider: () => ShopifyProvider,
|
|
59
60
|
SpreeProvider: () => SpreeProvider,
|
|
61
|
+
StelOrderProvider: () => StelOrderProvider,
|
|
60
62
|
StorefrontConfigError: () => import_types2.StorefrontConfigError,
|
|
61
63
|
WOOCOMMERCE_CAPABILITIES: () => WOOCOMMERCE_CAPABILITIES,
|
|
62
64
|
WooCommerceProvider: () => WooCommerceProvider,
|
|
@@ -163,6 +165,41 @@ var MEDUSA_CAPABILITIES = {
|
|
|
163
165
|
loyaltyProgram: false
|
|
164
166
|
// Requires custom implementation
|
|
165
167
|
};
|
|
168
|
+
var STELORDER_CAPABILITIES = {
|
|
169
|
+
// Profile Management
|
|
170
|
+
updateProfile: false,
|
|
171
|
+
updateEmail: false,
|
|
172
|
+
changePasswordDirect: false,
|
|
173
|
+
changePasswordViaEmail: false,
|
|
174
|
+
deleteAccount: false,
|
|
175
|
+
// Avatar
|
|
176
|
+
uploadAvatar: false,
|
|
177
|
+
avatarViaMetadata: false,
|
|
178
|
+
// Custom Attributes
|
|
179
|
+
customAttributes: false,
|
|
180
|
+
// Orders
|
|
181
|
+
fetchOrders: true,
|
|
182
|
+
cancelOrder: false,
|
|
183
|
+
// Addresses
|
|
184
|
+
manageAddresses: true,
|
|
185
|
+
defaultAddress: false,
|
|
186
|
+
// Cart/Checkout
|
|
187
|
+
applyDiscounts: false,
|
|
188
|
+
shippingCalculation: false,
|
|
189
|
+
guestCheckout: false,
|
|
190
|
+
savedPaymentMethods: false,
|
|
191
|
+
listPaymentProviders: true,
|
|
192
|
+
regionSpecificPayments: false,
|
|
193
|
+
// Wishlist
|
|
194
|
+
wishlist: false,
|
|
195
|
+
// Search
|
|
196
|
+
advancedSearch: false,
|
|
197
|
+
facetedFilters: false,
|
|
198
|
+
// Reviews
|
|
199
|
+
productReviews: false,
|
|
200
|
+
// Loyalty
|
|
201
|
+
loyaltyProgram: false
|
|
202
|
+
};
|
|
166
203
|
var SHOPIFY_CAPABILITIES = {
|
|
167
204
|
// Profile Management
|
|
168
205
|
updateProfile: true,
|
|
@@ -1786,11 +1823,17 @@ var MedusaAuthProvider = class {
|
|
|
1786
1823
|
logger;
|
|
1787
1824
|
authConfig;
|
|
1788
1825
|
storageManager;
|
|
1789
|
-
|
|
1826
|
+
/** Medusa backend base URL — needed for raw fetch calls */
|
|
1827
|
+
baseUrl;
|
|
1828
|
+
/** Publishable API key — sent as x-publishable-api-key on store calls */
|
|
1829
|
+
publishableKey;
|
|
1830
|
+
constructor(sdk, authConfig, debug = false, baseUrl = "", publishableKey = "") {
|
|
1790
1831
|
this.sdk = sdk;
|
|
1791
1832
|
this.authConfig = authConfig;
|
|
1792
1833
|
this.logger = createLogger("MedusaAuthProvider", debug);
|
|
1793
1834
|
this.storageManager = new AuthStorageManager(authConfig);
|
|
1835
|
+
this.baseUrl = baseUrl;
|
|
1836
|
+
this.publishableKey = publishableKey;
|
|
1794
1837
|
this.logger.info("Medusa Auth Provider initialized", {
|
|
1795
1838
|
method: authConfig.method,
|
|
1796
1839
|
storage: authConfig.storage
|
|
@@ -2066,6 +2109,81 @@ var MedusaAuthProvider = class {
|
|
|
2066
2109
|
void 0
|
|
2067
2110
|
);
|
|
2068
2111
|
}
|
|
2112
|
+
// ========================================
|
|
2113
|
+
// Multi-Tenant — Channel Link
|
|
2114
|
+
// ========================================
|
|
2115
|
+
/**
|
|
2116
|
+
* Link the currently authenticated customer to the storefront's sales channel.
|
|
2117
|
+
*
|
|
2118
|
+
* Must be called immediately after a successful `login()` so the customer
|
|
2119
|
+
* appears in the admin panel of the correct storefront channel.
|
|
2120
|
+
*
|
|
2121
|
+
* Implements `POST /store/thor/customers/me/channels`.
|
|
2122
|
+
* Idempotent — returns `{ linked: false, reason: 'already_linked' }` when
|
|
2123
|
+
* the link already exists.
|
|
2124
|
+
*
|
|
2125
|
+
* @throws {ProviderAPIError} When not authenticated (401) or channel cannot
|
|
2126
|
+
* be resolved from the publishable key (400).
|
|
2127
|
+
*/
|
|
2128
|
+
async linkCustomerToChannel() {
|
|
2129
|
+
this.logger.debug("linkCustomerToChannel");
|
|
2130
|
+
if (!this.baseUrl || !this.publishableKey) {
|
|
2131
|
+
throw new ProviderAPIError(
|
|
2132
|
+
"linkCustomerToChannel requires baseUrl and publishableKey to be set on the auth provider.",
|
|
2133
|
+
"CONFIGURATION_ERROR",
|
|
2134
|
+
void 0
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
2137
|
+
const token = await this.storageManager.getToken();
|
|
2138
|
+
const headers = {
|
|
2139
|
+
"x-publishable-api-key": this.publishableKey,
|
|
2140
|
+
"Content-Type": "application/json"
|
|
2141
|
+
};
|
|
2142
|
+
if (token) {
|
|
2143
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
2144
|
+
}
|
|
2145
|
+
try {
|
|
2146
|
+
const url = `${this.baseUrl}/store/thor/customers/me/channels`;
|
|
2147
|
+
const response = await fetch(url, {
|
|
2148
|
+
method: "POST",
|
|
2149
|
+
headers
|
|
2150
|
+
});
|
|
2151
|
+
if (response.status === 401) {
|
|
2152
|
+
throw new ProviderAPIError(
|
|
2153
|
+
"linkCustomerToChannel: not authenticated. Call login() first.",
|
|
2154
|
+
"NOT_AUTHENTICATED",
|
|
2155
|
+
401
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
if (response.status === 400) {
|
|
2159
|
+
const body = await response.json().catch(() => ({}));
|
|
2160
|
+
throw new ProviderAPIError(
|
|
2161
|
+
`linkCustomerToChannel: could not resolve sales channel from publishable key. ${body?.message ?? ""}`.trim(),
|
|
2162
|
+
"CHANNEL_RESOLUTION_FAILED",
|
|
2163
|
+
400
|
|
2164
|
+
);
|
|
2165
|
+
}
|
|
2166
|
+
if (!response.ok) {
|
|
2167
|
+
const body = await response.json().catch(() => ({}));
|
|
2168
|
+
throw new ProviderAPIError(
|
|
2169
|
+
`linkCustomerToChannel failed with status ${response.status}: ${body?.message ?? "Unknown error"}`,
|
|
2170
|
+
"LINK_CHANNEL_FAILED",
|
|
2171
|
+
response.status
|
|
2172
|
+
);
|
|
2173
|
+
}
|
|
2174
|
+
const data = await response.json();
|
|
2175
|
+
this.logger.info("Customer linked to channel", { linked: data.linked, reason: data.reason });
|
|
2176
|
+
return data;
|
|
2177
|
+
} catch (error) {
|
|
2178
|
+
if (error instanceof ProviderAPIError) throw error;
|
|
2179
|
+
this.logger.error("linkCustomerToChannel failed", error);
|
|
2180
|
+
throw new ProviderAPIError(
|
|
2181
|
+
`linkCustomerToChannel failed: ${error.message}`,
|
|
2182
|
+
"LINK_CHANNEL_FAILED",
|
|
2183
|
+
error
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2069
2187
|
};
|
|
2070
2188
|
|
|
2071
2189
|
// src/providers/medusa/payment.ts
|
|
@@ -2208,6 +2326,290 @@ async function getPaymentMethods(sdk, options) {
|
|
|
2208
2326
|
}
|
|
2209
2327
|
}
|
|
2210
2328
|
|
|
2329
|
+
// src/providers/medusa/admin.ts
|
|
2330
|
+
var MedusaAdminClient = class {
|
|
2331
|
+
baseUrl;
|
|
2332
|
+
apiKey;
|
|
2333
|
+
logger;
|
|
2334
|
+
constructor(config) {
|
|
2335
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
2336
|
+
this.apiKey = config.apiKey;
|
|
2337
|
+
this.logger = createLogger("MedusaAdminClient", config.debug);
|
|
2338
|
+
}
|
|
2339
|
+
// -----------------------------------------------------------------------
|
|
2340
|
+
// Private — HTTP helper
|
|
2341
|
+
// -----------------------------------------------------------------------
|
|
2342
|
+
async request(method, path, body) {
|
|
2343
|
+
const url = `${this.baseUrl}${path}`;
|
|
2344
|
+
this.logger.debug(`[admin] ${method} ${path}`);
|
|
2345
|
+
const headers = {
|
|
2346
|
+
"x-medusa-access-token": this.apiKey,
|
|
2347
|
+
"Content-Type": "application/json"
|
|
2348
|
+
};
|
|
2349
|
+
const init = {
|
|
2350
|
+
method,
|
|
2351
|
+
headers,
|
|
2352
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
2353
|
+
};
|
|
2354
|
+
let response;
|
|
2355
|
+
try {
|
|
2356
|
+
response = await fetch(url, init);
|
|
2357
|
+
} catch (networkError) {
|
|
2358
|
+
throw new ProviderAPIError(
|
|
2359
|
+
`[admin] Network error on ${method} ${path}: ${networkError.message}`,
|
|
2360
|
+
"NETWORK_ERROR",
|
|
2361
|
+
networkError
|
|
2362
|
+
);
|
|
2363
|
+
}
|
|
2364
|
+
if (!response.ok) {
|
|
2365
|
+
let message = `HTTP ${response.status}`;
|
|
2366
|
+
try {
|
|
2367
|
+
const json = await response.json();
|
|
2368
|
+
message = json?.message ?? message;
|
|
2369
|
+
} catch {
|
|
2370
|
+
}
|
|
2371
|
+
throw new ProviderAPIError(
|
|
2372
|
+
`[admin] ${method} ${path} failed: ${message}`,
|
|
2373
|
+
`ADMIN_API_${response.status}`,
|
|
2374
|
+
response.status
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
return response.json();
|
|
2378
|
+
}
|
|
2379
|
+
// -----------------------------------------------------------------------
|
|
2380
|
+
// 3.1 — Sales Channel: Customers
|
|
2381
|
+
// -----------------------------------------------------------------------
|
|
2382
|
+
/**
|
|
2383
|
+
* Paginated list of customers linked to a sales channel.
|
|
2384
|
+
*
|
|
2385
|
+
* `GET /admin/thor/sales-channels/:id/customers`
|
|
2386
|
+
*/
|
|
2387
|
+
async getChannelCustomers(salesChannelId, options = {}) {
|
|
2388
|
+
const { limit = 20, offset = 0 } = options;
|
|
2389
|
+
return this.request(
|
|
2390
|
+
"GET",
|
|
2391
|
+
`/admin/thor/sales-channels/${salesChannelId}/customers?limit=${limit}&offset=${offset}`
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
// -----------------------------------------------------------------------
|
|
2395
|
+
// 3.2 — Sales Channel: API Keys
|
|
2396
|
+
// -----------------------------------------------------------------------
|
|
2397
|
+
/**
|
|
2398
|
+
* Publishable API keys associated with a sales channel.
|
|
2399
|
+
*
|
|
2400
|
+
* `GET /admin/thor/sales-channels/:id/api-keys`
|
|
2401
|
+
*/
|
|
2402
|
+
async getChannelApiKeys(salesChannelId) {
|
|
2403
|
+
return this.request(
|
|
2404
|
+
"GET",
|
|
2405
|
+
`/admin/thor/sales-channels/${salesChannelId}/api-keys`
|
|
2406
|
+
);
|
|
2407
|
+
}
|
|
2408
|
+
// -----------------------------------------------------------------------
|
|
2409
|
+
// 3.3 — Sales Channel: Categories
|
|
2410
|
+
// -----------------------------------------------------------------------
|
|
2411
|
+
/**
|
|
2412
|
+
* Product categories assigned to a sales channel.
|
|
2413
|
+
*
|
|
2414
|
+
* `GET /admin/thor/sales-channels/:id/categories`
|
|
2415
|
+
*/
|
|
2416
|
+
async getChannelCategories(salesChannelId, options = {}) {
|
|
2417
|
+
const { limit = 20, offset = 0, include_descendants = false } = options;
|
|
2418
|
+
const qs = new URLSearchParams({
|
|
2419
|
+
limit: String(limit),
|
|
2420
|
+
offset: String(offset),
|
|
2421
|
+
include_descendants: String(include_descendants)
|
|
2422
|
+
});
|
|
2423
|
+
return this.request(
|
|
2424
|
+
"GET",
|
|
2425
|
+
`/admin/thor/sales-channels/${salesChannelId}/categories?${qs}`
|
|
2426
|
+
);
|
|
2427
|
+
}
|
|
2428
|
+
// -----------------------------------------------------------------------
|
|
2429
|
+
// 3.4 — Category: Sales Channels
|
|
2430
|
+
// -----------------------------------------------------------------------
|
|
2431
|
+
/**
|
|
2432
|
+
* Sales channels to which a category is assigned.
|
|
2433
|
+
*
|
|
2434
|
+
* `GET /admin/thor/categories/:id/sales-channels`
|
|
2435
|
+
*/
|
|
2436
|
+
async getCategorySalesChannels(categoryId) {
|
|
2437
|
+
return this.request(
|
|
2438
|
+
"GET",
|
|
2439
|
+
`/admin/thor/categories/${categoryId}/sales-channels`
|
|
2440
|
+
);
|
|
2441
|
+
}
|
|
2442
|
+
// -----------------------------------------------------------------------
|
|
2443
|
+
// 3.5 — Category: Assign to Sales Channels
|
|
2444
|
+
// -----------------------------------------------------------------------
|
|
2445
|
+
/**
|
|
2446
|
+
* Assign a category to one or more sales channels (idempotent).
|
|
2447
|
+
*
|
|
2448
|
+
* `POST /admin/thor/categories/:id/sales-channels`
|
|
2449
|
+
*/
|
|
2450
|
+
async assignCategoryToChannels(categoryId, body) {
|
|
2451
|
+
return this.request(
|
|
2452
|
+
"POST",
|
|
2453
|
+
`/admin/thor/categories/${categoryId}/sales-channels`,
|
|
2454
|
+
body
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
// -----------------------------------------------------------------------
|
|
2458
|
+
// 3.6 — Category: Unassign from Sales Channels
|
|
2459
|
+
// -----------------------------------------------------------------------
|
|
2460
|
+
/**
|
|
2461
|
+
* Remove category assignment from one or more sales channels (idempotent).
|
|
2462
|
+
*
|
|
2463
|
+
* `DELETE /admin/thor/categories/:id/sales-channels`
|
|
2464
|
+
*/
|
|
2465
|
+
async unassignCategoryFromChannels(categoryId, body) {
|
|
2466
|
+
return this.request(
|
|
2467
|
+
"DELETE",
|
|
2468
|
+
`/admin/thor/categories/${categoryId}/sales-channels`,
|
|
2469
|
+
body
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
// -----------------------------------------------------------------------
|
|
2473
|
+
// 3.7 — Collection: Sales Channels
|
|
2474
|
+
// -----------------------------------------------------------------------
|
|
2475
|
+
/**
|
|
2476
|
+
* Sales channels to which a collection is assigned.
|
|
2477
|
+
*
|
|
2478
|
+
* `GET /admin/thor/collections/:id/sales-channels`
|
|
2479
|
+
*/
|
|
2480
|
+
async getCollectionSalesChannels(collectionId) {
|
|
2481
|
+
return this.request(
|
|
2482
|
+
"GET",
|
|
2483
|
+
`/admin/thor/collections/${collectionId}/sales-channels`
|
|
2484
|
+
);
|
|
2485
|
+
}
|
|
2486
|
+
// -----------------------------------------------------------------------
|
|
2487
|
+
// 3.8 — Collection: Assign to Sales Channels
|
|
2488
|
+
// -----------------------------------------------------------------------
|
|
2489
|
+
/**
|
|
2490
|
+
* Assign a collection to one or more sales channels (idempotent).
|
|
2491
|
+
*
|
|
2492
|
+
* `POST /admin/thor/collections/:id/sales-channels`
|
|
2493
|
+
*/
|
|
2494
|
+
async assignCollectionToChannels(collectionId, body) {
|
|
2495
|
+
return this.request(
|
|
2496
|
+
"POST",
|
|
2497
|
+
`/admin/thor/collections/${collectionId}/sales-channels`,
|
|
2498
|
+
body
|
|
2499
|
+
);
|
|
2500
|
+
}
|
|
2501
|
+
// -----------------------------------------------------------------------
|
|
2502
|
+
// 3.9 — Collection: Unassign from Sales Channels
|
|
2503
|
+
// -----------------------------------------------------------------------
|
|
2504
|
+
/**
|
|
2505
|
+
* Remove collection assignment from one or more sales channels (idempotent).
|
|
2506
|
+
*
|
|
2507
|
+
* `DELETE /admin/thor/collections/:id/sales-channels`
|
|
2508
|
+
*/
|
|
2509
|
+
async unassignCollectionFromChannels(collectionId, body) {
|
|
2510
|
+
return this.request(
|
|
2511
|
+
"DELETE",
|
|
2512
|
+
`/admin/thor/collections/${collectionId}/sales-channels`,
|
|
2513
|
+
body
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2516
|
+
// -----------------------------------------------------------------------
|
|
2517
|
+
// 3.10 — Storefront Config: Read
|
|
2518
|
+
// -----------------------------------------------------------------------
|
|
2519
|
+
/**
|
|
2520
|
+
* Read the visual storefront configuration for a channel.
|
|
2521
|
+
* `:id` is the `sales_channel_id`.
|
|
2522
|
+
*
|
|
2523
|
+
* `GET /admin/thor/storefront-config/:id`
|
|
2524
|
+
*/
|
|
2525
|
+
async getStorefrontConfig(salesChannelId) {
|
|
2526
|
+
return this.request(
|
|
2527
|
+
"GET",
|
|
2528
|
+
`/admin/thor/storefront-config/${salesChannelId}`
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
// -----------------------------------------------------------------------
|
|
2532
|
+
// 3.11 — Storefront Config: Update
|
|
2533
|
+
// -----------------------------------------------------------------------
|
|
2534
|
+
/**
|
|
2535
|
+
* Update the visual storefront configuration for a channel.
|
|
2536
|
+
* Triggers the `storefront_config.updated` event → ISR webhook.
|
|
2537
|
+
*
|
|
2538
|
+
* `PUT /admin/thor/storefront-config/:id`
|
|
2539
|
+
*/
|
|
2540
|
+
async updateStorefrontConfig(salesChannelId, body) {
|
|
2541
|
+
return this.request(
|
|
2542
|
+
"PUT",
|
|
2543
|
+
`/admin/thor/storefront-config/${salesChannelId}`,
|
|
2544
|
+
body
|
|
2545
|
+
);
|
|
2546
|
+
}
|
|
2547
|
+
// -----------------------------------------------------------------------
|
|
2548
|
+
// 3.12 — Site Designer Config: Read
|
|
2549
|
+
// -----------------------------------------------------------------------
|
|
2550
|
+
/**
|
|
2551
|
+
* Read the active Site Designer configuration for a channel, including the
|
|
2552
|
+
* last 10 history entries.
|
|
2553
|
+
*
|
|
2554
|
+
* `GET /admin/thor/site-config/:sales_channel_id`
|
|
2555
|
+
*/
|
|
2556
|
+
async getSiteConfig(salesChannelId) {
|
|
2557
|
+
return this.request(
|
|
2558
|
+
"GET",
|
|
2559
|
+
`/admin/thor/site-config/${salesChannelId}`
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
// -----------------------------------------------------------------------
|
|
2563
|
+
// 3.13 — Site Designer Config: Update
|
|
2564
|
+
// -----------------------------------------------------------------------
|
|
2565
|
+
/**
|
|
2566
|
+
* Update the Site Designer configuration for a channel.
|
|
2567
|
+
* Creates a new history entry on every PUT.
|
|
2568
|
+
*
|
|
2569
|
+
* `PUT /admin/thor/site-config/:sales_channel_id`
|
|
2570
|
+
*/
|
|
2571
|
+
async updateSiteConfig(salesChannelId, body) {
|
|
2572
|
+
return this.request(
|
|
2573
|
+
"PUT",
|
|
2574
|
+
`/admin/thor/site-config/${salesChannelId}`,
|
|
2575
|
+
body
|
|
2576
|
+
);
|
|
2577
|
+
}
|
|
2578
|
+
// -----------------------------------------------------------------------
|
|
2579
|
+
// 3.14 — Site Designer Config: History
|
|
2580
|
+
// -----------------------------------------------------------------------
|
|
2581
|
+
/**
|
|
2582
|
+
* Full paginated history of Site Designer versions for a channel.
|
|
2583
|
+
* Entries are immutable — they are never deleted.
|
|
2584
|
+
*
|
|
2585
|
+
* `GET /admin/thor/site-config/:sales_channel_id/history`
|
|
2586
|
+
*/
|
|
2587
|
+
async getSiteConfigHistory(salesChannelId, options = {}) {
|
|
2588
|
+
const { limit = 20, offset = 0 } = options;
|
|
2589
|
+
return this.request(
|
|
2590
|
+
"GET",
|
|
2591
|
+
`/admin/thor/site-config/${salesChannelId}/history?limit=${limit}&offset=${offset}`
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
// -----------------------------------------------------------------------
|
|
2595
|
+
// 3.15 — Site Designer Config: Restore
|
|
2596
|
+
// -----------------------------------------------------------------------
|
|
2597
|
+
/**
|
|
2598
|
+
* Restore a previous Site Designer version.
|
|
2599
|
+
* Internally runs the update workflow with the stored config of the target
|
|
2600
|
+
* version — this creates a **new** history entry and does NOT mutate past
|
|
2601
|
+
* entries.
|
|
2602
|
+
*
|
|
2603
|
+
* `POST /admin/thor/site-config/:sales_channel_id/restore/:version`
|
|
2604
|
+
*/
|
|
2605
|
+
async restoreSiteConfig(salesChannelId, version) {
|
|
2606
|
+
return this.request(
|
|
2607
|
+
"POST",
|
|
2608
|
+
`/admin/thor/site-config/${salesChannelId}/restore/${encodeURIComponent(version)}`
|
|
2609
|
+
);
|
|
2610
|
+
}
|
|
2611
|
+
};
|
|
2612
|
+
|
|
2211
2613
|
// src/providers/medusa/index.ts
|
|
2212
2614
|
var MEDUSA_CART_RETRIEVE_FIELDS = "+items,+items.variant,+items.variant.product,+items.variant.product.thumbnail,+items.variant.options,+items.variant.options.option";
|
|
2213
2615
|
var MedusaProvider = class {
|
|
@@ -2242,6 +2644,18 @@ var MedusaProvider = class {
|
|
|
2242
2644
|
* Only created when adminConfig.apiKey is provided.
|
|
2243
2645
|
*/
|
|
2244
2646
|
sdkAdmin;
|
|
2647
|
+
/**
|
|
2648
|
+
* Admin client for Thor multi-tenant endpoints (`/admin/thor/`).
|
|
2649
|
+
* Available when `adminConfig.apiKey` is provided.
|
|
2650
|
+
*
|
|
2651
|
+
* @example
|
|
2652
|
+
* ```typescript
|
|
2653
|
+
* const { customers } = await commerce.admin.getChannelCustomers('sc_01JXXXXX');
|
|
2654
|
+
* const { api_keys } = await commerce.admin.getChannelApiKeys('sc_01JXXXXX');
|
|
2655
|
+
* await commerce.admin.updateStorefrontConfig('sc_01JXXXXX', { theme_accent_color: '#EF4444', ... });
|
|
2656
|
+
* ```
|
|
2657
|
+
*/
|
|
2658
|
+
admin;
|
|
2245
2659
|
/** Cached distinct product status values — rarely changes, cached for process lifetime */
|
|
2246
2660
|
_cachedProductStatuses = null;
|
|
2247
2661
|
/**
|
|
@@ -2312,10 +2726,15 @@ var MedusaProvider = class {
|
|
|
2312
2726
|
debug: config.debug ?? false
|
|
2313
2727
|
// No publishableKey or auth config: this instance is admin-only, no JWT needed
|
|
2314
2728
|
});
|
|
2729
|
+
this.admin = new MedusaAdminClient({
|
|
2730
|
+
baseUrl: config.baseUrl,
|
|
2731
|
+
apiKey: config.adminConfig.apiKey,
|
|
2732
|
+
debug: config.debug
|
|
2733
|
+
});
|
|
2315
2734
|
this.logger.info("[Admin Auth] Dedicated admin SDK instance created with API key");
|
|
2316
2735
|
}
|
|
2317
2736
|
if (config.auth) {
|
|
2318
|
-
this.auth = new MedusaAuthProvider(this.sdk, config.auth, config.debug);
|
|
2737
|
+
this.auth = new MedusaAuthProvider(this.sdk, config.auth, config.debug, config.baseUrl, config.publishableKey);
|
|
2319
2738
|
this.logger.info("Auth provider initialized");
|
|
2320
2739
|
}
|
|
2321
2740
|
this._capabilitiesPromise = this._fetchCapabilities();
|
|
@@ -8069,6 +8488,1031 @@ var MagentoProvider = class {
|
|
|
8069
8488
|
}
|
|
8070
8489
|
};
|
|
8071
8490
|
|
|
8491
|
+
// src/providers/stelorder/transforms.ts
|
|
8492
|
+
function normalizeStelCollection(payload) {
|
|
8493
|
+
if (!payload) {
|
|
8494
|
+
return [];
|
|
8495
|
+
}
|
|
8496
|
+
return Array.isArray(payload) ? payload : [payload];
|
|
8497
|
+
}
|
|
8498
|
+
function normalizeStelEntity(payload) {
|
|
8499
|
+
if (!payload) {
|
|
8500
|
+
return void 0;
|
|
8501
|
+
}
|
|
8502
|
+
return Array.isArray(payload) ? payload[0] : payload;
|
|
8503
|
+
}
|
|
8504
|
+
function transformStelMoney(amount, currencyCode = "USD") {
|
|
8505
|
+
const normalizedAmount = typeof amount === "string" ? Number.parseFloat(amount) : amount ?? 0;
|
|
8506
|
+
return {
|
|
8507
|
+
amount: Number.isFinite(normalizedAmount) ? normalizedAmount.toFixed(2) : "0.00",
|
|
8508
|
+
currencyCode: currencyCode.toUpperCase()
|
|
8509
|
+
};
|
|
8510
|
+
}
|
|
8511
|
+
function transformStelImage(url, altText = "") {
|
|
8512
|
+
const normalizedUrl = url?.trim();
|
|
8513
|
+
if (!normalizedUrl) {
|
|
8514
|
+
return void 0;
|
|
8515
|
+
}
|
|
8516
|
+
return {
|
|
8517
|
+
url: normalizedUrl,
|
|
8518
|
+
altText,
|
|
8519
|
+
width: 800,
|
|
8520
|
+
height: 800
|
|
8521
|
+
};
|
|
8522
|
+
}
|
|
8523
|
+
function normalizeStelDate(value) {
|
|
8524
|
+
if (!value) {
|
|
8525
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
8526
|
+
}
|
|
8527
|
+
const parsed = new Date(value);
|
|
8528
|
+
return Number.isNaN(parsed.getTime()) ? (/* @__PURE__ */ new Date()).toISOString() : parsed.toISOString();
|
|
8529
|
+
}
|
|
8530
|
+
function toStelStringId(value) {
|
|
8531
|
+
if (value === null || value === void 0) {
|
|
8532
|
+
return "";
|
|
8533
|
+
}
|
|
8534
|
+
return String(value);
|
|
8535
|
+
}
|
|
8536
|
+
function slugify(value) {
|
|
8537
|
+
return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8538
|
+
}
|
|
8539
|
+
function buildStelHandle(...candidates) {
|
|
8540
|
+
for (const candidate of candidates) {
|
|
8541
|
+
if (candidate === null || candidate === void 0) {
|
|
8542
|
+
continue;
|
|
8543
|
+
}
|
|
8544
|
+
const normalized = slugify(String(candidate).trim());
|
|
8545
|
+
if (normalized) {
|
|
8546
|
+
return normalized;
|
|
8547
|
+
}
|
|
8548
|
+
}
|
|
8549
|
+
return "stel-item";
|
|
8550
|
+
}
|
|
8551
|
+
function transformStelProductImages(product) {
|
|
8552
|
+
const images = [];
|
|
8553
|
+
if (!product["item-images"]) {
|
|
8554
|
+
return images;
|
|
8555
|
+
}
|
|
8556
|
+
const sorted = [...product["item-images"]].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
8557
|
+
for (const img of sorted) {
|
|
8558
|
+
const transformed = transformStelImage(img["item-image-path"], product.name ?? "");
|
|
8559
|
+
if (transformed) {
|
|
8560
|
+
images.push(transformed);
|
|
8561
|
+
}
|
|
8562
|
+
}
|
|
8563
|
+
return images;
|
|
8564
|
+
}
|
|
8565
|
+
function deriveStelStockStatus(product) {
|
|
8566
|
+
if (!product["stock-enabled"]) {
|
|
8567
|
+
return "in_stock";
|
|
8568
|
+
}
|
|
8569
|
+
const realStock = product["real-stock"] ?? 0;
|
|
8570
|
+
const minimumStock = product["minimum-stock"] ?? 0;
|
|
8571
|
+
if (realStock <= 0) {
|
|
8572
|
+
return "out_of_stock";
|
|
8573
|
+
}
|
|
8574
|
+
if (realStock <= minimumStock) {
|
|
8575
|
+
return "low_stock";
|
|
8576
|
+
}
|
|
8577
|
+
return "in_stock";
|
|
8578
|
+
}
|
|
8579
|
+
function buildSyntheticVariant(product, currencyCode) {
|
|
8580
|
+
const price = transformStelMoney(product["sales-price"], currencyCode);
|
|
8581
|
+
const stockStatus = deriveStelStockStatus(product);
|
|
8582
|
+
const quantityAvailable = product["stock-enabled"] ? product["real-stock"] ?? 0 : void 0;
|
|
8583
|
+
const image = product["item-images"]?.[0] ? transformStelImage(product["item-images"][0]["item-image-path"], product.name ?? "") : void 0;
|
|
8584
|
+
return {
|
|
8585
|
+
id: toStelStringId(product.id),
|
|
8586
|
+
title: "Default",
|
|
8587
|
+
sku: product.barcode ?? product["full-reference"] ?? void 0,
|
|
8588
|
+
availableForSale: stockStatus !== "out_of_stock",
|
|
8589
|
+
selectedOptions: [],
|
|
8590
|
+
price,
|
|
8591
|
+
compareAtPrice: void 0,
|
|
8592
|
+
quantityAvailable,
|
|
8593
|
+
stockStatus,
|
|
8594
|
+
image
|
|
8595
|
+
};
|
|
8596
|
+
}
|
|
8597
|
+
function transformStelProduct(raw, currencyCode) {
|
|
8598
|
+
const id = toStelStringId(raw.id);
|
|
8599
|
+
const handle = buildStelHandle(raw["full-reference"], raw.reference, raw.name, raw.id);
|
|
8600
|
+
const price = transformStelMoney(raw["sales-price"], currencyCode);
|
|
8601
|
+
const images = transformStelProductImages(raw);
|
|
8602
|
+
const variant = buildSyntheticVariant(raw, currencyCode);
|
|
8603
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8604
|
+
return {
|
|
8605
|
+
id,
|
|
8606
|
+
handle,
|
|
8607
|
+
title: raw.name ?? "",
|
|
8608
|
+
description: raw.description ?? "",
|
|
8609
|
+
availableForSale: variant.availableForSale,
|
|
8610
|
+
options: [],
|
|
8611
|
+
variants: [variant],
|
|
8612
|
+
featuredImage: images[0],
|
|
8613
|
+
images,
|
|
8614
|
+
priceRange: {
|
|
8615
|
+
minVariantPrice: price,
|
|
8616
|
+
maxVariantPrice: price
|
|
8617
|
+
},
|
|
8618
|
+
productType: void 0,
|
|
8619
|
+
vendor: void 0,
|
|
8620
|
+
tags: raw.promotional ? ["promotional"] : [],
|
|
8621
|
+
createdAt: normalizeStelDate(raw["creation-date"]),
|
|
8622
|
+
updatedAt: normalizeStelDate(raw["utc-last-modification-date"]) || now,
|
|
8623
|
+
metadata: {
|
|
8624
|
+
stelFullReference: raw["full-reference"],
|
|
8625
|
+
stelReference: raw.reference,
|
|
8626
|
+
stelBarcode: raw.barcode,
|
|
8627
|
+
stelCategoryId: raw["product-category-id"],
|
|
8628
|
+
stelInactive: raw.inactive,
|
|
8629
|
+
stelPromotional: raw.promotional,
|
|
8630
|
+
stelPurchasePrice: raw["purchase-price"]
|
|
8631
|
+
}
|
|
8632
|
+
};
|
|
8633
|
+
}
|
|
8634
|
+
function transformStelCategory(raw) {
|
|
8635
|
+
const id = toStelStringId(raw.id);
|
|
8636
|
+
const handle = buildStelHandle(raw.slug, raw.name, raw.id);
|
|
8637
|
+
return {
|
|
8638
|
+
id,
|
|
8639
|
+
name: raw.name ?? "",
|
|
8640
|
+
handle,
|
|
8641
|
+
description: raw.description ?? void 0,
|
|
8642
|
+
parentCategoryId: raw["parent-category-id"] ? toStelStringId(raw["parent-category-id"]) : void 0,
|
|
8643
|
+
metadata: {
|
|
8644
|
+
stelSlug: raw.slug
|
|
8645
|
+
}
|
|
8646
|
+
};
|
|
8647
|
+
}
|
|
8648
|
+
function transformStelCategoryToCollection(raw) {
|
|
8649
|
+
const id = toStelStringId(raw.id);
|
|
8650
|
+
const handle = buildStelHandle(raw.slug, raw.name, raw.id);
|
|
8651
|
+
return {
|
|
8652
|
+
id,
|
|
8653
|
+
handle,
|
|
8654
|
+
title: raw.name ?? "",
|
|
8655
|
+
description: raw.description ?? "",
|
|
8656
|
+
path: `/collections/${handle}`,
|
|
8657
|
+
updatedAt: normalizeStelDate(raw["utc-last-modification-date"])
|
|
8658
|
+
};
|
|
8659
|
+
}
|
|
8660
|
+
function transformStelWarehouseToStockLocation(raw) {
|
|
8661
|
+
return {
|
|
8662
|
+
id: toStelStringId(raw.id),
|
|
8663
|
+
name: raw.name ?? "Warehouse"
|
|
8664
|
+
};
|
|
8665
|
+
}
|
|
8666
|
+
function transformStelProductWarehouseToInventoryLevel(pw) {
|
|
8667
|
+
return {
|
|
8668
|
+
locationId: toStelStringId(pw["warehouse-id"]),
|
|
8669
|
+
locationName: pw["warehouse-path"] ?? `Warehouse ${pw["warehouse-id"] ?? "unknown"}`,
|
|
8670
|
+
availableQuantity: pw["real-stock"] ?? 0,
|
|
8671
|
+
reservedQuantity: Math.max(0, (pw["virtual-stock"] ?? 0) - (pw["real-stock"] ?? 0)),
|
|
8672
|
+
stockedQuantity: pw["virtual-stock"] ?? pw["real-stock"] ?? 0
|
|
8673
|
+
};
|
|
8674
|
+
}
|
|
8675
|
+
function splitStelName(fullName) {
|
|
8676
|
+
if (!fullName?.trim()) {
|
|
8677
|
+
return { firstName: "", lastName: "" };
|
|
8678
|
+
}
|
|
8679
|
+
const parts = fullName.trim().split(/\s+/);
|
|
8680
|
+
const firstName = parts[0] ?? "";
|
|
8681
|
+
const lastName = parts.length > 1 ? parts.slice(1).join(" ") : "";
|
|
8682
|
+
return { firstName, lastName };
|
|
8683
|
+
}
|
|
8684
|
+
function transformStelClient(raw) {
|
|
8685
|
+
const { firstName, lastName } = splitStelName(raw.name ?? raw["business-name"]);
|
|
8686
|
+
return {
|
|
8687
|
+
id: toStelStringId(raw.id),
|
|
8688
|
+
email: raw.email ?? "",
|
|
8689
|
+
firstName: firstName || null,
|
|
8690
|
+
lastName: lastName || null,
|
|
8691
|
+
phone: raw.phone ?? raw.mobile ?? null,
|
|
8692
|
+
hasAccount: true,
|
|
8693
|
+
createdAt: normalizeStelDate(raw["utc-last-modification-date"]),
|
|
8694
|
+
updatedAt: normalizeStelDate(raw["utc-last-modification-date"]),
|
|
8695
|
+
metadata: {
|
|
8696
|
+
stelBusinessName: raw["business-name"],
|
|
8697
|
+
stelRateId: raw["rate-id"],
|
|
8698
|
+
stelPaymentOptionId: raw["payment-option-id"],
|
|
8699
|
+
stelPaymentTermId: raw["payment-term-id"],
|
|
8700
|
+
stelDeliveryOptionId: raw["delivery-option-id"]
|
|
8701
|
+
}
|
|
8702
|
+
};
|
|
8703
|
+
}
|
|
8704
|
+
function deriveStelOrderStatus(raw) {
|
|
8705
|
+
if (raw.deleted) {
|
|
8706
|
+
return "cancelled";
|
|
8707
|
+
}
|
|
8708
|
+
const status = (raw.status ?? "").toLowerCase();
|
|
8709
|
+
if (status.includes("cancel") || status.includes("anulad")) {
|
|
8710
|
+
return "cancelled";
|
|
8711
|
+
}
|
|
8712
|
+
if (status.includes("complet") || status.includes("cerrad") || status.includes("settled")) {
|
|
8713
|
+
return "completed";
|
|
8714
|
+
}
|
|
8715
|
+
if (status.includes("process") || status.includes("confirm")) {
|
|
8716
|
+
return "processing";
|
|
8717
|
+
}
|
|
8718
|
+
return "pending";
|
|
8719
|
+
}
|
|
8720
|
+
function deriveStelPaymentStatus(raw) {
|
|
8721
|
+
const orderStatus = deriveStelOrderStatus(raw);
|
|
8722
|
+
if (orderStatus === "completed") {
|
|
8723
|
+
return "paid";
|
|
8724
|
+
}
|
|
8725
|
+
if (orderStatus === "cancelled") {
|
|
8726
|
+
return "pending";
|
|
8727
|
+
}
|
|
8728
|
+
return "pending";
|
|
8729
|
+
}
|
|
8730
|
+
function deriveStelFulfillmentStatus(raw) {
|
|
8731
|
+
const orderStatus = deriveStelOrderStatus(raw);
|
|
8732
|
+
if (orderStatus === "completed") {
|
|
8733
|
+
return "fulfilled";
|
|
8734
|
+
}
|
|
8735
|
+
return "unfulfilled";
|
|
8736
|
+
}
|
|
8737
|
+
function transformStelDocumentLine(line, index, currencyCode) {
|
|
8738
|
+
const unitPrice = line["item-base-price"] ?? 0;
|
|
8739
|
+
const quantity = line.units ?? 1;
|
|
8740
|
+
const lineTotal = unitPrice * quantity;
|
|
8741
|
+
return {
|
|
8742
|
+
id: toStelStringId(line["item-id"] ?? index),
|
|
8743
|
+
title: line["item-name"] ?? "",
|
|
8744
|
+
variantTitle: void 0,
|
|
8745
|
+
quantity,
|
|
8746
|
+
price: transformStelMoney(unitPrice, currencyCode),
|
|
8747
|
+
total: transformStelMoney(lineTotal, currencyCode),
|
|
8748
|
+
sku: void 0,
|
|
8749
|
+
productId: line["item-id"] ? toStelStringId(line["item-id"]) : void 0
|
|
8750
|
+
};
|
|
8751
|
+
}
|
|
8752
|
+
function transformStelSalesOrder(raw, currencyCode) {
|
|
8753
|
+
const id = toStelStringId(raw.id);
|
|
8754
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8755
|
+
const items = (raw["document-lines"] ?? []).filter((line) => line["line-type"] === "ITEM" || !line["line-type"]).map((line, i) => transformStelDocumentLine(line, i, currencyCode));
|
|
8756
|
+
const status = deriveStelOrderStatus(raw);
|
|
8757
|
+
return {
|
|
8758
|
+
id,
|
|
8759
|
+
orderNumber: raw.reference ?? id,
|
|
8760
|
+
email: raw.email ?? "",
|
|
8761
|
+
status,
|
|
8762
|
+
paymentStatus: deriveStelPaymentStatus(raw),
|
|
8763
|
+
fulfillmentStatus: deriveStelFulfillmentStatus(raw),
|
|
8764
|
+
customerId: raw["client-id"] ? toStelStringId(raw["client-id"]) : void 0,
|
|
8765
|
+
items,
|
|
8766
|
+
subtotal: transformStelMoney(raw["total-base"], currencyCode),
|
|
8767
|
+
total: transformStelMoney(raw.total, currencyCode),
|
|
8768
|
+
tax: raw["total-tax"] != null ? transformStelMoney(raw["total-tax"], currencyCode) : void 0,
|
|
8769
|
+
createdAt: normalizeStelDate(raw["creation-date"]),
|
|
8770
|
+
updatedAt: normalizeStelDate(raw["utc-last-modification-date"]) || now,
|
|
8771
|
+
cancelledAt: status === "cancelled" ? normalizeStelDate(raw["utc-last-modification-date"]) : void 0,
|
|
8772
|
+
completedAt: status === "completed" ? normalizeStelDate(raw["utc-last-modification-date"]) : void 0,
|
|
8773
|
+
metadata: {
|
|
8774
|
+
stelReference: raw.reference,
|
|
8775
|
+
stelStatus: raw.status,
|
|
8776
|
+
stelPaymentOptionId: raw["payment-option-id"],
|
|
8777
|
+
stelPaymentTermId: raw["payment-term-id"],
|
|
8778
|
+
stelDeliveryOptionId: raw["delivery-option-id"]
|
|
8779
|
+
}
|
|
8780
|
+
};
|
|
8781
|
+
}
|
|
8782
|
+
function inferPaymentMethodType(name) {
|
|
8783
|
+
if (!name) {
|
|
8784
|
+
return "other";
|
|
8785
|
+
}
|
|
8786
|
+
const lower = name.toLowerCase();
|
|
8787
|
+
if (lower.includes("transfer") || lower.includes("bank") || lower.includes("sepa") || lower.includes("iban")) {
|
|
8788
|
+
return "bank_transfer";
|
|
8789
|
+
}
|
|
8790
|
+
if (lower.includes("card") || lower.includes("tarjeta") || lower.includes("visa") || lower.includes("master")) {
|
|
8791
|
+
return "card";
|
|
8792
|
+
}
|
|
8793
|
+
if (lower.includes("paypal") || lower.includes("wallet") || lower.includes("apple") || lower.includes("google")) {
|
|
8794
|
+
return "wallet";
|
|
8795
|
+
}
|
|
8796
|
+
if (lower.includes("cash") || lower.includes("contrar") || lower.includes("efectivo") || lower.includes("cod")) {
|
|
8797
|
+
return "cash_on_delivery";
|
|
8798
|
+
}
|
|
8799
|
+
if (lower.includes("crypto") || lower.includes("bitcoin")) {
|
|
8800
|
+
return "crypto";
|
|
8801
|
+
}
|
|
8802
|
+
return "other";
|
|
8803
|
+
}
|
|
8804
|
+
function transformStelPaymentOption(raw) {
|
|
8805
|
+
return {
|
|
8806
|
+
id: toStelStringId(raw.id),
|
|
8807
|
+
name: raw.name ?? "Payment Option",
|
|
8808
|
+
type: inferPaymentMethodType(raw.name),
|
|
8809
|
+
isEnabled: !raw.deleted,
|
|
8810
|
+
requiresSetup: false,
|
|
8811
|
+
description: raw.description ?? void 0,
|
|
8812
|
+
metadata: {
|
|
8813
|
+
stelPath: raw.path,
|
|
8814
|
+
source: "paymentOption"
|
|
8815
|
+
}
|
|
8816
|
+
};
|
|
8817
|
+
}
|
|
8818
|
+
function transformStelDeliveryOption(raw, currencyCode) {
|
|
8819
|
+
return {
|
|
8820
|
+
id: toStelStringId(raw.id),
|
|
8821
|
+
name: raw.name ?? "Delivery Option",
|
|
8822
|
+
price: transformStelMoney(0, currencyCode),
|
|
8823
|
+
description: raw.description ?? void 0
|
|
8824
|
+
};
|
|
8825
|
+
}
|
|
8826
|
+
|
|
8827
|
+
// src/providers/stelorder/client.ts
|
|
8828
|
+
var DEFAULT_RETRY_CONFIG2 = {
|
|
8829
|
+
maxRetries: 2,
|
|
8830
|
+
initialDelayMs: 300,
|
|
8831
|
+
backoffMultiplier: 2,
|
|
8832
|
+
maxDelayMs: 5e3,
|
|
8833
|
+
timeoutMs: 1e4,
|
|
8834
|
+
retryableStatusCodes: [408, 429, 500, 502, 503, 504]
|
|
8835
|
+
};
|
|
8836
|
+
function sleep2(ms) {
|
|
8837
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8838
|
+
}
|
|
8839
|
+
function calculateDelay2(attempt, retryConfig) {
|
|
8840
|
+
const delay = retryConfig.initialDelayMs * Math.pow(retryConfig.backoffMultiplier, attempt);
|
|
8841
|
+
return Math.min(delay, retryConfig.maxDelayMs);
|
|
8842
|
+
}
|
|
8843
|
+
function isRetryableError2(error, retryConfig) {
|
|
8844
|
+
if (error instanceof ProviderAPIError) {
|
|
8845
|
+
return typeof error.status === "number" && retryConfig.retryableStatusCodes.includes(error.status);
|
|
8846
|
+
}
|
|
8847
|
+
if (!(error instanceof Error)) {
|
|
8848
|
+
return false;
|
|
8849
|
+
}
|
|
8850
|
+
const message = error.message.toLowerCase();
|
|
8851
|
+
return error.name === "AbortError" || error.name === "TimeoutError" || message.includes("fetch failed") || message.includes("timeout") || message.includes("econnrefused") || message.includes("etimedout");
|
|
8852
|
+
}
|
|
8853
|
+
function createRetryingFetch(fetchImplementation, retryConfig, logger2) {
|
|
8854
|
+
return async (url, init) => {
|
|
8855
|
+
let lastError = new Error("Unknown STEL Order request error");
|
|
8856
|
+
for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt += 1) {
|
|
8857
|
+
const controller = new AbortController();
|
|
8858
|
+
const timeoutId = setTimeout(() => controller.abort(), retryConfig.timeoutMs);
|
|
8859
|
+
try {
|
|
8860
|
+
const response = await fetchImplementation(url, {
|
|
8861
|
+
...init,
|
|
8862
|
+
signal: controller.signal
|
|
8863
|
+
});
|
|
8864
|
+
clearTimeout(timeoutId);
|
|
8865
|
+
if (!response.ok && retryConfig.retryableStatusCodes.includes(response.status)) {
|
|
8866
|
+
throw new ProviderAPIError(
|
|
8867
|
+
`STEL Order API error: ${response.status} ${response.statusText}`,
|
|
8868
|
+
"stelorder",
|
|
8869
|
+
response.status
|
|
8870
|
+
);
|
|
8871
|
+
}
|
|
8872
|
+
return response;
|
|
8873
|
+
} catch (error) {
|
|
8874
|
+
clearTimeout(timeoutId);
|
|
8875
|
+
lastError = error;
|
|
8876
|
+
if (attempt >= retryConfig.maxRetries || !isRetryableError2(error, retryConfig)) {
|
|
8877
|
+
throw error;
|
|
8878
|
+
}
|
|
8879
|
+
const delay = calculateDelay2(attempt, retryConfig);
|
|
8880
|
+
logger2.warn("STEL request failed, retrying.", {
|
|
8881
|
+
url,
|
|
8882
|
+
attempt: attempt + 1,
|
|
8883
|
+
maxAttempts: retryConfig.maxRetries + 1,
|
|
8884
|
+
nextRetryIn: delay,
|
|
8885
|
+
error: extractErrorMessage(error)
|
|
8886
|
+
});
|
|
8887
|
+
await sleep2(delay);
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8890
|
+
throw lastError;
|
|
8891
|
+
};
|
|
8892
|
+
}
|
|
8893
|
+
async function parseErrorResponse(response) {
|
|
8894
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
8895
|
+
try {
|
|
8896
|
+
if (contentType.includes("application/json")) {
|
|
8897
|
+
const body = await response.json();
|
|
8898
|
+
const message = extractErrorMessage(body);
|
|
8899
|
+
if (message && message !== "An unknown error occurred") {
|
|
8900
|
+
return message;
|
|
8901
|
+
}
|
|
8902
|
+
}
|
|
8903
|
+
} catch {
|
|
8904
|
+
}
|
|
8905
|
+
try {
|
|
8906
|
+
const text = await response.text();
|
|
8907
|
+
if (text.trim()) {
|
|
8908
|
+
return text.trim();
|
|
8909
|
+
}
|
|
8910
|
+
} catch {
|
|
8911
|
+
}
|
|
8912
|
+
return `STEL Order API error: ${response.status} ${response.statusText}`;
|
|
8913
|
+
}
|
|
8914
|
+
function normalizeStelApiBaseUrl(baseUrl) {
|
|
8915
|
+
const trimmed = baseUrl.trim();
|
|
8916
|
+
if (!trimmed) {
|
|
8917
|
+
throw new ConfigurationError("Stel Order baseUrl is required.");
|
|
8918
|
+
}
|
|
8919
|
+
const normalized = trimmed.replace(/\/api\/openapi\.json$/i, "").replace(/\/+$/, "");
|
|
8920
|
+
return normalized.endsWith("/app") ? normalized : `${normalized}/app`;
|
|
8921
|
+
}
|
|
8922
|
+
var StelOrderClient = class {
|
|
8923
|
+
apiBaseUrl;
|
|
8924
|
+
apiKey;
|
|
8925
|
+
logger;
|
|
8926
|
+
retryConfig;
|
|
8927
|
+
fetchImplementation;
|
|
8928
|
+
constructor(config) {
|
|
8929
|
+
const apiKey = config.apiKey ?? config.publishableKey;
|
|
8930
|
+
if (!apiKey) {
|
|
8931
|
+
throw new ConfigurationError(
|
|
8932
|
+
"Stel Order API key is required. Pass `apiKey` or `publishableKey` to createProvider()."
|
|
8933
|
+
);
|
|
8934
|
+
}
|
|
8935
|
+
this.apiBaseUrl = normalizeStelApiBaseUrl(config.baseUrl);
|
|
8936
|
+
this.apiKey = apiKey;
|
|
8937
|
+
this.logger = createLogger("StelOrderClient", config.debug);
|
|
8938
|
+
this.retryConfig = {
|
|
8939
|
+
...DEFAULT_RETRY_CONFIG2,
|
|
8940
|
+
...config.retry
|
|
8941
|
+
};
|
|
8942
|
+
this.fetchImplementation = config.customFetch ? createRetryingFetch(config.customFetch, this.retryConfig, this.logger) : void 0;
|
|
8943
|
+
this.logger.info("Initializing Stel Order client", {
|
|
8944
|
+
apiBaseUrl: this.apiBaseUrl,
|
|
8945
|
+
retryConfig: this.retryConfig
|
|
8946
|
+
});
|
|
8947
|
+
}
|
|
8948
|
+
async list(resource, query) {
|
|
8949
|
+
const payload = await this.request({
|
|
8950
|
+
method: "GET",
|
|
8951
|
+
path: resource,
|
|
8952
|
+
query
|
|
8953
|
+
});
|
|
8954
|
+
return normalizeStelCollection(payload);
|
|
8955
|
+
}
|
|
8956
|
+
async getOne(resource, id, query) {
|
|
8957
|
+
const payload = await this.request({
|
|
8958
|
+
method: "GET",
|
|
8959
|
+
path: `${resource}/${id}`,
|
|
8960
|
+
query
|
|
8961
|
+
});
|
|
8962
|
+
return normalizeStelEntity(payload);
|
|
8963
|
+
}
|
|
8964
|
+
async create(resource, body) {
|
|
8965
|
+
return this.request({
|
|
8966
|
+
method: "POST",
|
|
8967
|
+
path: resource,
|
|
8968
|
+
body
|
|
8969
|
+
});
|
|
8970
|
+
}
|
|
8971
|
+
async update(resource, id, body) {
|
|
8972
|
+
return this.request({
|
|
8973
|
+
method: "PUT",
|
|
8974
|
+
path: `${resource}/${id}`,
|
|
8975
|
+
body
|
|
8976
|
+
});
|
|
8977
|
+
}
|
|
8978
|
+
async remove(resource, id) {
|
|
8979
|
+
await this.request({
|
|
8980
|
+
method: "DELETE",
|
|
8981
|
+
path: `${resource}/${id}`
|
|
8982
|
+
});
|
|
8983
|
+
}
|
|
8984
|
+
buildUrl(path, query) {
|
|
8985
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
8986
|
+
const url = new URL(`${this.apiBaseUrl}/${normalizedPath}`);
|
|
8987
|
+
if (!query) {
|
|
8988
|
+
return url.toString();
|
|
8989
|
+
}
|
|
8990
|
+
for (const [key, value] of Object.entries(query)) {
|
|
8991
|
+
if (value === void 0 || value === null || value === "") {
|
|
8992
|
+
continue;
|
|
8993
|
+
}
|
|
8994
|
+
if (Array.isArray(value)) {
|
|
8995
|
+
url.searchParams.set(key, value.map(String).join(","));
|
|
8996
|
+
continue;
|
|
8997
|
+
}
|
|
8998
|
+
url.searchParams.set(key, String(value));
|
|
8999
|
+
}
|
|
9000
|
+
return url.toString();
|
|
9001
|
+
}
|
|
9002
|
+
buildHeaders(headers, hasBody = false) {
|
|
9003
|
+
const normalizedHeaders = new Headers(headers);
|
|
9004
|
+
normalizedHeaders.set("Accept", "application/json");
|
|
9005
|
+
normalizedHeaders.set("APIKEY", this.apiKey);
|
|
9006
|
+
if (hasBody && !normalizedHeaders.has("Content-Type")) {
|
|
9007
|
+
normalizedHeaders.set("Content-Type", "application/json");
|
|
9008
|
+
}
|
|
9009
|
+
return normalizedHeaders;
|
|
9010
|
+
}
|
|
9011
|
+
async executeFetch(url, init) {
|
|
9012
|
+
if (this.fetchImplementation) {
|
|
9013
|
+
return this.fetchImplementation(url, init);
|
|
9014
|
+
}
|
|
9015
|
+
return fetchWithRetry(url, init, this.retryConfig);
|
|
9016
|
+
}
|
|
9017
|
+
async request(options) {
|
|
9018
|
+
const url = this.buildUrl(options.path, options.query);
|
|
9019
|
+
const hasBody = options.body !== void 0;
|
|
9020
|
+
this.logger.debug("STEL request", {
|
|
9021
|
+
method: options.method,
|
|
9022
|
+
url
|
|
9023
|
+
});
|
|
9024
|
+
const response = await this.executeFetch(url, {
|
|
9025
|
+
method: options.method,
|
|
9026
|
+
headers: this.buildHeaders(options.headers, hasBody),
|
|
9027
|
+
body: hasBody ? JSON.stringify(options.body) : void 0
|
|
9028
|
+
});
|
|
9029
|
+
if (!response.ok) {
|
|
9030
|
+
const message = await parseErrorResponse(response);
|
|
9031
|
+
throw new ProviderAPIError(message, "stelorder", response.status);
|
|
9032
|
+
}
|
|
9033
|
+
if (response.status === 204) {
|
|
9034
|
+
return void 0;
|
|
9035
|
+
}
|
|
9036
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
9037
|
+
if (contentType.includes("application/json")) {
|
|
9038
|
+
return await response.json();
|
|
9039
|
+
}
|
|
9040
|
+
return await response.text();
|
|
9041
|
+
}
|
|
9042
|
+
};
|
|
9043
|
+
|
|
9044
|
+
// src/providers/stelorder/index.ts
|
|
9045
|
+
var StelOrderProvider = class {
|
|
9046
|
+
name = "stelorder";
|
|
9047
|
+
version = "1.0.0";
|
|
9048
|
+
capabilities = STELORDER_CAPABILITIES;
|
|
9049
|
+
client;
|
|
9050
|
+
logger;
|
|
9051
|
+
currencyCode;
|
|
9052
|
+
storefrontId;
|
|
9053
|
+
companyName;
|
|
9054
|
+
constructor(config) {
|
|
9055
|
+
this.client = new StelOrderClient(config);
|
|
9056
|
+
this.logger = createLogger("StelOrderProvider", config.debug);
|
|
9057
|
+
this.currencyCode = config.currencyCode ?? "USD";
|
|
9058
|
+
this.storefrontId = config.storefrontId ?? "stelorder-primary";
|
|
9059
|
+
this.companyName = config.companyName ?? "Stel Order";
|
|
9060
|
+
this.logger.info("Initializing Stel Order provider", {
|
|
9061
|
+
storefrontId: this.storefrontId,
|
|
9062
|
+
companyName: this.companyName,
|
|
9063
|
+
currencyCode: this.currencyCode
|
|
9064
|
+
});
|
|
9065
|
+
}
|
|
9066
|
+
// ============================================
|
|
9067
|
+
// Product Operations
|
|
9068
|
+
// ============================================
|
|
9069
|
+
async getProduct(handle, _options) {
|
|
9070
|
+
this.logger.debug("getProduct", { handle });
|
|
9071
|
+
try {
|
|
9072
|
+
if (/^\d+$/.test(handle)) {
|
|
9073
|
+
const raw = await this.client.getOne("products", handle);
|
|
9074
|
+
return raw ? transformStelProduct(raw, this.currencyCode) : void 0;
|
|
9075
|
+
}
|
|
9076
|
+
const rawProducts = await this.client.list("products", { limit: 100 });
|
|
9077
|
+
for (const raw of rawProducts) {
|
|
9078
|
+
const productHandle = buildStelHandle(raw["full-reference"], raw.reference, raw.name, raw.id);
|
|
9079
|
+
if (productHandle === handle) {
|
|
9080
|
+
return transformStelProduct(raw, this.currencyCode);
|
|
9081
|
+
}
|
|
9082
|
+
}
|
|
9083
|
+
return void 0;
|
|
9084
|
+
} catch (error) {
|
|
9085
|
+
this.logger.error("getProduct failed", { handle, error });
|
|
9086
|
+
throw this.wrapError("getProduct", error);
|
|
9087
|
+
}
|
|
9088
|
+
}
|
|
9089
|
+
async getProducts(options) {
|
|
9090
|
+
this.logger.debug("getProducts", { options });
|
|
9091
|
+
try {
|
|
9092
|
+
const query = {};
|
|
9093
|
+
if (options?.first) {
|
|
9094
|
+
query.limit = options.first;
|
|
9095
|
+
}
|
|
9096
|
+
if (options?.skip) {
|
|
9097
|
+
query.start = options.skip;
|
|
9098
|
+
}
|
|
9099
|
+
if (typeof options?.after === "number") {
|
|
9100
|
+
query.start = options.after;
|
|
9101
|
+
}
|
|
9102
|
+
if (options?.sortKey) {
|
|
9103
|
+
query.sort = options.reverse ? `-${options.sortKey}` : options.sortKey;
|
|
9104
|
+
}
|
|
9105
|
+
if (options?.categoryIds?.length) {
|
|
9106
|
+
query["product-category-id"] = `in:${options.categoryIds.join(",")}`;
|
|
9107
|
+
}
|
|
9108
|
+
const rawProducts = await this.client.list("products", query);
|
|
9109
|
+
let products = rawProducts.filter((p) => !p.deleted && !p.inactive).map((p) => transformStelProduct(p, this.currencyCode));
|
|
9110
|
+
if (options?.query) {
|
|
9111
|
+
const needle = options.query.toLowerCase();
|
|
9112
|
+
products = products.filter(
|
|
9113
|
+
(p) => p.title.toLowerCase().includes(needle) || p.description.toLowerCase().includes(needle) || p.handle.includes(needle)
|
|
9114
|
+
);
|
|
9115
|
+
}
|
|
9116
|
+
return products;
|
|
9117
|
+
} catch (error) {
|
|
9118
|
+
this.logger.error("getProducts failed", { options, error });
|
|
9119
|
+
throw this.wrapError("getProducts", error);
|
|
9120
|
+
}
|
|
9121
|
+
}
|
|
9122
|
+
async getProductRecommendations(_productId) {
|
|
9123
|
+
return [];
|
|
9124
|
+
}
|
|
9125
|
+
// ============================================
|
|
9126
|
+
// Category Operations
|
|
9127
|
+
// ============================================
|
|
9128
|
+
async getCategory(idOrHandle) {
|
|
9129
|
+
this.logger.debug("getCategory", { idOrHandle });
|
|
9130
|
+
try {
|
|
9131
|
+
if (/^\d+$/.test(idOrHandle)) {
|
|
9132
|
+
const raw = await this.client.getOne("productCategories", idOrHandle);
|
|
9133
|
+
return raw ? transformStelCategory(raw) : void 0;
|
|
9134
|
+
}
|
|
9135
|
+
const rawCategories = await this.client.list("productCategories");
|
|
9136
|
+
for (const raw of rawCategories) {
|
|
9137
|
+
const catHandle = buildStelHandle(raw.slug, raw.name, raw.id);
|
|
9138
|
+
if (catHandle === idOrHandle) {
|
|
9139
|
+
return transformStelCategory(raw);
|
|
9140
|
+
}
|
|
9141
|
+
}
|
|
9142
|
+
return void 0;
|
|
9143
|
+
} catch (error) {
|
|
9144
|
+
this.logger.error("getCategory failed", { idOrHandle, error });
|
|
9145
|
+
throw this.wrapError("getCategory", error);
|
|
9146
|
+
}
|
|
9147
|
+
}
|
|
9148
|
+
async getCategories(options) {
|
|
9149
|
+
this.logger.debug("getCategories", { options });
|
|
9150
|
+
try {
|
|
9151
|
+
if (options?.customFetcher) {
|
|
9152
|
+
return options.customFetcher(options);
|
|
9153
|
+
}
|
|
9154
|
+
const query = {};
|
|
9155
|
+
if (options?.limit) {
|
|
9156
|
+
query.limit = options.limit;
|
|
9157
|
+
}
|
|
9158
|
+
if (options?.parentCategoryId) {
|
|
9159
|
+
query["parent-category-id"] = options.parentCategoryId;
|
|
9160
|
+
}
|
|
9161
|
+
const rawCategories = await this.client.list("productCategories", query);
|
|
9162
|
+
let categories = rawCategories.filter((c) => !c.deleted).map(transformStelCategory);
|
|
9163
|
+
if (options?.query) {
|
|
9164
|
+
const needle = options.query.toLowerCase();
|
|
9165
|
+
categories = categories.filter(
|
|
9166
|
+
(c) => c.name.toLowerCase().includes(needle) || c.handle.includes(needle)
|
|
9167
|
+
);
|
|
9168
|
+
}
|
|
9169
|
+
return categories;
|
|
9170
|
+
} catch (error) {
|
|
9171
|
+
this.logger.error("getCategories failed", { options, error });
|
|
9172
|
+
throw this.wrapError("getCategories", error);
|
|
9173
|
+
}
|
|
9174
|
+
}
|
|
9175
|
+
async getCategoryProducts(handle, options) {
|
|
9176
|
+
this.logger.debug("getCategoryProducts", { handle, options });
|
|
9177
|
+
try {
|
|
9178
|
+
const category = await this.getCategory(handle);
|
|
9179
|
+
if (!category) {
|
|
9180
|
+
return [];
|
|
9181
|
+
}
|
|
9182
|
+
return this.getProducts({
|
|
9183
|
+
categoryIds: [category.id],
|
|
9184
|
+
first: options?.first,
|
|
9185
|
+
sortKey: options?.sortKey,
|
|
9186
|
+
reverse: options?.reverse,
|
|
9187
|
+
after: options?.after
|
|
9188
|
+
});
|
|
9189
|
+
} catch (error) {
|
|
9190
|
+
this.logger.error("getCategoryProducts failed", { handle, error });
|
|
9191
|
+
throw this.wrapError("getCategoryProducts", error);
|
|
9192
|
+
}
|
|
9193
|
+
}
|
|
9194
|
+
// ============================================
|
|
9195
|
+
// Collection Operations (alias categories)
|
|
9196
|
+
// ============================================
|
|
9197
|
+
async getCollection(handle) {
|
|
9198
|
+
this.logger.debug("getCollection", { handle });
|
|
9199
|
+
try {
|
|
9200
|
+
if (/^\d+$/.test(handle)) {
|
|
9201
|
+
const raw = await this.client.getOne("productCategories", handle);
|
|
9202
|
+
return raw ? transformStelCategoryToCollection(raw) : void 0;
|
|
9203
|
+
}
|
|
9204
|
+
const rawCategories = await this.client.list("productCategories");
|
|
9205
|
+
for (const raw of rawCategories) {
|
|
9206
|
+
const catHandle = buildStelHandle(raw.slug, raw.name, raw.id);
|
|
9207
|
+
if (catHandle === handle) {
|
|
9208
|
+
return transformStelCategoryToCollection(raw);
|
|
9209
|
+
}
|
|
9210
|
+
}
|
|
9211
|
+
return void 0;
|
|
9212
|
+
} catch (error) {
|
|
9213
|
+
this.logger.error("getCollection failed", { handle, error });
|
|
9214
|
+
throw this.wrapError("getCollection", error);
|
|
9215
|
+
}
|
|
9216
|
+
}
|
|
9217
|
+
async getCollections(options) {
|
|
9218
|
+
this.logger.debug("getCollections", { options });
|
|
9219
|
+
try {
|
|
9220
|
+
const query = {};
|
|
9221
|
+
if (options?.limit) {
|
|
9222
|
+
query.limit = options.limit;
|
|
9223
|
+
}
|
|
9224
|
+
const rawCategories = await this.client.list("productCategories", query);
|
|
9225
|
+
let collections = rawCategories.filter((c) => !c.deleted).map(transformStelCategoryToCollection);
|
|
9226
|
+
if (options?.query) {
|
|
9227
|
+
const needle = options.query.toLowerCase();
|
|
9228
|
+
collections = collections.filter(
|
|
9229
|
+
(c) => c.title.toLowerCase().includes(needle) || c.handle.includes(needle)
|
|
9230
|
+
);
|
|
9231
|
+
}
|
|
9232
|
+
return collections;
|
|
9233
|
+
} catch (error) {
|
|
9234
|
+
this.logger.error("getCollections failed", { options, error });
|
|
9235
|
+
throw this.wrapError("getCollections", error);
|
|
9236
|
+
}
|
|
9237
|
+
}
|
|
9238
|
+
async getCollectionProducts(handle, options) {
|
|
9239
|
+
return this.getCategoryProducts(handle, options);
|
|
9240
|
+
}
|
|
9241
|
+
// ============================================
|
|
9242
|
+
// Inventory Operations
|
|
9243
|
+
// ============================================
|
|
9244
|
+
async getStockLocations(_params) {
|
|
9245
|
+
this.logger.debug("getStockLocations");
|
|
9246
|
+
try {
|
|
9247
|
+
const rawWarehouses = await this.client.list("warehouses");
|
|
9248
|
+
return rawWarehouses.filter((w) => !w.deleted).map(transformStelWarehouseToStockLocation);
|
|
9249
|
+
} catch (error) {
|
|
9250
|
+
this.logger.error("getStockLocations failed", { error });
|
|
9251
|
+
throw this.wrapError("getStockLocations", error);
|
|
9252
|
+
}
|
|
9253
|
+
}
|
|
9254
|
+
async checkInventoryLevels(variantId, _salesChannelId) {
|
|
9255
|
+
this.logger.debug("checkInventoryLevels", { variantId });
|
|
9256
|
+
try {
|
|
9257
|
+
const raw = await this.client.getOne("products", variantId);
|
|
9258
|
+
if (!raw?.["product-warehouses"]?.length) {
|
|
9259
|
+
return [];
|
|
9260
|
+
}
|
|
9261
|
+
return raw["product-warehouses"].map(transformStelProductWarehouseToInventoryLevel);
|
|
9262
|
+
} catch (error) {
|
|
9263
|
+
this.logger.error("checkInventoryLevels failed", { variantId, error });
|
|
9264
|
+
throw this.wrapError("checkInventoryLevels", error);
|
|
9265
|
+
}
|
|
9266
|
+
}
|
|
9267
|
+
validateCartStock(_cartId) {
|
|
9268
|
+
return this.notSupported("validateCartStock");
|
|
9269
|
+
}
|
|
9270
|
+
getFulfillmentOptions(_cartId, _salesChannelId) {
|
|
9271
|
+
return this.notSupported("getFulfillmentOptions");
|
|
9272
|
+
}
|
|
9273
|
+
// ============================================
|
|
9274
|
+
// Cart Operations (unsupported — STEL is ERP, not storefront)
|
|
9275
|
+
// ============================================
|
|
9276
|
+
createCart(_options) {
|
|
9277
|
+
return this.notSupported("createCart");
|
|
9278
|
+
}
|
|
9279
|
+
getCart(_cartId) {
|
|
9280
|
+
return this.notSupported("getCart");
|
|
9281
|
+
}
|
|
9282
|
+
addToCart(_cartId, _lines) {
|
|
9283
|
+
return this.notSupported("addToCart");
|
|
9284
|
+
}
|
|
9285
|
+
removeFromCart(_cartId, _lineIds) {
|
|
9286
|
+
return this.notSupported("removeFromCart");
|
|
9287
|
+
}
|
|
9288
|
+
updateCart(_cartId, _lines) {
|
|
9289
|
+
return this.notSupported("updateCart");
|
|
9290
|
+
}
|
|
9291
|
+
// ============================================
|
|
9292
|
+
// Checkout Operations (unsupported)
|
|
9293
|
+
// ============================================
|
|
9294
|
+
completeCheckout(_cartId) {
|
|
9295
|
+
return this.notSupported("completeCheckout");
|
|
9296
|
+
}
|
|
9297
|
+
getRegionCountries(_regionId) {
|
|
9298
|
+
return this.notSupported("getRegionCountries");
|
|
9299
|
+
}
|
|
9300
|
+
addShippingMethod(_cartId, _methodId) {
|
|
9301
|
+
return this.notSupported("addShippingMethod");
|
|
9302
|
+
}
|
|
9303
|
+
initiatePaymentSession(_cartId, _providerId) {
|
|
9304
|
+
return this.notSupported("initiatePaymentSession");
|
|
9305
|
+
}
|
|
9306
|
+
applyDiscountCode(_cartId, _code) {
|
|
9307
|
+
return this.notSupported("applyDiscountCode");
|
|
9308
|
+
}
|
|
9309
|
+
removeDiscountCode(_cartId, _code) {
|
|
9310
|
+
return this.notSupported("removeDiscountCode");
|
|
9311
|
+
}
|
|
9312
|
+
// ============================================
|
|
9313
|
+
// Payment Methods
|
|
9314
|
+
// ============================================
|
|
9315
|
+
async getPaymentMethods(_options) {
|
|
9316
|
+
this.logger.debug("getPaymentMethods");
|
|
9317
|
+
try {
|
|
9318
|
+
const rawOptions = await this.client.list("paymentOptions");
|
|
9319
|
+
return rawOptions.filter((o) => !o.deleted).map(transformStelPaymentOption);
|
|
9320
|
+
} catch (error) {
|
|
9321
|
+
this.logger.error("getPaymentMethods failed", { error });
|
|
9322
|
+
throw this.wrapError("getPaymentMethods", error);
|
|
9323
|
+
}
|
|
9324
|
+
}
|
|
9325
|
+
// ============================================
|
|
9326
|
+
// Shipping / Delivery Methods
|
|
9327
|
+
// ============================================
|
|
9328
|
+
async getShippingMethods(_cartId) {
|
|
9329
|
+
this.logger.debug("getShippingMethods (delivery options listing)");
|
|
9330
|
+
try {
|
|
9331
|
+
const rawOptions = await this.client.list("deliveryOptions");
|
|
9332
|
+
return rawOptions.filter((o) => !o.deleted).map((o) => transformStelDeliveryOption(o, this.currencyCode));
|
|
9333
|
+
} catch (error) {
|
|
9334
|
+
this.logger.error("getShippingMethods failed", { error });
|
|
9335
|
+
throw this.wrapError("getShippingMethods", error);
|
|
9336
|
+
}
|
|
9337
|
+
}
|
|
9338
|
+
// ============================================
|
|
9339
|
+
// Customer Operations (optional)
|
|
9340
|
+
// ============================================
|
|
9341
|
+
async getCustomer(customerId) {
|
|
9342
|
+
this.logger.debug("getCustomer", { customerId });
|
|
9343
|
+
try {
|
|
9344
|
+
const raw = await this.client.getOne("clients", customerId);
|
|
9345
|
+
return raw ? transformStelClient(raw) : void 0;
|
|
9346
|
+
} catch (error) {
|
|
9347
|
+
this.logger.error("getCustomer failed", { customerId, error });
|
|
9348
|
+
throw this.wrapError("getCustomer", error);
|
|
9349
|
+
}
|
|
9350
|
+
}
|
|
9351
|
+
async getCustomers(options) {
|
|
9352
|
+
this.logger.debug("getCustomers", { options });
|
|
9353
|
+
try {
|
|
9354
|
+
if (options?.customFetcher) {
|
|
9355
|
+
return options.customFetcher(options);
|
|
9356
|
+
}
|
|
9357
|
+
const query = {};
|
|
9358
|
+
if (options?.limit) {
|
|
9359
|
+
query.limit = options.limit;
|
|
9360
|
+
}
|
|
9361
|
+
if (options?.offset) {
|
|
9362
|
+
query.start = options.offset;
|
|
9363
|
+
}
|
|
9364
|
+
const rawClients = await this.client.list("clients", query);
|
|
9365
|
+
let customers = rawClients.filter((c) => !c.deleted).map(transformStelClient);
|
|
9366
|
+
if (options?.search) {
|
|
9367
|
+
const needle = options.search.toLowerCase();
|
|
9368
|
+
customers = customers.filter(
|
|
9369
|
+
(c) => c.email.toLowerCase().includes(needle) || (c.firstName ?? "").toLowerCase().includes(needle) || (c.lastName ?? "").toLowerCase().includes(needle)
|
|
9370
|
+
);
|
|
9371
|
+
}
|
|
9372
|
+
return customers;
|
|
9373
|
+
} catch (error) {
|
|
9374
|
+
this.logger.error("getCustomers failed", { options, error });
|
|
9375
|
+
throw this.wrapError("getCustomers", error);
|
|
9376
|
+
}
|
|
9377
|
+
}
|
|
9378
|
+
async updateCustomer(customerId, data) {
|
|
9379
|
+
this.logger.debug("updateCustomer", { customerId, data });
|
|
9380
|
+
try {
|
|
9381
|
+
const stelPayload = {};
|
|
9382
|
+
if (data.firstName || data.lastName) {
|
|
9383
|
+
stelPayload.name = [data.firstName, data.lastName].filter(Boolean).join(" ");
|
|
9384
|
+
}
|
|
9385
|
+
if (data.email) {
|
|
9386
|
+
stelPayload.email = data.email;
|
|
9387
|
+
}
|
|
9388
|
+
if (data.phone) {
|
|
9389
|
+
stelPayload.phone = data.phone;
|
|
9390
|
+
}
|
|
9391
|
+
const raw = await this.client.update("clients", customerId, stelPayload);
|
|
9392
|
+
return transformStelClient(raw);
|
|
9393
|
+
} catch (error) {
|
|
9394
|
+
this.logger.error("updateCustomer failed", { customerId, error });
|
|
9395
|
+
throw this.wrapError("updateCustomer", error);
|
|
9396
|
+
}
|
|
9397
|
+
}
|
|
9398
|
+
// ============================================
|
|
9399
|
+
// Order Operations (optional)
|
|
9400
|
+
// ============================================
|
|
9401
|
+
async getOrder(orderId) {
|
|
9402
|
+
this.logger.debug("getOrder", { orderId });
|
|
9403
|
+
try {
|
|
9404
|
+
const raw = await this.client.getOne("salesOrders", orderId);
|
|
9405
|
+
return raw ? transformStelSalesOrder(raw, this.currencyCode) : void 0;
|
|
9406
|
+
} catch (error) {
|
|
9407
|
+
this.logger.error("getOrder failed", { orderId, error });
|
|
9408
|
+
throw this.wrapError("getOrder", error);
|
|
9409
|
+
}
|
|
9410
|
+
}
|
|
9411
|
+
async getOrders(options) {
|
|
9412
|
+
this.logger.debug("getOrders", { options });
|
|
9413
|
+
try {
|
|
9414
|
+
if (options?.customFetcher) {
|
|
9415
|
+
return options.customFetcher(options);
|
|
9416
|
+
}
|
|
9417
|
+
const query = {};
|
|
9418
|
+
if (options?.limit) {
|
|
9419
|
+
query.limit = options.limit;
|
|
9420
|
+
}
|
|
9421
|
+
if (options?.offset) {
|
|
9422
|
+
query.start = options.offset;
|
|
9423
|
+
}
|
|
9424
|
+
if (options?.customerId) {
|
|
9425
|
+
query["client-id"] = options.customerId;
|
|
9426
|
+
}
|
|
9427
|
+
const rawOrders = await this.client.list("salesOrders", query);
|
|
9428
|
+
return rawOrders.filter((o) => !o.deleted).map((o) => transformStelSalesOrder(o, this.currencyCode));
|
|
9429
|
+
} catch (error) {
|
|
9430
|
+
this.logger.error("getOrders failed", { options, error });
|
|
9431
|
+
throw this.wrapError("getOrders", error);
|
|
9432
|
+
}
|
|
9433
|
+
}
|
|
9434
|
+
async getAdminOrders(options) {
|
|
9435
|
+
this.logger.debug("getAdminOrders", { options });
|
|
9436
|
+
try {
|
|
9437
|
+
const query = {};
|
|
9438
|
+
if (options?.limit) {
|
|
9439
|
+
query.limit = options.limit;
|
|
9440
|
+
}
|
|
9441
|
+
if (options?.offset) {
|
|
9442
|
+
query.start = options.offset;
|
|
9443
|
+
}
|
|
9444
|
+
const rawOrders = await this.client.list("salesOrders", query);
|
|
9445
|
+
let orders = rawOrders.filter((o) => !o.deleted).map((o) => transformStelSalesOrder(o, this.currencyCode));
|
|
9446
|
+
if (options?.search) {
|
|
9447
|
+
const needle = options.search.toLowerCase();
|
|
9448
|
+
orders = orders.filter(
|
|
9449
|
+
(o) => o.orderNumber.toLowerCase().includes(needle) || o.email.toLowerCase().includes(needle)
|
|
9450
|
+
);
|
|
9451
|
+
}
|
|
9452
|
+
if (options?.status) {
|
|
9453
|
+
const statuses = Array.isArray(options.status) ? options.status : [options.status];
|
|
9454
|
+
orders = orders.filter((o) => statuses.includes(o.status));
|
|
9455
|
+
}
|
|
9456
|
+
return { orders, count: orders.length };
|
|
9457
|
+
} catch (error) {
|
|
9458
|
+
this.logger.error("getAdminOrders failed", { options, error });
|
|
9459
|
+
throw this.wrapError("getAdminOrders", error);
|
|
9460
|
+
}
|
|
9461
|
+
}
|
|
9462
|
+
cancelOrder(_orderId) {
|
|
9463
|
+
return this.notSupported("cancelOrder");
|
|
9464
|
+
}
|
|
9465
|
+
createFulfillment(_orderId, _items) {
|
|
9466
|
+
return this.notSupported("createFulfillment");
|
|
9467
|
+
}
|
|
9468
|
+
createRefund(_orderId, _amount, _reason) {
|
|
9469
|
+
return this.notSupported("createRefund");
|
|
9470
|
+
}
|
|
9471
|
+
// ============================================
|
|
9472
|
+
// Storefront Context
|
|
9473
|
+
// ============================================
|
|
9474
|
+
async getStorefrontContext() {
|
|
9475
|
+
return {
|
|
9476
|
+
id: this.storefrontId,
|
|
9477
|
+
name: this.companyName,
|
|
9478
|
+
platformType: "stelorder",
|
|
9479
|
+
requiresProductScoping: false,
|
|
9480
|
+
currencyCode: this.currencyCode,
|
|
9481
|
+
metadata: {
|
|
9482
|
+
apiBaseUrl: this.client.apiBaseUrl,
|
|
9483
|
+
singleCompany: true,
|
|
9484
|
+
providerVersion: this.version
|
|
9485
|
+
}
|
|
9486
|
+
};
|
|
9487
|
+
}
|
|
9488
|
+
isStorefrontRequired() {
|
|
9489
|
+
return false;
|
|
9490
|
+
}
|
|
9491
|
+
// ============================================
|
|
9492
|
+
// Internal helpers
|
|
9493
|
+
// ============================================
|
|
9494
|
+
notSupported(operation) {
|
|
9495
|
+
return Promise.reject(
|
|
9496
|
+
new ProviderAPIError(
|
|
9497
|
+
`Stel Order does not support \`${operation}\`. STEL is an ERP adapter without cart/checkout/auth capabilities.`,
|
|
9498
|
+
"stelorder",
|
|
9499
|
+
501
|
|
9500
|
+
)
|
|
9501
|
+
);
|
|
9502
|
+
}
|
|
9503
|
+
wrapError(operation, error) {
|
|
9504
|
+
if (error instanceof ProviderAPIError) {
|
|
9505
|
+
return error;
|
|
9506
|
+
}
|
|
9507
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9508
|
+
return new ProviderAPIError(
|
|
9509
|
+
`Stel Order \`${operation}\` failed: ${message}`,
|
|
9510
|
+
"stelorder",
|
|
9511
|
+
500
|
|
9512
|
+
);
|
|
9513
|
+
}
|
|
9514
|
+
};
|
|
9515
|
+
|
|
8072
9516
|
// src/providers/mock/mock.ts
|
|
8073
9517
|
var FIXTURE_TIMESTAMP = "2026-01-01T00:00:00.000Z";
|
|
8074
9518
|
var DEFAULT_SITE_URL = "https://mock.thorcommerce.local";
|
|
@@ -9088,6 +10532,8 @@ function createProvider(options) {
|
|
|
9088
10532
|
return new MockProvider(config);
|
|
9089
10533
|
case "medusa":
|
|
9090
10534
|
return new MedusaProvider(config);
|
|
10535
|
+
case "stelorder":
|
|
10536
|
+
return new StelOrderProvider(config);
|
|
9091
10537
|
case "shopify":
|
|
9092
10538
|
return new ShopifyProvider(config);
|
|
9093
10539
|
case "bigcommerce":
|
|
@@ -9132,8 +10578,10 @@ function isProviderType(provider, type) {
|
|
|
9132
10578
|
ResourceNotFoundError,
|
|
9133
10579
|
SHOPIFY_CAPABILITIES,
|
|
9134
10580
|
SPREE_CAPABILITIES,
|
|
10581
|
+
STELORDER_CAPABILITIES,
|
|
9135
10582
|
ShopifyProvider,
|
|
9136
10583
|
SpreeProvider,
|
|
10584
|
+
StelOrderProvider,
|
|
9137
10585
|
StorefrontConfigError,
|
|
9138
10586
|
WOOCOMMERCE_CAPABILITIES,
|
|
9139
10587
|
WooCommerceProvider,
|