@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.
- package/dist/index.d.mts +66 -387
- package/dist/index.d.ts +66 -387
- package/dist/index.js +70 -2901
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +66 -2891
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/backend/factory.ts +1 -11
- package/src/factory/index.ts +74 -52
- package/src/index.ts +5 -15
- package/src/providers/index.ts +1 -16
- package/src/types/capabilities.types.ts +0 -250
- package/src/providers/bigcommerce/bigcommerce.ts +0 -6
- package/src/providers/bigcommerce/index.ts +0 -315
- package/src/providers/bigcommerce/transforms.ts +0 -227
- package/src/providers/magento/index.ts +0 -387
- package/src/providers/magento/magento.ts +0 -5
- package/src/providers/magento/queries.ts +0 -267
- package/src/providers/magento/transforms.ts +0 -249
- package/src/providers/shopify/auth.ts +0 -408
- package/src/providers/shopify/index.ts +0 -444
- package/src/providers/shopify/mutations.ts +0 -247
- package/src/providers/shopify/queries.ts +0 -324
- package/src/providers/shopify/shopify.ts +0 -7
- package/src/providers/shopify/transforms.ts +0 -275
- package/src/providers/spree/index.ts +0 -344
- package/src/providers/spree/spree.ts +0 -5
- package/src/providers/spree/transforms.ts +0 -254
- package/src/providers/woocommerce/index.ts +0 -310
- package/src/providers/woocommerce/transforms.ts +0 -254
- package/src/providers/woocommerce/woocommerce.ts +0 -5
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adobe Commerce (Magento) Transform Functions
|
|
3
|
-
* Converts Magento GraphQL / REST API responses to @thorprovider/types
|
|
4
|
-
*
|
|
5
|
-
* Magento returns price as { regularPrice: { amount: { value, currency } } }
|
|
6
|
-
* Images have url + label directly.
|
|
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(value: number | undefined, currencyCode: string): Money {
|
|
16
|
-
return { amount: (value ?? 0).toFixed(2), currencyCode };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function extractPrice(priceRange: any, currencyCode: string): { min: Money; max: Money } {
|
|
20
|
-
const minPrice = priceRange?.minimum_price?.regular_price?.value ?? priceRange?.minimum_price?.final_price?.value ?? 0;
|
|
21
|
-
const maxPrice = priceRange?.maximum_price?.regular_price?.value ?? priceRange?.maximum_price?.final_price?.value ?? minPrice;
|
|
22
|
-
const currency = priceRange?.minimum_price?.regular_price?.currency ?? currencyCode;
|
|
23
|
-
return {
|
|
24
|
-
min: toMoney(minPrice, currency),
|
|
25
|
-
max: toMoney(maxPrice, currency),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ============================================
|
|
30
|
-
// Product Transforms
|
|
31
|
-
// ============================================
|
|
32
|
-
|
|
33
|
-
export function transformProduct(data: any, currencyCode: string = 'USD'): Product {
|
|
34
|
-
const id = String(data.id ?? data.uid ?? '');
|
|
35
|
-
const { min, max } = extractPrice(data.price_range, currencyCode);
|
|
36
|
-
|
|
37
|
-
// Images
|
|
38
|
-
const images: Image[] = (data.media_gallery ?? data.media_gallery_entries ?? []).map((img: any) => ({
|
|
39
|
-
url: img.url ?? (img.file ? `${img.file}` : ''),
|
|
40
|
-
altText: img.label ?? data.name ?? '',
|
|
41
|
-
width: undefined,
|
|
42
|
-
height: undefined,
|
|
43
|
-
}));
|
|
44
|
-
|
|
45
|
-
// Small image fallback
|
|
46
|
-
if (images.length === 0 && data.small_image?.url) {
|
|
47
|
-
images.push({ url: data.small_image.url, altText: data.small_image.label ?? data.name ?? '' });
|
|
48
|
-
}
|
|
49
|
-
if (images.length === 0 && data.image?.url) {
|
|
50
|
-
images.push({ url: data.image.url, altText: data.image.label ?? data.name ?? '' });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Variants from configurable_options / variants
|
|
54
|
-
const variants: ProductVariant[] = [];
|
|
55
|
-
if (data.variants && Array.isArray(data.variants)) {
|
|
56
|
-
for (const v of data.variants) {
|
|
57
|
-
const product = v.product ?? {};
|
|
58
|
-
const vPrice = product.price_range?.minimum_price?.final_price ?? {};
|
|
59
|
-
variants.push({
|
|
60
|
-
id: String(product.id ?? product.uid ?? product.sku ?? ''),
|
|
61
|
-
title: product.name ?? product.sku ?? '',
|
|
62
|
-
availableForSale: product.stock_status === 'IN_STOCK',
|
|
63
|
-
price: toMoney(vPrice.value ?? min.amount, vPrice.currency ?? currencyCode),
|
|
64
|
-
compareAtPrice: undefined,
|
|
65
|
-
selectedOptions: (v.attributes ?? []).map((attr: any) => ({
|
|
66
|
-
name: attr.label ?? '',
|
|
67
|
-
value: attr.value_index != null ? String(attr.value_index) : '',
|
|
68
|
-
})),
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Default variant if simple product
|
|
74
|
-
if (variants.length === 0) {
|
|
75
|
-
variants.push({
|
|
76
|
-
id: data.sku ?? id,
|
|
77
|
-
title: data.name ?? '',
|
|
78
|
-
availableForSale: data.stock_status === 'IN_STOCK',
|
|
79
|
-
price: min,
|
|
80
|
-
compareAtPrice: undefined,
|
|
81
|
-
selectedOptions: [],
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Options
|
|
86
|
-
const options: ProductOption[] = (data.configurable_options ?? []).map((opt: any) => ({
|
|
87
|
-
id: String(opt.attribute_id ?? opt.attribute_code ?? ''),
|
|
88
|
-
name: opt.label ?? opt.attribute_code ?? '',
|
|
89
|
-
values: (opt.values ?? []).map((v: any) => v.label ?? v.store_label ?? String(v.value_index ?? '')),
|
|
90
|
-
}));
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
id,
|
|
94
|
-
handle: data.url_key ?? data.sku ?? id,
|
|
95
|
-
title: data.name ?? '',
|
|
96
|
-
description: data.short_description?.html ?? data.description?.html ?? '',
|
|
97
|
-
descriptionHtml: data.description?.html ?? '',
|
|
98
|
-
availableForSale: data.stock_status === 'IN_STOCK',
|
|
99
|
-
priceRange: { minVariantPrice: min, maxVariantPrice: max },
|
|
100
|
-
variants,
|
|
101
|
-
options,
|
|
102
|
-
images,
|
|
103
|
-
featuredImage: images[0] ?? { url: '', altText: data.name ?? '' },
|
|
104
|
-
tags: [],
|
|
105
|
-
seo: {
|
|
106
|
-
title: data.meta_title ?? data.name ?? '',
|
|
107
|
-
description: data.meta_description ?? '',
|
|
108
|
-
},
|
|
109
|
-
updatedAt: data.updated_at ?? new Date().toISOString(),
|
|
110
|
-
createdAt: data.created_at ?? new Date().toISOString(),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ============================================
|
|
115
|
-
// Collection Transforms (Magento Categories)
|
|
116
|
-
// ============================================
|
|
117
|
-
|
|
118
|
-
export function transformCollection(data: any): Collection {
|
|
119
|
-
return {
|
|
120
|
-
id: String(data.id ?? data.uid ?? ''),
|
|
121
|
-
handle: data.url_key ?? data.url_path ?? String(data.id ?? data.uid ?? ''),
|
|
122
|
-
title: data.name ?? '',
|
|
123
|
-
description: data.description ?? '',
|
|
124
|
-
seo: {
|
|
125
|
-
title: data.meta_title ?? data.name ?? '',
|
|
126
|
-
description: data.meta_description ?? '',
|
|
127
|
-
},
|
|
128
|
-
updatedAt: data.updated_at ?? new Date().toISOString(),
|
|
129
|
-
path: `/collections/${data.url_key ?? data.url_path ?? data.id}`,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ============================================
|
|
134
|
-
// Cart Transforms (Magento Cart / Quote)
|
|
135
|
-
// ============================================
|
|
136
|
-
|
|
137
|
-
export function transformCart(data: any, currencyCode: string = 'USD'): Cart {
|
|
138
|
-
const items: CartItem[] = (data.items ?? []).map((item: any): CartItem => {
|
|
139
|
-
const product = item.product ?? {};
|
|
140
|
-
const priceVal = item.prices?.price?.value ?? product.price_range?.minimum_price?.final_price?.value ?? 0;
|
|
141
|
-
const totalVal = item.prices?.row_total?.value ?? priceVal * (item.quantity ?? 1);
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
id: String(item.id ?? item.uid ?? ''),
|
|
145
|
-
quantity: item.quantity ?? 1,
|
|
146
|
-
cost: {
|
|
147
|
-
totalAmount: toMoney(totalVal, item.prices?.price?.currency ?? currencyCode),
|
|
148
|
-
},
|
|
149
|
-
merchandise: {
|
|
150
|
-
id: String(product.sku ?? item.id ?? ''),
|
|
151
|
-
title: product.name ?? item.product?.name ?? '',
|
|
152
|
-
selectedOptions: (item.configurable_options ?? []).map((opt: any) => ({
|
|
153
|
-
name: opt.option_label ?? '',
|
|
154
|
-
value: opt.value_label ?? '',
|
|
155
|
-
})),
|
|
156
|
-
product: {
|
|
157
|
-
id: String(product.id ?? product.uid ?? ''),
|
|
158
|
-
handle: product.url_key ?? '',
|
|
159
|
-
title: product.name ?? '',
|
|
160
|
-
featuredImage: {
|
|
161
|
-
url: product.small_image?.url ?? product.thumbnail?.url ?? '',
|
|
162
|
-
altText: product.name ?? '',
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
};
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const totalVal = data.prices?.grand_total?.value ?? 0;
|
|
170
|
-
const subtotalVal = data.prices?.subtotal_excluding_tax?.value ?? data.prices?.subtotal_including_tax?.value ?? totalVal;
|
|
171
|
-
const taxVal = (data.prices?.applied_taxes ?? []).reduce((sum: number, t: any) => sum + (t.amount?.value ?? 0), 0);
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
id: String(data.id ?? ''),
|
|
175
|
-
checkoutUrl: '',
|
|
176
|
-
totalQuantity: items.reduce((sum, i) => sum + i.quantity, 0),
|
|
177
|
-
cost: {
|
|
178
|
-
subtotalAmount: toMoney(subtotalVal, data.prices?.grand_total?.currency ?? currencyCode),
|
|
179
|
-
totalAmount: toMoney(totalVal, data.prices?.grand_total?.currency ?? currencyCode),
|
|
180
|
-
totalTaxAmount: toMoney(taxVal, currencyCode),
|
|
181
|
-
},
|
|
182
|
-
lines: items,
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// ============================================
|
|
187
|
-
// Customer Transforms
|
|
188
|
-
// ============================================
|
|
189
|
-
|
|
190
|
-
export function transformCustomer(data: any): Customer {
|
|
191
|
-
return {
|
|
192
|
-
id: String(data.id ?? ''),
|
|
193
|
-
email: data.email ?? '',
|
|
194
|
-
firstName: data.firstname ?? data.first_name ?? '',
|
|
195
|
-
lastName: data.lastname ?? data.last_name ?? '',
|
|
196
|
-
phone: data.phone ?? '',
|
|
197
|
-
hasAccount: true,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function transformAddress(addr: any): Address {
|
|
202
|
-
return {
|
|
203
|
-
id: String(addr.id ?? ''),
|
|
204
|
-
firstName: addr.firstname ?? '',
|
|
205
|
-
lastName: addr.lastname ?? '',
|
|
206
|
-
company: addr.company ?? '',
|
|
207
|
-
address1: Array.isArray(addr.street) ? addr.street[0] ?? '' : addr.street ?? '',
|
|
208
|
-
address2: Array.isArray(addr.street) ? addr.street[1] ?? '' : '',
|
|
209
|
-
city: addr.city ?? '',
|
|
210
|
-
province: addr.region?.region ?? '',
|
|
211
|
-
postalCode: addr.postcode ?? '',
|
|
212
|
-
countryCode: addr.country_code ?? addr.country_id ?? '',
|
|
213
|
-
phone: addr.telephone ?? '',
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// ============================================
|
|
218
|
-
// Order Transforms
|
|
219
|
-
// ============================================
|
|
220
|
-
|
|
221
|
-
export function transformOrder(data: any, currencyCode: string = 'USD'): Order {
|
|
222
|
-
const items: OrderItem[] = (data.items ?? []).map((item: any) => ({
|
|
223
|
-
id: String(item.id ?? ''),
|
|
224
|
-
title: item.product_name ?? '',
|
|
225
|
-
quantity: item.quantity_ordered ?? item.quantity ?? 1,
|
|
226
|
-
price: toMoney(item.product_sale_price?.value ?? item.price ?? 0, currencyCode),
|
|
227
|
-
total: toMoney((item.product_sale_price?.value ?? item.price ?? 0) * (item.quantity_ordered ?? item.quantity ?? 1), currencyCode),
|
|
228
|
-
sku: item.product_sku ?? item.sku,
|
|
229
|
-
productId: String(item.product_id ?? ''),
|
|
230
|
-
}));
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
id: String(data.id ?? data.order_number ?? ''),
|
|
234
|
-
orderNumber: data.order_number ?? data.number ?? String(data.id ?? ''),
|
|
235
|
-
email: data.email ?? '',
|
|
236
|
-
status: (data.status === 'complete' ? 'completed' : data.status === 'canceled' ? 'cancelled' : data.status === 'closed' ? 'refunded' : 'pending') as any,
|
|
237
|
-
paymentStatus: (data.payment_status === 'paid' ? 'paid' : 'pending') as any,
|
|
238
|
-
fulfillmentStatus: (data.shipment_status === 'shipped' ? 'fulfilled' : 'unfulfilled') as any,
|
|
239
|
-
items,
|
|
240
|
-
total: toMoney(data.total?.grand_total?.value ?? data.grand_total ?? 0, currencyCode),
|
|
241
|
-
subtotal: toMoney(data.total?.subtotal?.value ?? data.subtotal ?? 0, currencyCode),
|
|
242
|
-
tax: toMoney(data.total?.total_tax?.value ?? data.tax_amount ?? 0, currencyCode),
|
|
243
|
-
shipping: toMoney(data.total?.total_shipping?.value ?? data.shipping_amount ?? 0, currencyCode),
|
|
244
|
-
shippingAddress: data.shipping_address ? transformAddress(data.shipping_address) : undefined,
|
|
245
|
-
billingAddress: data.billing_address ? transformAddress(data.billing_address) : undefined,
|
|
246
|
-
createdAt: data.order_date ?? data.created_at ?? new Date().toISOString(),
|
|
247
|
-
updatedAt: data.updated_at ?? data.order_date ?? new Date().toISOString(),
|
|
248
|
-
};
|
|
249
|
-
}
|
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Address,
|
|
3
|
-
AuthConfig,
|
|
4
|
-
AuthProvider,
|
|
5
|
-
AuthResponse,
|
|
6
|
-
CreateAddressData,
|
|
7
|
-
Customer,
|
|
8
|
-
LoginCredentials,
|
|
9
|
-
RegisterData,
|
|
10
|
-
UpdateCustomerData,
|
|
11
|
-
} from '@thorprovider/types';
|
|
12
|
-
import { createLogger } from '../../core/logger';
|
|
13
|
-
import { ProviderAPIError, extractErrorMessage } from '../../core/errors';
|
|
14
|
-
import { AuthStorage } from '../../lib/auth-storage';
|
|
15
|
-
import {
|
|
16
|
-
CUSTOMER_ACCESS_TOKEN_CREATE,
|
|
17
|
-
CUSTOMER_ACCESS_TOKEN_DELETE,
|
|
18
|
-
CUSTOMER_ADDRESS_CREATE,
|
|
19
|
-
CUSTOMER_ADDRESS_DELETE,
|
|
20
|
-
CUSTOMER_ADDRESS_UPDATE,
|
|
21
|
-
CUSTOMER_CREATE,
|
|
22
|
-
CUSTOMER_RECOVER,
|
|
23
|
-
CUSTOMER_RESET,
|
|
24
|
-
CUSTOMER_UPDATE,
|
|
25
|
-
} from './mutations';
|
|
26
|
-
import { GET_CUSTOMER } from './queries';
|
|
27
|
-
import { transformAddress, transformCustomer, transformCustomerAddresses } from './transforms';
|
|
28
|
-
|
|
29
|
-
type ShopifyGraphqlRequester = <T = any>(gql: string, variables?: Record<string, unknown>) => Promise<T>;
|
|
30
|
-
|
|
31
|
-
interface ShopifyCustomerToken {
|
|
32
|
-
accessToken: string;
|
|
33
|
-
expiresAt?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface ShopifyAuthDependencies {
|
|
37
|
-
query: ShopifyGraphqlRequester;
|
|
38
|
-
authConfig: AuthConfig;
|
|
39
|
-
debug?: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export class ShopifyAuthProvider implements AuthProvider {
|
|
43
|
-
private readonly logger;
|
|
44
|
-
private readonly storage: AuthStorage;
|
|
45
|
-
|
|
46
|
-
constructor(private readonly deps: ShopifyAuthDependencies) {
|
|
47
|
-
this.logger = createLogger('ShopifyAuthProvider', deps.debug);
|
|
48
|
-
this.storage = new AuthStorage(deps.authConfig, 'thor:shopify:auth');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
private async readToken(): Promise<ShopifyCustomerToken | null> {
|
|
52
|
-
const token = await this.storage.getJson<ShopifyCustomerToken>('customer-access-token');
|
|
53
|
-
if (!token?.accessToken) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (token.expiresAt && new Date(token.expiresAt).getTime() <= Date.now()) {
|
|
58
|
-
await this.storage.remove('customer-access-token');
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return token;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async getStoredToken(): Promise<ShopifyCustomerToken | null> {
|
|
66
|
-
return this.readToken();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
private async writeToken(token: ShopifyCustomerToken | null): Promise<void> {
|
|
70
|
-
if (!token) {
|
|
71
|
-
await this.storage.remove('customer-access-token');
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
await this.storage.setJson('customer-access-token', token);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
private async requireToken(): Promise<ShopifyCustomerToken> {
|
|
79
|
-
const token = await this.readToken();
|
|
80
|
-
if (!token) {
|
|
81
|
-
throw new ProviderAPIError('Shopify customer access token is required.', 'shopify', 401);
|
|
82
|
-
}
|
|
83
|
-
return token;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private async fetchCurrentCustomer(): Promise<any | null> {
|
|
87
|
-
const token = await this.readToken();
|
|
88
|
-
if (!token) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
const data = await this.deps.query<{ customer: any }>(GET_CUSTOMER, {
|
|
94
|
-
customerAccessToken: token.accessToken,
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
return data.customer ?? null;
|
|
98
|
-
} catch (error) {
|
|
99
|
-
await this.writeToken(null);
|
|
100
|
-
this.logger.warn('Failed to fetch current Shopify customer', extractErrorMessage(error));
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async login(credentials: LoginCredentials): Promise<AuthResponse> {
|
|
106
|
-
try {
|
|
107
|
-
const data = await this.deps.query<{
|
|
108
|
-
customerAccessTokenCreate: {
|
|
109
|
-
customerAccessToken?: ShopifyCustomerToken;
|
|
110
|
-
customerUserErrors?: Array<{ message: string }>;
|
|
111
|
-
};
|
|
112
|
-
}>(CUSTOMER_ACCESS_TOKEN_CREATE, {
|
|
113
|
-
input: {
|
|
114
|
-
email: credentials.email,
|
|
115
|
-
password: credentials.password,
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
const result = data.customerAccessTokenCreate;
|
|
120
|
-
if (result.customerUserErrors?.length || !result.customerAccessToken?.accessToken) {
|
|
121
|
-
throw new ProviderAPIError(
|
|
122
|
-
result.customerUserErrors?.map((entry) => entry.message).join('; ') || 'Shopify login failed.',
|
|
123
|
-
'shopify',
|
|
124
|
-
400,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
await this.writeToken(result.customerAccessToken);
|
|
129
|
-
const customer = await this.getCurrentCustomer();
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
token: result.customerAccessToken.accessToken,
|
|
133
|
-
customer: customer ?? undefined,
|
|
134
|
-
};
|
|
135
|
-
} catch (error) {
|
|
136
|
-
throw new ProviderAPIError(`Shopify login failed: ${extractErrorMessage(error)}`, 'shopify', 400);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async register(data: RegisterData): Promise<AuthResponse> {
|
|
141
|
-
try {
|
|
142
|
-
const createResult = await this.deps.query<{
|
|
143
|
-
customerCreate: {
|
|
144
|
-
customer?: any;
|
|
145
|
-
customerUserErrors?: Array<{ message: string }>;
|
|
146
|
-
};
|
|
147
|
-
}>(CUSTOMER_CREATE, {
|
|
148
|
-
input: {
|
|
149
|
-
firstName: data.first_name,
|
|
150
|
-
lastName: data.last_name,
|
|
151
|
-
email: data.email,
|
|
152
|
-
phone: data.phone,
|
|
153
|
-
password: data.password,
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const result = createResult.customerCreate;
|
|
158
|
-
if (result.customerUserErrors?.length) {
|
|
159
|
-
throw new ProviderAPIError(
|
|
160
|
-
result.customerUserErrors.map((entry) => entry.message).join('; '),
|
|
161
|
-
'shopify',
|
|
162
|
-
400,
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return this.login({ email: data.email, password: data.password });
|
|
167
|
-
} catch (error) {
|
|
168
|
-
throw new ProviderAPIError(`Shopify registration failed: ${extractErrorMessage(error)}`, 'shopify', 400);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async logout(): Promise<void> {
|
|
173
|
-
try {
|
|
174
|
-
const token = await this.readToken();
|
|
175
|
-
if (token?.accessToken) {
|
|
176
|
-
await this.deps.query(CUSTOMER_ACCESS_TOKEN_DELETE, {
|
|
177
|
-
customerAccessToken: token.accessToken,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
} catch (error) {
|
|
181
|
-
this.logger.warn('Shopify logout returned an error', extractErrorMessage(error));
|
|
182
|
-
} finally {
|
|
183
|
-
await this.writeToken(null);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async requestPasswordReset(email: string): Promise<void> {
|
|
188
|
-
try {
|
|
189
|
-
const data = await this.deps.query<{
|
|
190
|
-
customerRecover: {
|
|
191
|
-
customerUserErrors?: Array<{ message: string }>;
|
|
192
|
-
};
|
|
193
|
-
}>(CUSTOMER_RECOVER, { email });
|
|
194
|
-
|
|
195
|
-
if (data.customerRecover.customerUserErrors?.length) {
|
|
196
|
-
throw new ProviderAPIError(
|
|
197
|
-
data.customerRecover.customerUserErrors.map((entry) => entry.message).join('; '),
|
|
198
|
-
'shopify',
|
|
199
|
-
400,
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
} catch (error) {
|
|
203
|
-
throw new ProviderAPIError(`Shopify password reset request failed: ${extractErrorMessage(error)}`, 'shopify', 400);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async resetPassword(token: string, password: string, metadata?: Record<string, unknown>): Promise<void> {
|
|
208
|
-
const customerId = typeof metadata?.customerId === 'string' ? metadata.customerId : null;
|
|
209
|
-
if (!customerId) {
|
|
210
|
-
throw new ProviderAPIError('Shopify password reset requires metadata.customerId.', 'shopify', 400);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
const data = await this.deps.query<{
|
|
215
|
-
customerReset: {
|
|
216
|
-
customerAccessToken?: ShopifyCustomerToken;
|
|
217
|
-
customerUserErrors?: Array<{ message: string }>;
|
|
218
|
-
};
|
|
219
|
-
}>(CUSTOMER_RESET, {
|
|
220
|
-
id: customerId,
|
|
221
|
-
input: {
|
|
222
|
-
resetToken: token,
|
|
223
|
-
password,
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
if (data.customerReset.customerUserErrors?.length) {
|
|
228
|
-
throw new ProviderAPIError(
|
|
229
|
-
data.customerReset.customerUserErrors.map((entry) => entry.message).join('; '),
|
|
230
|
-
'shopify',
|
|
231
|
-
400,
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (data.customerReset.customerAccessToken?.accessToken) {
|
|
236
|
-
await this.writeToken(data.customerReset.customerAccessToken);
|
|
237
|
-
}
|
|
238
|
-
} catch (error) {
|
|
239
|
-
throw new ProviderAPIError(`Shopify password reset failed: ${extractErrorMessage(error)}`, 'shopify', 400);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
async getCurrentCustomer(): Promise<Customer | null> {
|
|
244
|
-
const customer = await this.fetchCurrentCustomer();
|
|
245
|
-
return customer ? transformCustomer(customer) : null;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
async updateCustomer(data: UpdateCustomerData): Promise<Customer> {
|
|
249
|
-
const token = await this.requireToken();
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
const payload: Record<string, unknown> = {};
|
|
253
|
-
if (data.first_name !== undefined) payload.firstName = data.first_name;
|
|
254
|
-
if (data.last_name !== undefined) payload.lastName = data.last_name;
|
|
255
|
-
if (data.email !== undefined) payload.email = data.email;
|
|
256
|
-
if (data.phone !== undefined && data.phone !== null) payload.phone = data.phone;
|
|
257
|
-
if (data.password !== undefined) payload.password = data.password;
|
|
258
|
-
|
|
259
|
-
const response = await this.deps.query<{
|
|
260
|
-
customerUpdate: {
|
|
261
|
-
customer?: any;
|
|
262
|
-
customerAccessToken?: ShopifyCustomerToken;
|
|
263
|
-
customerUserErrors?: Array<{ message: string }>;
|
|
264
|
-
};
|
|
265
|
-
}>(CUSTOMER_UPDATE, {
|
|
266
|
-
customerAccessToken: token.accessToken,
|
|
267
|
-
customer: payload,
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
const result = response.customerUpdate;
|
|
271
|
-
if (result.customerUserErrors?.length || !result.customer) {
|
|
272
|
-
throw new ProviderAPIError(
|
|
273
|
-
result.customerUserErrors?.map((entry) => entry.message).join('; ') || 'Shopify customer update failed.',
|
|
274
|
-
'shopify',
|
|
275
|
-
400,
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (result.customerAccessToken?.accessToken) {
|
|
280
|
-
await this.writeToken(result.customerAccessToken);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return transformCustomer(result.customer);
|
|
284
|
-
} catch (error) {
|
|
285
|
-
throw new ProviderAPIError(`Shopify update customer failed: ${extractErrorMessage(error)}`, 'shopify', 400);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async isAuthenticated(): Promise<boolean> {
|
|
290
|
-
return (await this.getCurrentCustomer()) !== null;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
getAuthToken(): string | null {
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
async getAddresses(): Promise<Address[]> {
|
|
298
|
-
const customer = await this.fetchCurrentCustomer();
|
|
299
|
-
return customer ? transformCustomerAddresses(customer) : [];
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
private toMailingAddressInput(data: Partial<CreateAddressData>): Record<string, unknown> {
|
|
303
|
-
return {
|
|
304
|
-
firstName: data.firstName,
|
|
305
|
-
lastName: data.lastName,
|
|
306
|
-
company: data.company,
|
|
307
|
-
address1: data.address1,
|
|
308
|
-
address2: data.address2,
|
|
309
|
-
city: data.city,
|
|
310
|
-
province: data.province,
|
|
311
|
-
country: data.countryCode,
|
|
312
|
-
zip: data.postalCode,
|
|
313
|
-
phone: data.phone,
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
async createAddress(data: CreateAddressData): Promise<Address> {
|
|
318
|
-
const token = await this.requireToken();
|
|
319
|
-
|
|
320
|
-
try {
|
|
321
|
-
const response = await this.deps.query<{
|
|
322
|
-
customerAddressCreate: {
|
|
323
|
-
customerAddress?: any;
|
|
324
|
-
customerUserErrors?: Array<{ message: string }>;
|
|
325
|
-
};
|
|
326
|
-
}>(CUSTOMER_ADDRESS_CREATE, {
|
|
327
|
-
customerAccessToken: token.accessToken,
|
|
328
|
-
address: this.toMailingAddressInput(data),
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
if (response.customerAddressCreate.customerUserErrors?.length || !response.customerAddressCreate.customerAddress) {
|
|
332
|
-
throw new ProviderAPIError(
|
|
333
|
-
response.customerAddressCreate.customerUserErrors?.map((entry) => entry.message).join('; ') || 'Shopify address create failed.',
|
|
334
|
-
'shopify',
|
|
335
|
-
400,
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return transformAddress(response.customerAddressCreate.customerAddress);
|
|
340
|
-
} catch (error) {
|
|
341
|
-
throw new ProviderAPIError(`Shopify create address failed: ${extractErrorMessage(error)}`, 'shopify', 400);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
async updateAddress(addressId: string, data: Partial<CreateAddressData>): Promise<Address> {
|
|
346
|
-
const token = await this.requireToken();
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
const response = await this.deps.query<{
|
|
350
|
-
customerAddressUpdate: {
|
|
351
|
-
customerAddress?: any;
|
|
352
|
-
customerUserErrors?: Array<{ message: string }>;
|
|
353
|
-
};
|
|
354
|
-
}>(CUSTOMER_ADDRESS_UPDATE, {
|
|
355
|
-
customerAccessToken: token.accessToken,
|
|
356
|
-
id: addressId,
|
|
357
|
-
address: this.toMailingAddressInput(data),
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
if (response.customerAddressUpdate.customerUserErrors?.length || !response.customerAddressUpdate.customerAddress) {
|
|
361
|
-
throw new ProviderAPIError(
|
|
362
|
-
response.customerAddressUpdate.customerUserErrors?.map((entry) => entry.message).join('; ') || 'Shopify address update failed.',
|
|
363
|
-
'shopify',
|
|
364
|
-
400,
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return transformAddress(response.customerAddressUpdate.customerAddress);
|
|
369
|
-
} catch (error) {
|
|
370
|
-
throw new ProviderAPIError(`Shopify update address failed: ${extractErrorMessage(error)}`, 'shopify', 400);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
async deleteAddress(addressId: string): Promise<void> {
|
|
375
|
-
const token = await this.requireToken();
|
|
376
|
-
|
|
377
|
-
try {
|
|
378
|
-
const response = await this.deps.query<{
|
|
379
|
-
customerAddressDelete: {
|
|
380
|
-
customerUserErrors?: Array<{ message: string }>;
|
|
381
|
-
};
|
|
382
|
-
}>(CUSTOMER_ADDRESS_DELETE, {
|
|
383
|
-
customerAccessToken: token.accessToken,
|
|
384
|
-
id: addressId,
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
if (response.customerAddressDelete.customerUserErrors?.length) {
|
|
388
|
-
throw new ProviderAPIError(
|
|
389
|
-
response.customerAddressDelete.customerUserErrors.map((entry) => entry.message).join('; '),
|
|
390
|
-
'shopify',
|
|
391
|
-
400,
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
} catch (error) {
|
|
395
|
-
throw new ProviderAPIError(`Shopify delete address failed: ${extractErrorMessage(error)}`, 'shopify', 400);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
async deleteCustomerAccount(): Promise<void> {
|
|
400
|
-
throw new ProviderAPIError('Shopify customer self-service deletion requires Admin API and is not supported.', 'shopify', 501);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
async linkCustomerToChannel(): Promise<{ linked: boolean; reason?: string }> {
|
|
404
|
-
// Shopify does not have the concept of Thor sales channels.
|
|
405
|
-
// Return a no-op response so multi-tenant flows don't break when running against Shopify.
|
|
406
|
-
return { linked: false, reason: 'not_applicable' };
|
|
407
|
-
}
|
|
408
|
-
}
|