@thorprovider/adapters 4.1.0 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,427 @@
1
+ import type {
2
+ Cart,
3
+ CartLineInput,
4
+ CartLineUpdate,
5
+ Collection,
6
+ CollectionProductsOptions,
7
+ GetCategoriesOptions,
8
+ GetCollectionsOptions,
9
+ GetOrdersOptions,
10
+ GetProductsOptions,
11
+ InventoryLevel,
12
+ Order,
13
+ PaymentMethodsOptions,
14
+ Product,
15
+ ProductCategory,
16
+ Region,
17
+ ShippingMethod,
18
+ StockLocation,
19
+ StockValidation,
20
+ StorefrontContext,
21
+ FulfillmentOption,
22
+ PaymentMethod,
23
+ } from '@thorprovider/types';
24
+ import type { CommerceProvider, ProviderConfig } from '../../types/provider';
25
+ import { ProviderAPIError, ResourceNotFoundError } from '../../core/errors';
26
+ import { MOCK_CAPABILITIES } from '../../types/capabilities.types';
27
+ import {
28
+ buildMockCart,
29
+ completeMockCheckout,
30
+ createMockCart,
31
+ getMockAdminOrders,
32
+ getMockAdminRegions,
33
+ getMockCategories,
34
+ getMockCategory,
35
+ getMockCollection,
36
+ getMockCollections,
37
+ getMockCartState,
38
+ getMockFulfillmentOptions,
39
+ getMockInventoryLevels,
40
+ getMockOrder,
41
+ getMockPaymentMethods,
42
+ getMockProduct,
43
+ getMockProductRecommendations,
44
+ getMockProducts,
45
+ getMockRegionCountries,
46
+ getMockRegions,
47
+ getMockShippingMethods,
48
+ getMockStockLocations,
49
+ saveMockCartState,
50
+ saveMockOrder,
51
+ validateMockCartStock,
52
+ } from './mock';
53
+
54
+ export interface MockProviderConfig extends ProviderConfig {
55
+ currencyCode?: string;
56
+ siteUrl?: string;
57
+ }
58
+
59
+ export class MockProvider implements CommerceProvider {
60
+ readonly name = 'mock';
61
+ readonly version = '1.0.0';
62
+ readonly capabilities = MOCK_CAPABILITIES;
63
+ readonly isMock = true;
64
+
65
+ private readonly currencyCode: string;
66
+ private readonly siteUrl: string;
67
+
68
+ constructor(config: MockProviderConfig = {}) {
69
+ this.currencyCode = config.currencyCode ?? 'USD';
70
+ this.siteUrl = config.siteUrl ?? 'https://mock.thorcommerce.local';
71
+ }
72
+
73
+ private hasVariant(merchandiseId: string, regionId?: string): boolean {
74
+ return getMockProducts({
75
+ currencyCode: this.currencyCode,
76
+ regionId,
77
+ }).some((product) =>
78
+ product.variants.some((variant) => variant.id === merchandiseId)
79
+ );
80
+ }
81
+
82
+ async getProduct(handle: string, options?: { regionId?: string }): Promise<Product | undefined> {
83
+ return getMockProduct(handle, options?.regionId);
84
+ }
85
+
86
+ async getProducts(options?: GetProductsOptions): Promise<Product[]> {
87
+ return getMockProducts({
88
+ currencyCode: this.currencyCode,
89
+ query: options?.query,
90
+ sortKey: options?.sortKey,
91
+ reverse: options?.reverse,
92
+ first: options?.first,
93
+ skip: options?.skip,
94
+ categoryIds: options?.categoryIds,
95
+ collectionIds: options?.collectionIds,
96
+ regionId: options?.regionId,
97
+ });
98
+ }
99
+
100
+ async getProductRecommendations(productId: string): Promise<Product[]> {
101
+ return getMockProductRecommendations(productId);
102
+ }
103
+
104
+ async getCollection(handle: string): Promise<Collection | undefined> {
105
+ return getMockCollection(handle);
106
+ }
107
+
108
+ async getCollections(options?: GetCollectionsOptions): Promise<Collection[]> {
109
+ const collections = getMockCollections();
110
+ const query = options?.query?.trim().toLowerCase();
111
+ const filtered = query
112
+ ? collections.filter((collection) =>
113
+ [collection.title, collection.description, collection.handle]
114
+ .join(' ')
115
+ .toLowerCase()
116
+ .includes(query)
117
+ )
118
+ : collections;
119
+
120
+ return filtered.slice(0, options?.limit ?? filtered.length);
121
+ }
122
+
123
+ async getCollectionProducts(handle: string, options?: CollectionProductsOptions): Promise<Product[]> {
124
+ const collection = getMockCollection(handle);
125
+ if (!collection) {
126
+ return [];
127
+ }
128
+
129
+ return getMockProducts({
130
+ currencyCode: this.currencyCode,
131
+ collectionIds: [collection.id],
132
+ sortKey: options?.sortKey,
133
+ reverse: options?.reverse,
134
+ first: options?.first,
135
+ });
136
+ }
137
+
138
+ async getCategory(idOrHandle: string): Promise<ProductCategory | undefined> {
139
+ return getMockCategory(idOrHandle);
140
+ }
141
+
142
+ async getCategories(options?: GetCategoriesOptions): Promise<ProductCategory[]> {
143
+ const categories = getMockCategories();
144
+ const query = options?.query?.trim().toLowerCase();
145
+
146
+ let filtered = query
147
+ ? categories.filter((category) =>
148
+ [category.name, category.description ?? '', category.handle]
149
+ .join(' ')
150
+ .toLowerCase()
151
+ .includes(query)
152
+ )
153
+ : categories;
154
+
155
+ if (options?.parentCategoryId) {
156
+ filtered = filtered.filter((category) => category.parentCategoryId === options.parentCategoryId);
157
+ }
158
+
159
+ return filtered.slice(0, options?.limit ?? filtered.length);
160
+ }
161
+
162
+ async getCategoryProducts(handle: string, options?: CollectionProductsOptions): Promise<Product[]> {
163
+ const category = getMockCategory(handle);
164
+ if (!category) {
165
+ return [];
166
+ }
167
+
168
+ return getMockProducts({
169
+ currencyCode: this.currencyCode,
170
+ categoryIds: [category.id],
171
+ sortKey: options?.sortKey,
172
+ reverse: options?.reverse,
173
+ first: options?.first,
174
+ });
175
+ }
176
+
177
+ async createCart(options?: { regionId?: string }): Promise<Cart> {
178
+ const cart = createMockCart(options?.regionId);
179
+ return buildMockCart(cart, this.currencyCode, this.siteUrl);
180
+ }
181
+
182
+ async getCart(cartId: string): Promise<Cart | undefined> {
183
+ const cart = getMockCartState(cartId);
184
+ return cart ? buildMockCart(cart, this.currencyCode, this.siteUrl) : undefined;
185
+ }
186
+
187
+ async addToCart(cartId: string, lines: CartLineInput[]): Promise<Cart> {
188
+ const cart = getMockCartState(cartId);
189
+ if (!cart) {
190
+ throw new ResourceNotFoundError('cart', cartId);
191
+ }
192
+
193
+ for (const line of lines) {
194
+ if (!this.hasVariant(line.merchandiseId, cart.regionId)) {
195
+ throw new ResourceNotFoundError('variant', line.merchandiseId);
196
+ }
197
+ }
198
+
199
+ const nextLines = [...cart.lines];
200
+ for (const line of lines) {
201
+ const existing = nextLines.find((item) => item.merchandiseId === line.merchandiseId);
202
+ if (existing) {
203
+ existing.quantity += line.quantity;
204
+ } else {
205
+ nextLines.push({
206
+ id: `mock-line-${line.merchandiseId}`,
207
+ merchandiseId: line.merchandiseId,
208
+ quantity: line.quantity,
209
+ });
210
+ }
211
+ }
212
+
213
+ const saved = saveMockCartState({ ...cart, lines: nextLines });
214
+ return buildMockCart(saved, this.currencyCode, this.siteUrl);
215
+ }
216
+
217
+ async removeFromCart(cartId: string, lineIds: string[]): Promise<Cart> {
218
+ const cart = getMockCartState(cartId);
219
+ if (!cart) {
220
+ throw new ResourceNotFoundError('cart', cartId);
221
+ }
222
+
223
+ const saved = saveMockCartState({
224
+ ...cart,
225
+ lines: cart.lines.filter((line) => !lineIds.includes(line.id)),
226
+ });
227
+
228
+ return buildMockCart(saved, this.currencyCode, this.siteUrl);
229
+ }
230
+
231
+ async updateCart(cartId: string, lines: CartLineUpdate[]): Promise<Cart> {
232
+ const cart = getMockCartState(cartId);
233
+ if (!cart) {
234
+ throw new ResourceNotFoundError('cart', cartId);
235
+ }
236
+
237
+ for (const line of lines) {
238
+ if (!this.hasVariant(line.merchandiseId, cart.regionId)) {
239
+ throw new ResourceNotFoundError('variant', line.merchandiseId);
240
+ }
241
+ }
242
+
243
+ const updates = new Map(lines.map((line) => [line.id, line]));
244
+ const nextLines = cart.lines
245
+ .map((line) => {
246
+ const update = updates.get(line.id);
247
+ if (!update) {
248
+ return line;
249
+ }
250
+
251
+ return {
252
+ ...line,
253
+ merchandiseId: update.merchandiseId,
254
+ quantity: update.quantity,
255
+ };
256
+ })
257
+ .filter((line) => line.quantity > 0);
258
+
259
+ const saved = saveMockCartState({ ...cart, lines: nextLines });
260
+ return buildMockCart(saved, this.currencyCode, this.siteUrl);
261
+ }
262
+
263
+ async getRegions(): Promise<Array<{ id: string; name: string; currency_code: string; countries?: Array<{ id: string; iso_2: string; name: string; display_name?: string }> }>> {
264
+ return getMockRegions();
265
+ }
266
+
267
+ async completeCheckout(cartId: string): Promise<Order> {
268
+ const cart = getMockCartState(cartId);
269
+ if (!cart) {
270
+ throw new ResourceNotFoundError('cart', cartId);
271
+ }
272
+
273
+ const order = completeMockCheckout(cart, this.currencyCode, this.siteUrl);
274
+ return order;
275
+ }
276
+
277
+ async getShippingMethods(cartId: string): Promise<ShippingMethod[]> {
278
+ const cart = getMockCartState(cartId);
279
+ return getMockShippingMethods(cart?.regionId);
280
+ }
281
+
282
+ async getRegionCountries(regionId: string) {
283
+ return getMockRegionCountries(regionId);
284
+ }
285
+
286
+ async addShippingMethod(cartId: string, methodId: string): Promise<Cart> {
287
+ const cart = getMockCartState(cartId);
288
+ if (!cart) {
289
+ throw new ResourceNotFoundError('cart', cartId);
290
+ }
291
+
292
+ const allowed = getMockShippingMethods(cart.regionId).some((method) => method.id === methodId);
293
+ if (!allowed) {
294
+ throw new ProviderAPIError(`Unknown mock shipping method: ${methodId}`, 'mock', 400);
295
+ }
296
+
297
+ const saved = saveMockCartState({ ...cart, shippingMethodId: methodId });
298
+ return buildMockCart(saved, this.currencyCode, this.siteUrl);
299
+ }
300
+
301
+ async initiatePaymentSession(cartId: string, providerId: string): Promise<void> {
302
+ const cart = getMockCartState(cartId);
303
+ if (!cart) {
304
+ throw new ResourceNotFoundError('cart', cartId);
305
+ }
306
+
307
+ const allowed = getMockPaymentMethods(this.currencyCode).some((method) => method.id === providerId);
308
+ if (!allowed) {
309
+ throw new ProviderAPIError(`Unknown mock payment method: ${providerId}`, 'mock', 400);
310
+ }
311
+
312
+ saveMockCartState({ ...cart, paymentProviderId: providerId });
313
+ }
314
+
315
+ async applyDiscountCode(cartId: string, code: string): Promise<Cart> {
316
+ const cart = getMockCartState(cartId);
317
+ if (!cart) {
318
+ throw new ResourceNotFoundError('cart', cartId);
319
+ }
320
+
321
+ const normalized = code.trim();
322
+ if (!normalized) {
323
+ throw new ProviderAPIError('Discount code is required.', 'mock', 400);
324
+ }
325
+
326
+ const saved = saveMockCartState({ ...cart, discountCode: normalized });
327
+ return buildMockCart(saved, this.currencyCode, this.siteUrl);
328
+ }
329
+
330
+ async removeDiscountCode(cartId: string, _code: string): Promise<Cart> {
331
+ const cart = getMockCartState(cartId);
332
+ if (!cart) {
333
+ throw new ResourceNotFoundError('cart', cartId);
334
+ }
335
+
336
+ const saved = saveMockCartState({ ...cart, discountCode: undefined });
337
+ return buildMockCart(saved, this.currencyCode, this.siteUrl);
338
+ }
339
+
340
+ async getOrders(_options: GetOrdersOptions): Promise<Order[]> {
341
+ return getMockAdminOrders().orders;
342
+ }
343
+
344
+ async getPaymentMethods(options: PaymentMethodsOptions): Promise<PaymentMethod[]> {
345
+ return getMockPaymentMethods(options.currencyCode ?? this.currencyCode);
346
+ }
347
+
348
+ async getStockLocations(): Promise<StockLocation[]> {
349
+ return getMockStockLocations();
350
+ }
351
+
352
+ async checkInventoryLevels(variantId: string): Promise<InventoryLevel[]> {
353
+ return getMockInventoryLevels(variantId);
354
+ }
355
+
356
+ async validateCartStock(cartId: string): Promise<StockValidation> {
357
+ const cart = getMockCartState(cartId);
358
+ if (!cart) {
359
+ throw new ResourceNotFoundError('cart', cartId);
360
+ }
361
+
362
+ return validateMockCartStock(cart);
363
+ }
364
+
365
+ async getFulfillmentOptions(cartId: string, _salesChannelId: string): Promise<FulfillmentOption[]> {
366
+ const cart = getMockCartState(cartId);
367
+ return getMockFulfillmentOptions(cart?.regionId);
368
+ }
369
+
370
+ async cancelOrder(orderId: string): Promise<Order> {
371
+ const order = getMockOrder(orderId);
372
+ if (!order) {
373
+ throw new ResourceNotFoundError('order', orderId);
374
+ }
375
+
376
+ return saveMockOrder({
377
+ ...order,
378
+ status: 'cancelled',
379
+ updatedAt: new Date().toISOString(),
380
+ cancelledAt: new Date().toISOString(),
381
+ });
382
+ }
383
+
384
+ async createFulfillment(_orderId: string, _items?: Array<{ id: string; quantity: number }>): Promise<void> {
385
+ return;
386
+ }
387
+
388
+ async createRefund(orderId: string, _amount?: number, _reason?: string): Promise<void> {
389
+ const order = getMockOrder(orderId);
390
+ if (!order) {
391
+ throw new ResourceNotFoundError('order', orderId);
392
+ }
393
+
394
+ saveMockOrder({
395
+ ...order,
396
+ status: 'refunded',
397
+ paymentStatus: 'refunded',
398
+ updatedAt: new Date().toISOString(),
399
+ });
400
+ }
401
+
402
+ async getAdminOrders(): Promise<{ orders: Order[]; count: number }> {
403
+ return getMockAdminOrders();
404
+ }
405
+
406
+ async getAdminRegions(): Promise<Region[]> {
407
+ return getMockAdminRegions();
408
+ }
409
+
410
+ async getStorefrontContext(): Promise<StorefrontContext> {
411
+ return {
412
+ id: 'mock-storefront',
413
+ name: 'Mock Storefront',
414
+ platformType: 'mock',
415
+ requiresProductScoping: false,
416
+ currencyCode: this.currencyCode,
417
+ locale: 'en-US',
418
+ metadata: {
419
+ mode: 'design',
420
+ },
421
+ };
422
+ }
423
+
424
+ isStorefrontRequired(): boolean {
425
+ return false;
426
+ }
427
+ }