@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
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @thorprovider/adapters — BigCommerce Provider
|
|
3
|
+
* Implements CommerceProvider for BigCommerce REST Storefront API
|
|
4
|
+
*
|
|
5
|
+
* Uses raw fetch against BigCommerce Storefront REST API.
|
|
6
|
+
* The official `@bigcommerce/storefront-data-hooks` is deprecated (3 years unmaintained).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
Product,
|
|
11
|
+
GetProductsOptions,
|
|
12
|
+
Cart,
|
|
13
|
+
CartLineInput,
|
|
14
|
+
CartLineUpdate,
|
|
15
|
+
Collection,
|
|
16
|
+
CollectionProductsOptions,
|
|
17
|
+
GetCollectionsOptions,
|
|
18
|
+
ProductCategory,
|
|
19
|
+
GetCategoriesOptions,
|
|
20
|
+
Order,
|
|
21
|
+
GetOrdersOptions,
|
|
22
|
+
Customer,
|
|
23
|
+
Address,
|
|
24
|
+
PaymentMethod,
|
|
25
|
+
PaymentMethodsOptions,
|
|
26
|
+
ShippingMethod,
|
|
27
|
+
StockLocation,
|
|
28
|
+
InventoryLevel,
|
|
29
|
+
StockValidation,
|
|
30
|
+
FulfillmentOption,
|
|
31
|
+
StorefrontContext,
|
|
32
|
+
BackendCapabilities,
|
|
33
|
+
} from '@thorprovider/types';
|
|
34
|
+
import type { CommerceProvider, ProviderConfig } from '../../types/provider';
|
|
35
|
+
import { BIGCOMMERCE_CAPABILITIES } from '../../types/capabilities.types';
|
|
36
|
+
import { createLogger } from '../../core/logger';
|
|
37
|
+
import { ProviderAPIError } from '../../core/errors';
|
|
38
|
+
import {
|
|
39
|
+
transformProduct,
|
|
40
|
+
transformCart,
|
|
41
|
+
transformCollection,
|
|
42
|
+
transformCustomer,
|
|
43
|
+
transformOrder,
|
|
44
|
+
} from './transforms';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* BigCommerce provider configuration
|
|
48
|
+
*/
|
|
49
|
+
export interface BigCommerceProviderConfig extends ProviderConfig {
|
|
50
|
+
/** BigCommerce store hash */
|
|
51
|
+
storeHash: string;
|
|
52
|
+
/** Storefront API token (JWT) */
|
|
53
|
+
storefrontApiToken: string;
|
|
54
|
+
/** Storefront API URL (usually https://store-{hash}.mybigcommerce.com) */
|
|
55
|
+
storefrontApiUrl: string;
|
|
56
|
+
/** Channel ID */
|
|
57
|
+
channelId: string;
|
|
58
|
+
/** Default currency code (default: 'USD') */
|
|
59
|
+
currencyCode?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class BigCommerceProvider implements CommerceProvider {
|
|
63
|
+
readonly name = 'bigcommerce';
|
|
64
|
+
readonly version = '1.0.0';
|
|
65
|
+
readonly capabilities: BackendCapabilities = BIGCOMMERCE_CAPABILITIES;
|
|
66
|
+
|
|
67
|
+
private logger: ReturnType<typeof createLogger>;
|
|
68
|
+
private storefrontApiUrl: string;
|
|
69
|
+
private storeHash: string;
|
|
70
|
+
private channelId: string;
|
|
71
|
+
private currencyCode: string;
|
|
72
|
+
private headers: Record<string, string>;
|
|
73
|
+
|
|
74
|
+
constructor(config: BigCommerceProviderConfig) {
|
|
75
|
+
this.logger = createLogger('BigCommerceProvider', config.debug);
|
|
76
|
+
this.storefrontApiUrl = config.storefrontApiUrl.replace(/\/$/, '');
|
|
77
|
+
this.storeHash = config.storeHash;
|
|
78
|
+
this.channelId = config.channelId;
|
|
79
|
+
this.currencyCode = config.currencyCode ?? 'USD';
|
|
80
|
+
this.headers = {
|
|
81
|
+
'Content-Type': 'application/json',
|
|
82
|
+
'Authorization': `Bearer ${config.storefrontApiToken}`,
|
|
83
|
+
};
|
|
84
|
+
this.logger.info('Initializing BigCommerce Provider', { storeHash: this.storeHash, channelId: this.channelId });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private async fetchApi<T = any>(path: string, init?: RequestInit): Promise<T> {
|
|
88
|
+
const url = `${this.storefrontApiUrl}${path}`;
|
|
89
|
+
const response = await fetch(url, {
|
|
90
|
+
...init,
|
|
91
|
+
headers: { ...this.headers, ...init?.headers },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new ProviderAPIError(
|
|
96
|
+
`BigCommerce API error: ${response.status} ${response.statusText}`,
|
|
97
|
+
'BIGCOMMERCE_API_ERROR',
|
|
98
|
+
response.status,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return response.json() as Promise<T>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ============================================
|
|
106
|
+
// Product Operations
|
|
107
|
+
// ============================================
|
|
108
|
+
|
|
109
|
+
async getProduct(handle: string): Promise<Product | undefined> {
|
|
110
|
+
try {
|
|
111
|
+
const data = await this.fetchApi<{ data: any[] }>(`/api/storefront/products?include=variants,options,images&keyword=${encodeURIComponent(handle)}`);
|
|
112
|
+
const product = data.data?.[0];
|
|
113
|
+
return product ? transformProduct(product, this.currencyCode) : undefined;
|
|
114
|
+
} catch {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async getProducts(options?: GetProductsOptions): Promise<Product[]> {
|
|
120
|
+
const limit = options?.first ?? 20;
|
|
121
|
+
let path = `/api/storefront/products?include=variants,options,images&limit=${limit}`;
|
|
122
|
+
if (options?.query) path += `&keyword=${encodeURIComponent(options.query)}`;
|
|
123
|
+
|
|
124
|
+
const data = await this.fetchApi<{ data: any[] }>(path);
|
|
125
|
+
return (data.data ?? []).map((p) => transformProduct(p, this.currencyCode));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async getProductRecommendations(_productId: string): Promise<Product[]> {
|
|
129
|
+
return []; // BigCommerce doesn't have a Storefront recommendation API
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ============================================
|
|
133
|
+
// Collection Operations (BigCommerce Categories)
|
|
134
|
+
// ============================================
|
|
135
|
+
|
|
136
|
+
async getCollection(handle: string): Promise<Collection | undefined> {
|
|
137
|
+
try {
|
|
138
|
+
const data = await this.fetchApi<{ data: any[] }>(`/api/storefront/categories?url=/${handle}/`);
|
|
139
|
+
const cat = data.data?.[0];
|
|
140
|
+
return cat ? transformCollection(cat) : undefined;
|
|
141
|
+
} catch {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async getCollections(options?: GetCollectionsOptions): Promise<Collection[]> {
|
|
147
|
+
const limit = options?.limit ?? 50;
|
|
148
|
+
const data = await this.fetchApi<{ data: any[] }>(`/api/storefront/categories?limit=${limit}`);
|
|
149
|
+
return (data.data ?? []).map(transformCollection);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async getCollectionProducts(handle: string, options?: CollectionProductsOptions): Promise<Product[]> {
|
|
153
|
+
const limit = options?.first ?? 20;
|
|
154
|
+
const data = await this.fetchApi<{ data: any[] }>(
|
|
155
|
+
`/api/storefront/products?include=variants,options,images&categories:in=${handle}&limit=${limit}`,
|
|
156
|
+
);
|
|
157
|
+
return (data.data ?? []).map((p) => transformProduct(p, this.currencyCode));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============================================
|
|
161
|
+
// Category Operations
|
|
162
|
+
// ============================================
|
|
163
|
+
|
|
164
|
+
async getCategory(idOrHandle: string): Promise<ProductCategory | undefined> {
|
|
165
|
+
return this.getCollection(idOrHandle) as any;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async getCategories(options?: GetCategoriesOptions): Promise<ProductCategory[]> {
|
|
169
|
+
return this.getCollections(options) as any;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async getCategoryProducts(handle: string, options?: CollectionProductsOptions): Promise<Product[]> {
|
|
173
|
+
return this.getCollectionProducts(handle, options);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ============================================
|
|
177
|
+
// Cart Operations
|
|
178
|
+
// ============================================
|
|
179
|
+
|
|
180
|
+
async createCart(): Promise<Cart> {
|
|
181
|
+
const data = await this.fetchApi<{ data: any }>('/api/storefront/carts', {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
body: JSON.stringify({ line_items: [] }),
|
|
184
|
+
});
|
|
185
|
+
return transformCart(data.data ?? data, this.currencyCode);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async getCart(cartId: string): Promise<Cart | undefined> {
|
|
189
|
+
try {
|
|
190
|
+
const data = await this.fetchApi<{ data: any }>(`/api/storefront/carts/${cartId}?include=line_items.physical_items.options`);
|
|
191
|
+
return transformCart(data.data ?? data, this.currencyCode);
|
|
192
|
+
} catch {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async addToCart(cartId: string, lines: CartLineInput[]): Promise<Cart> {
|
|
198
|
+
const lineItems = lines.map((l) => ({
|
|
199
|
+
product_id: parseInt(l.merchandiseId, 10),
|
|
200
|
+
quantity: l.quantity,
|
|
201
|
+
}));
|
|
202
|
+
|
|
203
|
+
const data = await this.fetchApi<{ data: any }>(`/api/storefront/carts/${cartId}/items`, {
|
|
204
|
+
method: 'POST',
|
|
205
|
+
body: JSON.stringify({ line_items: lineItems }),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return transformCart(data.data ?? data, this.currencyCode);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async removeFromCart(cartId: string, lineIds: string[]): Promise<Cart> {
|
|
212
|
+
for (const lineId of lineIds) {
|
|
213
|
+
await this.fetchApi(`/api/storefront/carts/${cartId}/items/${lineId}`, { method: 'DELETE' });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const cart = await this.getCart(cartId);
|
|
217
|
+
if (!cart) throw new ProviderAPIError('Cart not found after removal', 'BIGCOMMERCE_CART_ERROR');
|
|
218
|
+
return cart;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async updateCart(cartId: string, lines: CartLineUpdate[]): Promise<Cart> {
|
|
222
|
+
for (const line of lines) {
|
|
223
|
+
await this.fetchApi(`/api/storefront/carts/${cartId}/items/${line.id}`, {
|
|
224
|
+
method: 'PUT',
|
|
225
|
+
body: JSON.stringify({ line_item: { quantity: line.quantity } }),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const cart = await this.getCart(cartId);
|
|
230
|
+
if (!cart) throw new ProviderAPIError('Cart not found after update', 'BIGCOMMERCE_CART_ERROR');
|
|
231
|
+
return cart;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ============================================
|
|
235
|
+
// Checkout Operations (Hosted)
|
|
236
|
+
// ============================================
|
|
237
|
+
|
|
238
|
+
async completeCheckout(_cartId: string): Promise<Order> {
|
|
239
|
+
throw new ProviderAPIError(
|
|
240
|
+
'BigCommerce uses Embedded Checkout. Redirect the buyer to cart.checkoutUrl.',
|
|
241
|
+
'BIGCOMMERCE_HOSTED_CHECKOUT',
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async getShippingMethods(_cartId: string): Promise<ShippingMethod[]> {
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async getRegionCountries(): Promise<import('@thorprovider/types').Country[]> {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async addShippingMethod(_cartId: string, _methodId: string): Promise<Cart> {
|
|
254
|
+
throw new ProviderAPIError('BigCommerce handles shipping in Embedded Checkout.', 'BIGCOMMERCE_HOSTED_CHECKOUT');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async initiatePaymentSession(_cartId: string, _providerId: string): Promise<void> {
|
|
258
|
+
throw new ProviderAPIError('BigCommerce handles payment in Embedded Checkout.', 'BIGCOMMERCE_HOSTED_CHECKOUT');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async applyDiscountCode(cartId: string, code: string): Promise<Cart> {
|
|
262
|
+
const data = await this.fetchApi<{ data: any }>(`/api/storefront/carts/${cartId}/coupons`, {
|
|
263
|
+
method: 'POST',
|
|
264
|
+
body: JSON.stringify({ coupon_code: code }),
|
|
265
|
+
});
|
|
266
|
+
return transformCart(data.data ?? data, this.currencyCode);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async removeDiscountCode(cartId: string, code: string): Promise<Cart> {
|
|
270
|
+
await this.fetchApi(`/api/storefront/carts/${cartId}/coupons/${encodeURIComponent(code)}`, { method: 'DELETE' });
|
|
271
|
+
const cart = await this.getCart(cartId);
|
|
272
|
+
if (!cart) throw new ProviderAPIError('Cart not found after coupon removal', 'BIGCOMMERCE_CART_ERROR');
|
|
273
|
+
return cart;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ============================================
|
|
277
|
+
// Customer, Order, Payment, Inventory, Admin
|
|
278
|
+
// ============================================
|
|
279
|
+
|
|
280
|
+
async getOrders(_options: GetOrdersOptions): Promise<Order[]> {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async getPaymentMethods(_options: PaymentMethodsOptions): Promise<PaymentMethod[]> {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async getStockLocations(): Promise<StockLocation[]> { return []; }
|
|
289
|
+
async checkInventoryLevels(): Promise<InventoryLevel[]> { return []; }
|
|
290
|
+
async validateCartStock(): Promise<StockValidation> { return { allAvailable: true, unavailableItems: [] }; }
|
|
291
|
+
async getFulfillmentOptions(): Promise<FulfillmentOption[]> { return []; }
|
|
292
|
+
|
|
293
|
+
async cancelOrder(_orderId: string): Promise<Order> {
|
|
294
|
+
throw new ProviderAPIError('Order cancellation requires BigCommerce Management API.', 'BIGCOMMERCE_ADMIN_REQUIRED');
|
|
295
|
+
}
|
|
296
|
+
async createFulfillment(_orderId: string): Promise<void> {
|
|
297
|
+
throw new ProviderAPIError('Fulfillment requires BigCommerce Management API.', 'BIGCOMMERCE_ADMIN_REQUIRED');
|
|
298
|
+
}
|
|
299
|
+
async createRefund(_orderId: string): Promise<void> {
|
|
300
|
+
throw new ProviderAPIError('Refunds require BigCommerce Management API.', 'BIGCOMMERCE_ADMIN_REQUIRED');
|
|
301
|
+
}
|
|
302
|
+
async getAdminOrders(): Promise<{ orders: Order[]; count: number }> { return { orders: [], count: 0 }; }
|
|
303
|
+
|
|
304
|
+
async getStorefrontContext(): Promise<StorefrontContext> {
|
|
305
|
+
return {
|
|
306
|
+
id: this.channelId,
|
|
307
|
+
name: `BigCommerce Store ${this.storeHash}`,
|
|
308
|
+
platformType: 'bigcommerce',
|
|
309
|
+
requiresProductScoping: false,
|
|
310
|
+
currencyCode: this.currencyCode,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
isStorefrontRequired(): boolean { return false; }
|
|
315
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @thorprovider/adapters — BigCommerce Provider
|
|
3
|
+
* Transform functions: BigCommerce REST API → @thorprovider/types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
Product,
|
|
8
|
+
ProductVariant,
|
|
9
|
+
ProductOption,
|
|
10
|
+
Cart,
|
|
11
|
+
CartItem,
|
|
12
|
+
Collection,
|
|
13
|
+
Money,
|
|
14
|
+
Image,
|
|
15
|
+
Order,
|
|
16
|
+
OrderItem,
|
|
17
|
+
OrderStatus,
|
|
18
|
+
PaymentStatus,
|
|
19
|
+
FulfillmentStatus,
|
|
20
|
+
Customer,
|
|
21
|
+
Address,
|
|
22
|
+
} from '@thorprovider/types';
|
|
23
|
+
|
|
24
|
+
// ============================================
|
|
25
|
+
// Money & Image Transforms
|
|
26
|
+
// ============================================
|
|
27
|
+
|
|
28
|
+
function transformMoney(amount: number | string | null | undefined, currencyCode: string = 'USD'): Money {
|
|
29
|
+
const numAmount = typeof amount === 'string' ? parseFloat(amount) : (amount ?? 0);
|
|
30
|
+
return {
|
|
31
|
+
amount: numAmount.toFixed(2),
|
|
32
|
+
currencyCode: currencyCode.toUpperCase(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function transformImage(url: string, alt: string = ''): Image {
|
|
37
|
+
return { url, altText: alt, width: 800, height: 800 };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ============================================
|
|
41
|
+
// Product Transforms
|
|
42
|
+
// ============================================
|
|
43
|
+
|
|
44
|
+
export function transformProduct(raw: any, currencyCode: string = 'USD'): Product {
|
|
45
|
+
const images: Image[] = (raw.images ?? []).map((img: any) =>
|
|
46
|
+
transformImage(img.url_standard ?? img.url_thumbnail ?? '', img.description ?? ''),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const variants: ProductVariant[] = (raw.variants ?? []).map((v: any) => ({
|
|
50
|
+
id: String(v.id),
|
|
51
|
+
title: v.option_display_name ?? `Variant ${v.id}`,
|
|
52
|
+
availableForSale: (v.purchasing_disabled !== true),
|
|
53
|
+
price: transformMoney(v.price ?? raw.price, currencyCode),
|
|
54
|
+
compareAtPrice: v.sale_price ? transformMoney(v.sale_price, currencyCode) : undefined,
|
|
55
|
+
selectedOptions: (v.option_values ?? []).map((ov: any) => ({
|
|
56
|
+
name: ov.option_display_name ?? '',
|
|
57
|
+
value: ov.label ?? '',
|
|
58
|
+
})),
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
const options: ProductOption[] = (raw.options ?? []).map((opt: any) => ({
|
|
62
|
+
id: String(opt.id),
|
|
63
|
+
name: opt.display_name ?? '',
|
|
64
|
+
values: (opt.option_values ?? []).map((val: any) => val.label ?? ''),
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
const price = raw.price ?? raw.calculated_price?.as_entered ?? 0;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
id: String(raw.id),
|
|
71
|
+
handle: raw.custom_url?.url?.replace(/^\//, '').replace(/\/$/, '') ?? String(raw.id),
|
|
72
|
+
title: raw.name ?? '',
|
|
73
|
+
description: raw.plain_text_description ?? raw.description ?? '',
|
|
74
|
+
descriptionHtml: raw.description ?? '',
|
|
75
|
+
availableForSale: raw.availability === 'available' || raw.is_visible !== false,
|
|
76
|
+
featuredImage: images[0] ?? { url: '', altText: '', width: 800, height: 800 },
|
|
77
|
+
images,
|
|
78
|
+
options,
|
|
79
|
+
variants,
|
|
80
|
+
priceRange: {
|
|
81
|
+
minVariantPrice: transformMoney(price, currencyCode),
|
|
82
|
+
maxVariantPrice: transformMoney(price, currencyCode),
|
|
83
|
+
},
|
|
84
|
+
seo: {
|
|
85
|
+
title: raw.page_title ?? raw.name ?? '',
|
|
86
|
+
description: raw.meta_description ?? '',
|
|
87
|
+
},
|
|
88
|
+
tags: [],
|
|
89
|
+
createdAt: raw.date_created ?? new Date().toISOString(),
|
|
90
|
+
updatedAt: raw.date_modified ?? new Date().toISOString(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================
|
|
95
|
+
// Collection Transforms (BigCommerce "Categories" or "Brands")
|
|
96
|
+
// ============================================
|
|
97
|
+
|
|
98
|
+
export function transformCollection(raw: any): Collection {
|
|
99
|
+
return {
|
|
100
|
+
id: String(raw.id ?? ''),
|
|
101
|
+
handle: raw.custom_url?.url?.replace(/^\//, '').replace(/\/$/, '') ?? String(raw.id),
|
|
102
|
+
title: raw.name ?? '',
|
|
103
|
+
description: raw.description ?? '',
|
|
104
|
+
seo: {
|
|
105
|
+
title: raw.page_title ?? raw.name ?? '',
|
|
106
|
+
description: raw.meta_description ?? '',
|
|
107
|
+
},
|
|
108
|
+
updatedAt: raw.date_modified ?? new Date().toISOString(),
|
|
109
|
+
path: `/collections/${raw.custom_url?.url?.replace(/^\//, '').replace(/\/$/, '') ?? raw.id}`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ============================================
|
|
114
|
+
// Cart Transforms
|
|
115
|
+
// ============================================
|
|
116
|
+
|
|
117
|
+
export function transformCart(raw: any, currencyCode: string = 'USD'): Cart {
|
|
118
|
+
const lineItems = Object.values(raw.line_items ?? {}).flat() as any[];
|
|
119
|
+
|
|
120
|
+
const lines: CartItem[] = lineItems.map((item: any) => ({
|
|
121
|
+
id: item.id,
|
|
122
|
+
quantity: item.quantity ?? 1,
|
|
123
|
+
cost: {
|
|
124
|
+
totalAmount: transformMoney(item.extended_sale_price ?? item.sale_price ?? 0, currencyCode),
|
|
125
|
+
},
|
|
126
|
+
merchandise: {
|
|
127
|
+
id: String(item.variant_id ?? item.product_id),
|
|
128
|
+
title: item.name ?? '',
|
|
129
|
+
selectedOptions: (item.options ?? []).map((opt: any) => ({
|
|
130
|
+
name: opt.name ?? '',
|
|
131
|
+
value: opt.value ?? '',
|
|
132
|
+
})),
|
|
133
|
+
product: {
|
|
134
|
+
id: String(item.product_id),
|
|
135
|
+
handle: String(item.product_id),
|
|
136
|
+
title: item.name ?? '',
|
|
137
|
+
featuredImage: item.image_url
|
|
138
|
+
? transformImage(item.image_url)
|
|
139
|
+
: { url: '', altText: '', width: 800, height: 800 },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
id: raw.id,
|
|
146
|
+
checkoutUrl: raw.redirect_urls?.checkout_url ?? '',
|
|
147
|
+
totalQuantity: lines.reduce((sum, l) => sum + l.quantity, 0),
|
|
148
|
+
cost: {
|
|
149
|
+
subtotalAmount: transformMoney(raw.base_amount ?? 0, currencyCode),
|
|
150
|
+
totalAmount: transformMoney(raw.cart_amount ?? 0, currencyCode),
|
|
151
|
+
},
|
|
152
|
+
lines,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ============================================
|
|
157
|
+
// Customer Transforms
|
|
158
|
+
// ============================================
|
|
159
|
+
|
|
160
|
+
export function transformCustomer(raw: any): Customer {
|
|
161
|
+
return {
|
|
162
|
+
id: String(raw.id),
|
|
163
|
+
email: raw.email ?? '',
|
|
164
|
+
firstName: raw.first_name ?? '',
|
|
165
|
+
lastName: raw.last_name ?? '',
|
|
166
|
+
phone: raw.phone ?? '',
|
|
167
|
+
hasAccount: true,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function transformAddress(raw: any): Address {
|
|
172
|
+
return {
|
|
173
|
+
id: String(raw.id ?? ''),
|
|
174
|
+
firstName: raw.first_name ?? '',
|
|
175
|
+
lastName: raw.last_name ?? '',
|
|
176
|
+
address1: raw.address1 ?? '',
|
|
177
|
+
address2: raw.address2 ?? '',
|
|
178
|
+
city: raw.city ?? '',
|
|
179
|
+
province: raw.state_or_province ?? '',
|
|
180
|
+
countryCode: raw.country_code ?? '',
|
|
181
|
+
postalCode: raw.postal_code ?? '',
|
|
182
|
+
phone: raw.phone ?? '',
|
|
183
|
+
company: raw.company ?? '',
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ============================================
|
|
188
|
+
// Order Transforms
|
|
189
|
+
// ============================================
|
|
190
|
+
|
|
191
|
+
export function transformOrder(raw: any, currencyCode: string = 'USD'): Order {
|
|
192
|
+
const items: OrderItem[] = (raw.products ?? []).map((p: any) => ({
|
|
193
|
+
id: String(p.id),
|
|
194
|
+
title: p.name ?? '',
|
|
195
|
+
variantTitle: p.sku ?? '',
|
|
196
|
+
quantity: p.quantity ?? 1,
|
|
197
|
+
price: transformMoney(p.base_price ?? 0, currencyCode),
|
|
198
|
+
total: transformMoney(p.base_total ?? 0, currencyCode),
|
|
199
|
+
productId: String(p.product_id),
|
|
200
|
+
}));
|
|
201
|
+
|
|
202
|
+
const statusMap: Record<string, OrderStatus> = {
|
|
203
|
+
'Incomplete': 'pending',
|
|
204
|
+
'Pending': 'pending',
|
|
205
|
+
'Shipped': 'completed',
|
|
206
|
+
'Partially Shipped': 'processing',
|
|
207
|
+
'Refunded': 'refunded',
|
|
208
|
+
'Cancelled': 'cancelled',
|
|
209
|
+
'Completed': 'completed',
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
id: String(raw.id),
|
|
214
|
+
orderNumber: String(raw.id),
|
|
215
|
+
email: raw.billing_address?.email ?? '',
|
|
216
|
+
status: statusMap[raw.status] ?? 'pending',
|
|
217
|
+
paymentStatus: raw.payment_status === 'paid' ? 'paid' : 'pending',
|
|
218
|
+
fulfillmentStatus: raw.status === 'Shipped' ? 'fulfilled' : 'unfulfilled',
|
|
219
|
+
items,
|
|
220
|
+
total: transformMoney(raw.total_inc_tax ?? 0, currencyCode),
|
|
221
|
+
subtotal: transformMoney(raw.subtotal_inc_tax ?? 0, currencyCode),
|
|
222
|
+
tax: transformMoney(raw.total_tax ?? 0, currencyCode),
|
|
223
|
+
shipping: transformMoney(raw.shipping_cost_inc_tax ?? 0, currencyCode),
|
|
224
|
+
createdAt: raw.date_created ?? new Date().toISOString(),
|
|
225
|
+
updatedAt: raw.date_modified ?? new Date().toISOString(),
|
|
226
|
+
};
|
|
227
|
+
}
|
package/src/providers/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @thorprovider/adapters
|
|
2
|
+
* @thorprovider/adapters v2.0
|
|
3
3
|
* Providers index
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -9,5 +9,20 @@ export type { MedusaProviderConfig } from './medusa';
|
|
|
9
9
|
export { StelOrderProvider } from './stelorder';
|
|
10
10
|
export type { StelOrderProviderConfig } from './stelorder';
|
|
11
11
|
|
|
12
|
+
export { ShopifyProvider } from './shopify';
|
|
13
|
+
export type { ShopifyProviderConfig } from './shopify';
|
|
14
|
+
|
|
15
|
+
export { BigCommerceProvider } from './bigcommerce';
|
|
16
|
+
export type { BigCommerceProviderConfig } from './bigcommerce';
|
|
17
|
+
|
|
18
|
+
export { WooCommerceProvider } from './woocommerce';
|
|
19
|
+
export type { WooCommerceProviderConfig } from './woocommerce';
|
|
20
|
+
|
|
21
|
+
export { SpreeProvider } from './spree';
|
|
22
|
+
export type { SpreeProviderConfig } from './spree';
|
|
23
|
+
|
|
24
|
+
export { MagentoProvider } from './magento';
|
|
25
|
+
export type { MagentoProviderConfig } from './magento';
|
|
26
|
+
|
|
12
27
|
export { MockProvider } from './mock';
|
|
13
28
|
export type { MockProviderConfig } from './mock';
|