@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,344 +0,0 @@
1
- /**
2
- * @thorprovider/adapters — Spree Commerce Provider
3
- * Implements CommerceProvider for Spree Commerce Storefront API v2
4
- *
5
- * Spree uses JSON:API format. Supports guest token + bearer token auth.
6
- * API base: /api/v2/storefront/
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 { SPREE_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
- * Spree provider configuration
42
- */
43
- export interface SpreeProviderConfig extends ProviderConfig {
44
- /** Spree store base URL (e.g., 'https://myspreestore.com') */
45
- baseUrl: string;
46
- /** Bearer token for authenticated operations (customer or OAuth) */
47
- bearerToken?: string;
48
- /** Default currency code (default: 'USD') */
49
- currencyCode?: string;
50
- }
51
-
52
- export class SpreeProvider implements CommerceProvider {
53
- readonly name = 'spree';
54
- readonly version = '1.0.0';
55
- readonly capabilities: BackendCapabilities = SPREE_CAPABILITIES;
56
-
57
- private logger: ReturnType<typeof createLogger>;
58
- private baseUrl: string;
59
- private apiBase: string;
60
- private currencyCode: string;
61
- private bearerToken?: string;
62
- private orderToken?: string; // guest cart token
63
-
64
- constructor(config: SpreeProviderConfig) {
65
- this.logger = createLogger('SpreeProvider', config.debug);
66
- this.baseUrl = config.baseUrl.replace(/\/$/, '');
67
- this.apiBase = `${this.baseUrl}/api/v2/storefront`;
68
- this.currencyCode = config.currencyCode ?? 'USD';
69
- this.bearerToken = config.bearerToken;
70
-
71
- this.logger.info('Initializing Spree Provider', { baseUrl: this.baseUrl });
72
- }
73
-
74
- private async apiFetch<T = any>(path: string, init?: RequestInit): Promise<{ data: T; included?: any[] }> {
75
- const headers: Record<string, string> = {
76
- 'Content-Type': 'application/vnd.api+json',
77
- 'Accept': 'application/vnd.api+json',
78
- ...((init?.headers as Record<string, string>) ?? {}),
79
- };
80
-
81
- if (this.bearerToken) {
82
- headers['Authorization'] = `Bearer ${this.bearerToken}`;
83
- }
84
- if (this.orderToken) {
85
- headers['X-Spree-Order-Token'] = this.orderToken;
86
- }
87
-
88
- const response = await fetch(`${this.apiBase}${path}`, {
89
- ...init,
90
- headers,
91
- });
92
-
93
- if (!response.ok) {
94
- throw new ProviderAPIError(
95
- `Spree API error: ${response.status}`,
96
- 'SPREE_API_ERROR',
97
- response.status,
98
- );
99
- }
100
-
101
- return response.json() as Promise<{ data: T; included?: any[] }>;
102
- }
103
-
104
- // ============================================
105
- // Product Operations
106
- // ============================================
107
-
108
- async getProduct(handle: string): Promise<Product | undefined> {
109
- try {
110
- const { data, included } = await this.apiFetch<any>(
111
- `/products/${encodeURIComponent(handle)}?include=images,variants,option_types`,
112
- );
113
- return transformProduct(data, included ?? [], this.currencyCode);
114
- } catch {
115
- return undefined;
116
- }
117
- }
118
-
119
- async getProducts(options?: GetProductsOptions): Promise<Product[]> {
120
- let path = `/products?include=images,variants&per_page=${options?.first ?? 20}`;
121
- if (options?.query) path += `&filter[name]=${encodeURIComponent(options.query)}`;
122
- if (options?.sortKey) {
123
- const sortMap: Record<string, string> = {
124
- 'price-asc': 'price',
125
- 'price-desc': '-price',
126
- 'title-asc': 'name',
127
- 'title-desc': '-name',
128
- 'created-desc': '-created_at',
129
- };
130
- if (sortMap[options.sortKey]) path += `&sort=${sortMap[options.sortKey]}`;
131
- }
132
-
133
- const { data, included } = await this.apiFetch<any[]>(path);
134
- return (Array.isArray(data) ? data : []).map((p) => transformProduct(p, included ?? [], this.currencyCode));
135
- }
136
-
137
- async getProductRecommendations(_productId: string): Promise<Product[]> {
138
- return [];
139
- }
140
-
141
- // ============================================
142
- // Collection/Taxon Operations
143
- // ============================================
144
-
145
- async getCollection(handle: string): Promise<Collection | undefined> {
146
- try {
147
- const { data } = await this.apiFetch<any>(`/taxons/${encodeURIComponent(handle)}`);
148
- return transformCollection(data);
149
- } catch {
150
- return undefined;
151
- }
152
- }
153
-
154
- async getCollections(options?: GetCollectionsOptions): Promise<Collection[]> {
155
- const { data } = await this.apiFetch<any[]>(`/taxons?per_page=${options?.limit ?? 50}`);
156
- return (Array.isArray(data) ? data : []).map(transformCollection);
157
- }
158
-
159
- async getCollectionProducts(handle: string, options?: CollectionProductsOptions): Promise<Product[]> {
160
- const { data, included } = await this.apiFetch<any[]>(
161
- `/products?include=images,variants&filter[taxons]=${encodeURIComponent(handle)}&per_page=${options?.first ?? 20}`,
162
- );
163
- return (Array.isArray(data) ? data : []).map((p) => transformProduct(p, included ?? [], this.currencyCode));
164
- }
165
-
166
- async getCategory(handle: string): Promise<ProductCategory | undefined> {
167
- return this.getCollection(handle) as any;
168
- }
169
-
170
- async getCategories(options?: GetCategoriesOptions): Promise<ProductCategory[]> {
171
- return this.getCollections(options) as any;
172
- }
173
-
174
- async getCategoryProducts(handle: string, options?: CollectionProductsOptions): Promise<Product[]> {
175
- return this.getCollectionProducts(handle, options);
176
- }
177
-
178
- // ============================================
179
- // Cart Operations
180
- // ============================================
181
-
182
- async createCart(): Promise<Cart> {
183
- const { data } = await this.apiFetch<any>('/cart', {
184
- method: 'POST',
185
- body: JSON.stringify({}),
186
- });
187
-
188
- // Store order token for guest operations
189
- if (data.attributes?.token) {
190
- this.orderToken = data.attributes.token;
191
- }
192
-
193
- return transformCart(data, this.currencyCode);
194
- }
195
-
196
- async getCart(_cartId: string): Promise<Cart | undefined> {
197
- try {
198
- const { data } = await this.apiFetch<any>('/cart?include=line_items');
199
- return transformCart(data, this.currencyCode);
200
- } catch {
201
- return undefined;
202
- }
203
- }
204
-
205
- async addToCart(_cartId: string, lines: CartLineInput[]): Promise<Cart> {
206
- for (const line of lines) {
207
- await this.apiFetch('/cart/add_item', {
208
- method: 'POST',
209
- body: JSON.stringify({ variant_id: line.merchandiseId, quantity: line.quantity }),
210
- });
211
- }
212
- const { data } = await this.apiFetch<any>('/cart?include=line_items');
213
- return transformCart(data, this.currencyCode);
214
- }
215
-
216
- async removeFromCart(_cartId: string, lineIds: string[]): Promise<Cart> {
217
- for (const id of lineIds) {
218
- await this.apiFetch(`/cart/remove_line_item/${id}`, { method: 'DELETE' });
219
- }
220
- const { data } = await this.apiFetch<any>('/cart?include=line_items');
221
- return transformCart(data, this.currencyCode);
222
- }
223
-
224
- async updateCart(_cartId: string, lines: CartLineUpdate[]): Promise<Cart> {
225
- for (const line of lines) {
226
- await this.apiFetch(`/cart/set_quantity`, {
227
- method: 'PATCH',
228
- body: JSON.stringify({ line_item_id: line.id, quantity: line.quantity }),
229
- });
230
- }
231
- const { data } = await this.apiFetch<any>('/cart?include=line_items');
232
- return transformCart(data, this.currencyCode);
233
- }
234
-
235
- // ============================================
236
- // Checkout Operations
237
- // ============================================
238
-
239
- async completeCheckout(_cartId: string): Promise<Order> {
240
- const { data } = await this.apiFetch<any>('/checkout/complete', { method: 'PATCH' });
241
- return transformOrder(data, this.currencyCode);
242
- }
243
-
244
- async getShippingMethods(_cartId: string): Promise<ShippingMethod[]> {
245
- try {
246
- const { data } = await this.apiFetch<any[]>('/checkout/shipping_rates');
247
- return (Array.isArray(data) ? data : []).map((rate: any) => ({
248
- id: String(rate.id),
249
- name: rate.attributes?.name ?? rate.name ?? '',
250
- price: { amount: String(parseFloat(rate.attributes?.final_price ?? rate.attributes?.cost ?? '0').toFixed(2)), currencyCode: this.currencyCode },
251
- }));
252
- } catch {
253
- return [];
254
- }
255
- }
256
-
257
- async getRegionCountries(): Promise<import('@thorprovider/types').Country[]> {
258
- return [];
259
- }
260
-
261
- async addShippingMethod(_cartId: string, methodId: string): Promise<Cart> {
262
- await this.apiFetch('/checkout', {
263
- method: 'PATCH',
264
- body: JSON.stringify({ order: { shipments_attributes: [{ selected_shipping_rate_id: methodId }] } }),
265
- });
266
- const { data } = await this.apiFetch<any>('/cart?include=line_items');
267
- return transformCart(data, this.currencyCode);
268
- }
269
-
270
- async initiatePaymentSession(_cartId: string, _providerId: string): Promise<void> {
271
- // Spree handles payments via checkout advance
272
- }
273
-
274
- async applyDiscountCode(_cartId: string, code: string): Promise<Cart> {
275
- await this.apiFetch('/cart/apply_coupon_code', {
276
- method: 'PATCH',
277
- body: JSON.stringify({ coupon_code: code }),
278
- });
279
- const { data } = await this.apiFetch<any>('/cart?include=line_items');
280
- return transformCart(data, this.currencyCode);
281
- }
282
-
283
- async removeDiscountCode(_cartId: string, code: string): Promise<Cart> {
284
- await this.apiFetch(`/cart/remove_coupon_code/${encodeURIComponent(code)}`, { method: 'DELETE' });
285
- const { data } = await this.apiFetch<any>('/cart?include=line_items');
286
- return transformCart(data, this.currencyCode);
287
- }
288
-
289
- // ============================================
290
- // Customer, Order, Payment, Inventory, Admin
291
- // ============================================
292
-
293
- async getOrders(_options: GetOrdersOptions): Promise<Order[]> {
294
- try {
295
- const { data } = await this.apiFetch<any[]>('/account/orders');
296
- return (Array.isArray(data) ? data : []).map((o) => transformOrder(o, this.currencyCode));
297
- } catch {
298
- return [];
299
- }
300
- }
301
-
302
- async getPaymentMethods(_options: PaymentMethodsOptions): Promise<PaymentMethod[]> {
303
- try {
304
- const { data } = await this.apiFetch<any[]>('/checkout/payment_methods');
305
- return (Array.isArray(data) ? data : []).map((pm: any) => ({
306
- id: String(pm.id),
307
- name: pm.attributes?.name ?? '',
308
- type: 'other' as const,
309
- isEnabled: true,
310
- requiresSetup: false,
311
- }));
312
- } catch {
313
- return [];
314
- }
315
- }
316
-
317
- async getStockLocations(): Promise<StockLocation[]> { return []; }
318
- async checkInventoryLevels(): Promise<InventoryLevel[]> { return []; }
319
- async validateCartStock(): Promise<StockValidation> { return { allAvailable: true, unavailableItems: [] }; }
320
- async getFulfillmentOptions(): Promise<FulfillmentOption[]> { return []; }
321
-
322
- async cancelOrder(_orderId: string): Promise<Order> {
323
- throw new ProviderAPIError('Order cancellation requires Spree Admin API.', 'SPREE_ADMIN_REQUIRED');
324
- }
325
- async createFulfillment(_orderId: string): Promise<void> {
326
- throw new ProviderAPIError('Fulfillment requires Spree Admin API.', 'SPREE_ADMIN_REQUIRED');
327
- }
328
- async createRefund(_orderId: string): Promise<void> {
329
- throw new ProviderAPIError('Refunds require Spree Admin API.', 'SPREE_ADMIN_REQUIRED');
330
- }
331
- async getAdminOrders(): Promise<{ orders: Order[]; count: number }> { return { orders: [], count: 0 }; }
332
-
333
- async getStorefrontContext(): Promise<StorefrontContext> {
334
- return {
335
- id: this.baseUrl,
336
- name: `Spree ${this.baseUrl}`,
337
- platformType: 'spree',
338
- requiresProductScoping: true,
339
- currencyCode: this.currencyCode,
340
- };
341
- }
342
-
343
- isStorefrontRequired(): boolean { return true; }
344
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Spree Commerce Provider — barrel exports
3
- */
4
- export { SpreeProvider } from './index';
5
- export type { SpreeProviderConfig } from './index';
@@ -1,254 +0,0 @@
1
- /**
2
- * Spree Commerce Transform Functions
3
- * Converts Spree JSON:API responses to @thorprovider/types
4
- *
5
- * Spree uses JSON:API format with `data` arrays containing `id`, `type`, `attributes`, `relationships`.
6
- * Prices are stored as strings (e.g., "19.99").
7
- */
8
-
9
- import type { Product, ProductVariant, ProductOption, Image, Collection, Cart, CartItem, Order, OrderItem, Customer, Address, Money } from '@thorprovider/types';
10
-
11
- // ============================================
12
- // Money / Price Helpers
13
- // ============================================
14
-
15
- function toMoney(amount: string | number, currencyCode: string): Money {
16
- const num = typeof amount === 'string' ? parseFloat(amount) || 0 : amount;
17
- return { amount: num.toFixed(2), currencyCode };
18
- }
19
-
20
- // ============================================
21
- // Product Transforms
22
- // ============================================
23
-
24
- export function transformProduct(data: any, included: any[] = [], currencyCode: string = 'USD'): Product {
25
- const attrs = data.attributes ?? data;
26
- const id = String(data.id ?? '');
27
-
28
- // Resolve images from relationships or included
29
- const images: Image[] = [];
30
- const imageRels = data.relationships?.images?.data ?? [];
31
- for (const imgRef of imageRels) {
32
- const imgIncluded = included.find((i: any) => i.type === 'image' && String(i.id) === String(imgRef.id));
33
- if (imgIncluded) {
34
- images.push({
35
- url: imgIncluded.attributes?.original_url ?? imgIncluded.attributes?.styles?.[0]?.url ?? '',
36
- altText: imgIncluded.attributes?.alt ?? attrs.name ?? '',
37
- width: undefined,
38
- height: undefined,
39
- });
40
- }
41
- }
42
-
43
- // If no resolved images, try attributes.images directly
44
- if (images.length === 0 && Array.isArray(attrs.images)) {
45
- for (const img of attrs.images) {
46
- images.push({
47
- url: img.original_url ?? img.url ?? '',
48
- altText: img.alt ?? attrs.name ?? '',
49
- width: undefined,
50
- height: undefined,
51
- });
52
- }
53
- }
54
-
55
- // Resolve variants from relationships or included
56
- const variants: ProductVariant[] = [];
57
- const variantRels = data.relationships?.variants?.data ?? [];
58
- for (const varRef of variantRels) {
59
- const varIncluded = included.find((i: any) => i.type === 'variant' && String(i.id) === String(varRef.id));
60
- if (varIncluded) {
61
- const vAttrs = varIncluded.attributes ?? {};
62
- variants.push({
63
- id: String(varIncluded.id),
64
- title: vAttrs.name ?? vAttrs.sku ?? `Variant ${varIncluded.id}`,
65
- availableForSale: vAttrs.in_stock !== false && vAttrs.purchasable !== false,
66
- price: toMoney(vAttrs.price ?? vAttrs.display_price ?? '0', currencyCode),
67
- compareAtPrice: vAttrs.compare_at_price ? toMoney(vAttrs.compare_at_price, currencyCode) : undefined,
68
- selectedOptions: (vAttrs.option_values ?? []).map((ov: any) => ({
69
- name: ov.option_type_name ?? 'Option',
70
- value: ov.name ?? ov.presentation ?? '',
71
- })),
72
- });
73
- }
74
- }
75
-
76
- // Fallback to default variant if no relationships
77
- if (variants.length === 0) {
78
- variants.push({
79
- id: attrs.default_variant_id ? String(attrs.default_variant_id) : id,
80
- title: 'Default',
81
- availableForSale: attrs.in_stock !== false && attrs.purchasable !== false,
82
- price: toMoney(attrs.price ?? attrs.display_price ?? '0', currencyCode),
83
- compareAtPrice: attrs.compare_at_price ? toMoney(attrs.compare_at_price, currencyCode) : undefined,
84
- selectedOptions: [],
85
- });
86
- }
87
-
88
- // Options from option_types
89
- const options: ProductOption[] = (attrs.option_types ?? []).map((ot: any) => ({
90
- id: String(ot.id ?? ot.name ?? ''),
91
- name: ot.presentation ?? ot.name ?? '',
92
- values: (ot.option_values ?? []).map((ov: any) => ov.presentation ?? ov.name ?? ''),
93
- }));
94
-
95
- return {
96
- id,
97
- handle: attrs.slug ?? id,
98
- title: attrs.name ?? '',
99
- description: attrs.description ?? '',
100
- descriptionHtml: attrs.description ?? '',
101
- availableForSale: attrs.in_stock !== false && attrs.purchasable !== false,
102
- priceRange: {
103
- minVariantPrice: variants.reduce(
104
- (min, v) => (v.price.amount < min.amount ? v.price : min),
105
- variants[0]?.price ?? toMoney('0', currencyCode),
106
- ),
107
- maxVariantPrice: variants.reduce(
108
- (max, v) => (v.price.amount > max.amount ? v.price : max),
109
- variants[0]?.price ?? toMoney('0', currencyCode),
110
- ),
111
- },
112
- variants,
113
- options,
114
- images,
115
- featuredImage: images[0] ?? { url: '', altText: attrs.name ?? '' },
116
- tags: [],
117
- seo: {
118
- title: attrs.meta_title ?? attrs.name ?? '',
119
- description: attrs.meta_description ?? '',
120
- },
121
- updatedAt: attrs.updated_at ?? new Date().toISOString(),
122
- createdAt: attrs.created_at ?? new Date().toISOString(),
123
- };
124
- }
125
-
126
- // ============================================
127
- // Collection Transforms (Spree Taxonomies/Taxons)
128
- // ============================================
129
-
130
- export function transformCollection(data: any): Collection {
131
- const attrs = data.attributes ?? data;
132
- return {
133
- id: String(data.id ?? ''),
134
- handle: attrs.permalink ?? attrs.slug ?? String(data.id ?? ''),
135
- title: attrs.name ?? '',
136
- description: attrs.description ?? '',
137
- seo: {
138
- title: attrs.meta_title ?? attrs.name ?? '',
139
- description: attrs.meta_description ?? '',
140
- },
141
- updatedAt: attrs.updated_at ?? new Date().toISOString(),
142
- path: `/collections/${attrs.permalink ?? attrs.slug ?? data.id}`,
143
- };
144
- }
145
-
146
- // ============================================
147
- // Cart Transforms
148
- // ============================================
149
-
150
- export function transformCart(data: any, currencyCode: string = 'USD'): Cart {
151
- const attrs = data.attributes ?? data;
152
- const items: CartItem[] = (attrs.line_items ?? data.included?.filter((i: any) => i.type === 'line_item') ?? []).map(
153
- (item: any): CartItem => {
154
- const itemAttrs = item.attributes ?? item;
155
- return {
156
- id: String(item.id ?? itemAttrs.id ?? ''),
157
- quantity: itemAttrs.quantity ?? 1,
158
- cost: {
159
- totalAmount: toMoney(itemAttrs.total ?? itemAttrs.display_total ?? '0', currencyCode),
160
- },
161
- merchandise: {
162
- id: String(itemAttrs.variant_id ?? item.id ?? ''),
163
- title: itemAttrs.name ?? '',
164
- selectedOptions: [],
165
- product: {
166
- id: String(itemAttrs.product_id ?? ''),
167
- handle: itemAttrs.slug ?? '',
168
- title: itemAttrs.name ?? '',
169
- featuredImage: { url: itemAttrs.image_url ?? '', altText: itemAttrs.name ?? '' },
170
- },
171
- },
172
- };
173
- },
174
- );
175
-
176
- return {
177
- id: String(data.id ?? attrs.token ?? ''),
178
- checkoutUrl: '',
179
- totalQuantity: items.reduce((sum, i) => sum + i.quantity, 0),
180
- cost: {
181
- subtotalAmount: toMoney(attrs.item_total ?? attrs.display_item_total ?? '0', currencyCode),
182
- totalAmount: toMoney(attrs.total ?? attrs.display_total ?? '0', currencyCode),
183
- totalTaxAmount: toMoney(attrs.tax_total ?? attrs.display_tax_total ?? '0', currencyCode),
184
- },
185
- lines: items,
186
- };
187
- }
188
-
189
- // ============================================
190
- // Customer Transforms
191
- // ============================================
192
-
193
- export function transformCustomer(data: any): Customer {
194
- const attrs = data.attributes ?? data;
195
- return {
196
- id: String(data.id ?? ''),
197
- email: attrs.email ?? '',
198
- firstName: attrs.first_name ?? '',
199
- lastName: attrs.last_name ?? '',
200
- phone: attrs.phone ?? '',
201
- hasAccount: true,
202
- };
203
- }
204
-
205
- function transformAddress(addr: any): Address {
206
- return {
207
- id: String(addr.id ?? ''),
208
- firstName: addr.first_name ?? addr.firstname ?? '',
209
- lastName: addr.last_name ?? addr.lastname ?? '',
210
- company: addr.company ?? '',
211
- address1: addr.address1 ?? '',
212
- address2: addr.address2 ?? '',
213
- city: addr.city ?? '',
214
- province: addr.state_name ?? addr.state ?? '',
215
- postalCode: addr.zipcode ?? addr.zip ?? '',
216
- countryCode: addr.country_iso ?? addr.country_code ?? '',
217
- phone: addr.phone ?? '',
218
- };
219
- }
220
-
221
- // ============================================
222
- // Order Transforms
223
- // ============================================
224
-
225
- export function transformOrder(data: any, currencyCode: string = 'USD'): Order {
226
- const attrs = data.attributes ?? data;
227
- const items: OrderItem[] = (attrs.line_items ?? []).map((item: any) => ({
228
- id: String(item.id ?? ''),
229
- title: item.name ?? '',
230
- quantity: item.quantity ?? 1,
231
- price: toMoney(item.price ?? '0', currencyCode),
232
- total: toMoney(item.total ?? '0', currencyCode),
233
- productId: String(item.product_id ?? ''),
234
- variantId: String(item.variant_id ?? ''),
235
- }));
236
-
237
- return {
238
- id: String(data.id ?? attrs.number ?? ''),
239
- orderNumber: attrs.number ?? String(data.id ?? ''),
240
- email: attrs.email ?? '',
241
- status: (attrs.state === 'complete' ? 'completed' : attrs.state === 'canceled' ? 'cancelled' : 'pending') as any,
242
- paymentStatus: (attrs.payment_state === 'paid' ? 'paid' : attrs.payment_state === 'balance_due' ? 'pending' : 'pending') as any,
243
- fulfillmentStatus: (attrs.shipment_state === 'shipped' ? 'fulfilled' : attrs.shipment_state === 'partial' ? 'partially_fulfilled' : 'unfulfilled') as any,
244
- items,
245
- total: toMoney(attrs.total ?? '0', currencyCode),
246
- subtotal: toMoney(attrs.item_total ?? '0', currencyCode),
247
- tax: toMoney(attrs.tax_total ?? '0', currencyCode),
248
- shipping: toMoney(attrs.ship_total ?? '0', currencyCode),
249
- shippingAddress: attrs.ship_address ? transformAddress(attrs.ship_address) : undefined,
250
- billingAddress: attrs.bill_address ? transformAddress(attrs.bill_address) : undefined,
251
- createdAt: attrs.created_at ?? new Date().toISOString(),
252
- updatedAt: attrs.updated_at ?? new Date().toISOString(),
253
- };
254
- }