@thorprovider/adapters 4.3.0 → 5.0.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.
@@ -1,310 +0,0 @@
1
- /**
2
- * @thorprovider/adapters — WooCommerce Provider
3
- * Implements CommerceProvider for WooCommerce Store API + REST API v3
4
- *
5
- * Store API: Unauthenticated, cookie-based sessions — products, cart, checkout
6
- * REST API v3: Authenticated via Application Passwords — customers, orders
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 { WOOCOMMERCE_CAPABILITIES } from '../../types/capabilities.types';
36
- import { createLogger } from '../../core/logger';
37
- import { ProviderAPIError } from '../../core/errors';
38
- import { transformProduct, transformCart, transformCollection, transformOrder } from './transforms';
39
-
40
- /**
41
- * WooCommerce provider configuration
42
- */
43
- export interface WooCommerceProviderConfig extends ProviderConfig {
44
- /** WordPress site URL (e.g., 'https://mystore.com') */
45
- url: string;
46
- /** WooCommerce REST API consumer key (for authenticated operations) */
47
- consumerKey?: string;
48
- /** WooCommerce REST API consumer secret (for authenticated operations) */
49
- consumerSecret?: string;
50
- /** Default currency code (default: 'USD') */
51
- currencyCode?: string;
52
- }
53
-
54
- export class WooCommerceProvider implements CommerceProvider {
55
- readonly name = 'woocommerce';
56
- readonly version = '1.0.0';
57
- readonly capabilities: BackendCapabilities = WOOCOMMERCE_CAPABILITIES;
58
-
59
- private logger: ReturnType<typeof createLogger>;
60
- private baseUrl: string;
61
- private storeApiBase: string;
62
- private restApiBase: string;
63
- private currencyCode: string;
64
- private authHeader?: string;
65
-
66
- constructor(config: WooCommerceProviderConfig) {
67
- this.logger = createLogger('WooCommerceProvider', config.debug);
68
- this.baseUrl = config.url.replace(/\/$/, '');
69
- this.storeApiBase = `${this.baseUrl}/wp-json/wc/store/v1`;
70
- this.restApiBase = `${this.baseUrl}/wp-json/wc/v3`;
71
- this.currencyCode = config.currencyCode ?? 'USD';
72
-
73
- if (config.consumerKey && config.consumerSecret) {
74
- this.authHeader = 'Basic ' + btoa(`${config.consumerKey}:${config.consumerSecret}`);
75
- }
76
-
77
- this.logger.info('Initializing WooCommerce Provider', { baseUrl: this.baseUrl });
78
- }
79
-
80
- private async storeApiFetch<T = any>(path: string, init?: RequestInit): Promise<T> {
81
- const response = await fetch(`${this.storeApiBase}${path}`, {
82
- ...init,
83
- headers: { 'Content-Type': 'application/json', ...init?.headers },
84
- });
85
- if (!response.ok) {
86
- throw new ProviderAPIError(`WooCommerce Store API error: ${response.status}`, 'WOOCOMMERCE_API_ERROR', response.status);
87
- }
88
- return response.json() as Promise<T>;
89
- }
90
-
91
- private async restApiFetch<T = any>(path: string, init?: RequestInit): Promise<T> {
92
- if (!this.authHeader) {
93
- throw new ProviderAPIError('WooCommerce REST API requires consumer key/secret.', 'WOOCOMMERCE_AUTH_REQUIRED');
94
- }
95
- const response = await fetch(`${this.restApiBase}${path}`, {
96
- ...init,
97
- headers: { 'Content-Type': 'application/json', 'Authorization': this.authHeader, ...init?.headers },
98
- });
99
- if (!response.ok) {
100
- throw new ProviderAPIError(`WooCommerce REST API error: ${response.status}`, 'WOOCOMMERCE_API_ERROR', response.status);
101
- }
102
- return response.json() as Promise<T>;
103
- }
104
-
105
- // ============================================
106
- // Product Operations (Store API)
107
- // ============================================
108
-
109
- async getProduct(handle: string): Promise<Product | undefined> {
110
- try {
111
- const products = await this.storeApiFetch<any[]>(`/products?slug=${encodeURIComponent(handle)}`);
112
- return products[0] ? transformProduct(products[0], this.currencyCode) : undefined;
113
- } catch {
114
- return undefined;
115
- }
116
- }
117
-
118
- async getProducts(options?: GetProductsOptions): Promise<Product[]> {
119
- let path = `/products?per_page=${options?.first ?? 20}`;
120
- if (options?.query) path += `&search=${encodeURIComponent(options.query)}`;
121
- if (options?.sortKey) {
122
- const [orderby, order] = options.sortKey.includes('-desc')
123
- ? [options.sortKey.replace('-desc', ''), 'desc']
124
- : [options.sortKey.replace('-asc', ''), 'asc'];
125
- path += `&orderby=${orderby}&order=${order}`;
126
- }
127
- const products = await this.storeApiFetch<any[]>(path);
128
- return products.map((p) => transformProduct(p, this.currencyCode));
129
- }
130
-
131
- async getProductRecommendations(_productId: string): Promise<Product[]> {
132
- return [];
133
- }
134
-
135
- // ============================================
136
- // Collection/Category Operations (Store API)
137
- // ============================================
138
-
139
- async getCollection(handle: string): Promise<Collection | undefined> {
140
- try {
141
- const cats = await this.storeApiFetch<any[]>(`/products/categories?slug=${encodeURIComponent(handle)}`);
142
- return cats[0] ? transformCollection(cats[0]) : undefined;
143
- } catch {
144
- return undefined;
145
- }
146
- }
147
-
148
- async getCollections(options?: GetCollectionsOptions): Promise<Collection[]> {
149
- const cats = await this.storeApiFetch<any[]>(`/products/categories?per_page=${options?.limit ?? 50}`);
150
- return cats.map(transformCollection);
151
- }
152
-
153
- async getCollectionProducts(handle: string, options?: CollectionProductsOptions): Promise<Product[]> {
154
- const products = await this.storeApiFetch<any[]>(
155
- `/products?category=${encodeURIComponent(handle)}&per_page=${options?.first ?? 20}`,
156
- );
157
- return products.map((p) => transformProduct(p, this.currencyCode));
158
- }
159
-
160
- async getCategory(idOrHandle: string): Promise<ProductCategory | undefined> {
161
- return this.getCollection(idOrHandle) as any;
162
- }
163
-
164
- async getCategories(options?: GetCategoriesOptions): Promise<ProductCategory[]> {
165
- return this.getCollections(options) as any;
166
- }
167
-
168
- async getCategoryProducts(handle: string, options?: CollectionProductsOptions): Promise<Product[]> {
169
- return this.getCollectionProducts(handle, options);
170
- }
171
-
172
- // ============================================
173
- // Cart Operations (Store API)
174
- // ============================================
175
-
176
- async createCart(): Promise<Cart> {
177
- const cart = await this.storeApiFetch<any>('/cart');
178
- return transformCart(cart, this.currencyCode);
179
- }
180
-
181
- async getCart(_cartId: string): Promise<Cart | undefined> {
182
- try {
183
- const cart = await this.storeApiFetch<any>('/cart');
184
- return transformCart(cart, this.currencyCode);
185
- } catch {
186
- return undefined;
187
- }
188
- }
189
-
190
- async addToCart(_cartId: string, lines: CartLineInput[]): Promise<Cart> {
191
- for (const line of lines) {
192
- await this.storeApiFetch('/cart/add-item', {
193
- method: 'POST',
194
- body: JSON.stringify({ id: parseInt(line.merchandiseId, 10), quantity: line.quantity }),
195
- });
196
- }
197
- const cart = await this.storeApiFetch<any>('/cart');
198
- return transformCart(cart, this.currencyCode);
199
- }
200
-
201
- async removeFromCart(_cartId: string, lineIds: string[]): Promise<Cart> {
202
- for (const key of lineIds) {
203
- await this.storeApiFetch('/cart/remove-item', {
204
- method: 'POST',
205
- body: JSON.stringify({ key }),
206
- });
207
- }
208
- const cart = await this.storeApiFetch<any>('/cart');
209
- return transformCart(cart, this.currencyCode);
210
- }
211
-
212
- async updateCart(_cartId: string, lines: CartLineUpdate[]): Promise<Cart> {
213
- for (const line of lines) {
214
- await this.storeApiFetch('/cart/update-item', {
215
- method: 'POST',
216
- body: JSON.stringify({ key: line.id, quantity: line.quantity }),
217
- });
218
- }
219
- const cart = await this.storeApiFetch<any>('/cart');
220
- return transformCart(cart, this.currencyCode);
221
- }
222
-
223
- // ============================================
224
- // Checkout Operations (Store API)
225
- // ============================================
226
-
227
- async completeCheckout(_cartId: string): Promise<Order> {
228
- const result = await this.storeApiFetch<any>('/checkout', { method: 'POST', body: JSON.stringify({}) });
229
- return transformOrder(result, this.currencyCode);
230
- }
231
-
232
- async getShippingMethods(_cartId: string): Promise<ShippingMethod[]> {
233
- return [];
234
- }
235
-
236
- async getRegionCountries(): Promise<import('@thorprovider/types').Country[]> {
237
- return [];
238
- }
239
-
240
- async addShippingMethod(_cartId: string, methodId: string): Promise<Cart> {
241
- await this.storeApiFetch('/cart/select-shipping-rate', {
242
- method: 'POST',
243
- body: JSON.stringify({ rate_id: methodId }),
244
- });
245
- const cart = await this.storeApiFetch<any>('/cart');
246
- return transformCart(cart, this.currencyCode);
247
- }
248
-
249
- async initiatePaymentSession(_cartId: string, _providerId: string): Promise<void> {
250
- // WooCommerce handles payment during checkout POST
251
- }
252
-
253
- async applyDiscountCode(_cartId: string, code: string): Promise<Cart> {
254
- await this.storeApiFetch('/cart/apply-coupon', {
255
- method: 'POST',
256
- body: JSON.stringify({ code }),
257
- });
258
- const cart = await this.storeApiFetch<any>('/cart');
259
- return transformCart(cart, this.currencyCode);
260
- }
261
-
262
- async removeDiscountCode(_cartId: string, code: string): Promise<Cart> {
263
- await this.storeApiFetch('/cart/remove-coupon', {
264
- method: 'POST',
265
- body: JSON.stringify({ code }),
266
- });
267
- const cart = await this.storeApiFetch<any>('/cart');
268
- return transformCart(cart, this.currencyCode);
269
- }
270
-
271
- // ============================================
272
- // Customer, Order, Payment, Inventory, Admin
273
- // ============================================
274
-
275
- async getOrders(_options: GetOrdersOptions): Promise<Order[]> {
276
- return [];
277
- }
278
-
279
- async getPaymentMethods(_options: PaymentMethodsOptions): Promise<PaymentMethod[]> {
280
- return [];
281
- }
282
-
283
- async getStockLocations(): Promise<StockLocation[]> { return []; }
284
- async checkInventoryLevels(): Promise<InventoryLevel[]> { return []; }
285
- async validateCartStock(): Promise<StockValidation> { return { allAvailable: true, unavailableItems: [] }; }
286
- async getFulfillmentOptions(): Promise<FulfillmentOption[]> { return []; }
287
-
288
- async cancelOrder(_orderId: string): Promise<Order> {
289
- throw new ProviderAPIError('Order cancellation requires WooCommerce REST API v3.', 'WOOCOMMERCE_AUTH_REQUIRED');
290
- }
291
- async createFulfillment(_orderId: string): Promise<void> {
292
- throw new ProviderAPIError('Fulfillment requires WooCommerce REST API v3.', 'WOOCOMMERCE_AUTH_REQUIRED');
293
- }
294
- async createRefund(_orderId: string): Promise<void> {
295
- throw new ProviderAPIError('Refunds require WooCommerce REST API v3.', 'WOOCOMMERCE_AUTH_REQUIRED');
296
- }
297
- async getAdminOrders(): Promise<{ orders: Order[]; count: number }> { return { orders: [], count: 0 }; }
298
-
299
- async getStorefrontContext(): Promise<StorefrontContext> {
300
- return {
301
- id: this.baseUrl,
302
- name: `WooCommerce ${this.baseUrl}`,
303
- platformType: 'woocommerce',
304
- requiresProductScoping: false,
305
- currencyCode: this.currencyCode,
306
- };
307
- }
308
-
309
- isStorefrontRequired(): boolean { return false; }
310
- }
@@ -1,254 +0,0 @@
1
- /**
2
- * @thorprovider/adapters — WooCommerce Provider
3
- * Transform functions: WooCommerce Store API responses → @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: string | number | null | undefined, currencyCode: string = 'USD'): Money {
29
- const numAmount = typeof amount === 'string' ? parseFloat(amount) : (amount ?? 0);
30
- return {
31
- amount: isNaN(numAmount) ? '0.00' : 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 (Store API format)
42
- // ============================================
43
-
44
- export function transformProduct(raw: any, currencyCode: string = 'USD'): Product {
45
- const images: Image[] = (raw.images ?? []).map((img: any) =>
46
- transformImage(img.src ?? '', img.alt ?? img.name ?? ''),
47
- );
48
-
49
- const variants: ProductVariant[] = (raw.variations ?? []).map((v: any) => ({
50
- id: String(v.id),
51
- title: v.name ?? '',
52
- availableForSale: v.in_stock !== false,
53
- price: transformMoney(v.prices?.price ?? v.price, currencyCode),
54
- compareAtPrice: v.prices?.regular_price
55
- ? transformMoney(v.prices.regular_price, currencyCode)
56
- : undefined,
57
- selectedOptions: (v.attributes ?? []).map((attr: any) => ({
58
- name: attr.name ?? '',
59
- value: attr.value ?? '',
60
- })),
61
- }));
62
-
63
- const options: ProductOption[] = (raw.attributes ?? [])
64
- .filter((attr: any) => attr.variation === true || attr.has_options === true)
65
- .map((attr: any) => ({
66
- id: String(attr.id),
67
- name: attr.name ?? '',
68
- values: (attr.terms ?? attr.options ?? []).map((t: any) =>
69
- typeof t === 'string' ? t : (t.name ?? t.value ?? ''),
70
- ),
71
- }));
72
-
73
- // WooCommerce Store API prices are in minor units (cents)
74
- const priceMinor = raw.prices?.price ?? '0';
75
- const priceDecimalPlaces = raw.prices?.currency_minor_unit ?? 2;
76
- const priceMajor = (parseInt(priceMinor, 10) / Math.pow(10, priceDecimalPlaces));
77
-
78
- return {
79
- id: String(raw.id),
80
- handle: raw.slug ?? String(raw.id),
81
- title: raw.name ?? '',
82
- description: raw.short_description ?? raw.description ?? '',
83
- descriptionHtml: raw.description ?? '',
84
- availableForSale: raw.is_purchasable !== false && raw.is_in_stock !== false,
85
- featuredImage: images[0] ?? { url: '', altText: '', width: 800, height: 800 },
86
- images,
87
- options,
88
- variants,
89
- priceRange: {
90
- minVariantPrice: transformMoney(priceMajor, currencyCode),
91
- maxVariantPrice: transformMoney(priceMajor, currencyCode),
92
- },
93
- seo: {
94
- title: raw.name ?? '',
95
- description: raw.short_description?.replace(/<[^>]*>/g, '') ?? '',
96
- },
97
- tags: [],
98
- createdAt: raw.date_created ?? new Date().toISOString(),
99
- updatedAt: raw.date_modified ?? new Date().toISOString(),
100
- };
101
- }
102
-
103
- // ============================================
104
- // Collection Transforms (WooCom categories)
105
- // ============================================
106
-
107
- export function transformCollection(raw: any): Collection {
108
- return {
109
- id: String(raw.id ?? ''),
110
- handle: raw.slug ?? String(raw.id),
111
- title: raw.name ?? '',
112
- description: raw.description ?? '',
113
- seo: {
114
- title: raw.name ?? '',
115
- description: raw.description?.replace(/<[^>]*>/g, '') ?? '',
116
- },
117
- updatedAt: new Date().toISOString(),
118
- path: `/collections/${raw.slug ?? raw.id}`,
119
- };
120
- }
121
-
122
- // ============================================
123
- // Cart Transforms (Store API cart format)
124
- // ============================================
125
-
126
- export function transformCart(raw: any, currencyCode: string = 'USD'): Cart {
127
- const lines: CartItem[] = (raw.items ?? []).map((item: any) => {
128
- const minorUnit = item.prices?.currency_minor_unit ?? 2;
129
- const divisor = Math.pow(10, minorUnit);
130
- const lineTotal = (parseInt(item.totals?.line_total ?? '0', 10)) / divisor;
131
-
132
- return {
133
- id: item.key ?? String(item.id),
134
- quantity: item.quantity ?? 1,
135
- cost: {
136
- totalAmount: transformMoney(lineTotal, currencyCode),
137
- },
138
- merchandise: {
139
- id: String(item.id),
140
- title: item.name ?? '',
141
- selectedOptions: (item.variation ?? []).map((v: any) => ({
142
- name: v.attribute ?? '',
143
- value: v.value ?? '',
144
- })),
145
- product: {
146
- id: String(item.id),
147
- handle: item.slug ?? String(item.id),
148
- title: item.name ?? '',
149
- featuredImage: item.images?.[0]
150
- ? transformImage(item.images[0].src, item.images[0].alt ?? '')
151
- : { url: '', altText: '', width: 800, height: 800 },
152
- },
153
- },
154
- };
155
- });
156
-
157
- const minorUnit = raw.totals?.currency_minor_unit ?? 2;
158
- const divisor = Math.pow(10, minorUnit);
159
- const subtotal = (parseInt(raw.totals?.total_items ?? '0', 10)) / divisor;
160
- const total = (parseInt(raw.totals?.total_price ?? '0', 10)) / divisor;
161
-
162
- return {
163
- id: raw.cart_key ?? undefined,
164
- checkoutUrl: '', // WooCommerce handles checkout via its own /checkout page
165
- totalQuantity: raw.items_count ?? lines.reduce((sum, l) => sum + l.quantity, 0),
166
- cost: {
167
- subtotalAmount: transformMoney(subtotal, currencyCode),
168
- totalAmount: transformMoney(total, currencyCode),
169
- },
170
- lines,
171
- };
172
- }
173
-
174
- // ============================================
175
- // Customer Transforms
176
- // ============================================
177
-
178
- export function transformCustomer(raw: any): Customer {
179
- return {
180
- id: String(raw.id),
181
- email: raw.email ?? '',
182
- firstName: raw.first_name ?? '',
183
- lastName: raw.last_name ?? '',
184
- phone: raw.billing?.phone ?? '',
185
- hasAccount: true,
186
- };
187
- }
188
-
189
- function transformWooAddress(raw: any): Address {
190
- return {
191
- id: undefined,
192
- firstName: raw.first_name ?? '',
193
- lastName: raw.last_name ?? '',
194
- address1: raw.address_1 ?? '',
195
- address2: raw.address_2 ?? '',
196
- city: raw.city ?? '',
197
- province: raw.state ?? '',
198
- countryCode: raw.country ?? '',
199
- postalCode: raw.postcode ?? '',
200
- phone: raw.phone ?? '',
201
- company: raw.company ?? '',
202
- };
203
- }
204
-
205
- // ============================================
206
- // Order Transforms
207
- // ============================================
208
-
209
- export function transformOrder(raw: any, currencyCode: string = 'USD'): Order {
210
- const items: OrderItem[] = (raw.line_items ?? []).map((item: any) => ({
211
- id: String(item.id),
212
- title: item.name ?? '',
213
- quantity: item.quantity ?? 1,
214
- price: transformMoney(item.price ?? 0, currencyCode),
215
- total: transformMoney(item.total ?? 0, currencyCode),
216
- productId: String(item.product_id),
217
- variantId: item.variation_id ? String(item.variation_id) : undefined,
218
- }));
219
-
220
- const statusMap: Record<string, OrderStatus> = {
221
- pending: 'pending',
222
- processing: 'processing',
223
- 'on-hold': 'pending',
224
- completed: 'completed',
225
- cancelled: 'cancelled',
226
- refunded: 'refunded',
227
- failed: 'cancelled',
228
- };
229
-
230
- const paymentMap: Record<string, PaymentStatus> = {
231
- pending: 'pending',
232
- processing: 'authorized',
233
- completed: 'paid',
234
- refunded: 'refunded',
235
- };
236
-
237
- return {
238
- id: String(raw.id),
239
- orderNumber: String(raw.number ?? raw.id),
240
- email: raw.billing?.email ?? '',
241
- status: statusMap[raw.status] ?? 'pending',
242
- paymentStatus: paymentMap[raw.status] ?? 'pending',
243
- fulfillmentStatus: raw.status === 'completed' ? 'fulfilled' : 'unfulfilled',
244
- items,
245
- total: transformMoney(raw.total ?? 0, currencyCode),
246
- subtotal: transformMoney(raw.subtotal ?? 0, currencyCode),
247
- tax: transformMoney(raw.total_tax ?? 0, currencyCode),
248
- shipping: transformMoney(raw.shipping_total ?? 0, currencyCode),
249
- shippingAddress: raw.shipping ? transformWooAddress(raw.shipping) : undefined,
250
- billingAddress: raw.billing ? transformWooAddress(raw.billing) : undefined,
251
- createdAt: raw.date_created ?? new Date().toISOString(),
252
- updatedAt: raw.date_modified ?? new Date().toISOString(),
253
- };
254
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * WooCommerce Provider — barrel exports
3
- */
4
- export { WooCommerceProvider } from './index';
5
- export type { WooCommerceProviderConfig } from './index';