@vencav/shoptet-client 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Václav Vracovský
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # @vencav/shoptet-client
2
+
3
+ TypeScript client for the [Shoptet](https://www.shoptet.cz/) Private API. Covers orders, stock, pricelists, and webhook signature validation.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @vencav/shoptet-client axios
9
+ ```
10
+
11
+ `axios` is a peer dependency and must be installed separately.
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { createShoptetClient } from "@vencav/shoptet-client";
17
+
18
+ const client = createShoptetClient(
19
+ {
20
+ apiUrl: "https://api.myshop.shoptet.com", // must be HTTPS
21
+ oauthAccessToken: "your-private-api-token",
22
+ webhookSecret: "your-webhook-secret",
23
+ },
24
+ {
25
+ maxBatchSize: 300, // optional, default 300 (Shoptet API hard limit)
26
+ }
27
+ );
28
+ ```
29
+
30
+ All methods return a `Result<T>` — either `{ status: "ok"; data: T }` or `{ status: "error"; error: Error }`. No method throws.
31
+
32
+ ## API
33
+
34
+ ### `getOrder(orderCode)`
35
+
36
+ ```ts
37
+ const result = await client.getOrder("OBJ-2024-001");
38
+ if (result.status === "error") {
39
+ console.error(result.error.message);
40
+ } else {
41
+ console.log(result.data.code, result.data.customer.email);
42
+ }
43
+ ```
44
+
45
+ ### `createOrder(order, options?)`
46
+
47
+ Creates an order via `POST /api/orders`. Accepts a `CreateOrderRequest` object and optional suppression flags.
48
+
49
+ ```ts
50
+ const result = await client.createOrder(
51
+ {
52
+ externalCode: "EXT-001",
53
+ currency: { code: "CZK" },
54
+ email: "customer@example.com",
55
+ items: [
56
+ {
57
+ itemType: "product",
58
+ code: "SKU-123",
59
+ name: "Product name",
60
+ amount: "1.000",
61
+ unitPriceWithVat: "299.00",
62
+ vatRate: "21.00",
63
+ },
64
+ ],
65
+ },
66
+ {
67
+ suppressEmailSending: true,
68
+ suppressDocumentGeneration: true,
69
+ }
70
+ );
71
+ ```
72
+
73
+ ### `batchUpdateStock(items)`
74
+
75
+ Updates stock quantities. Quantities are relative movements (positive = add, negative = subtract).
76
+
77
+ ```ts
78
+ const result = await client.batchUpdateStock([
79
+ { productCode: "SKU-123", quantity: 10 },
80
+ { productCode: "SKU-456", quantity: -3 },
81
+ ]);
82
+
83
+ if (result.status === "ok") {
84
+ console.log(`${result.data.success} updated, ${result.data.failed} failed`);
85
+ for (const err of result.data.errors) {
86
+ console.error(`${err.code}: ${err.error}`);
87
+ }
88
+ }
89
+ ```
90
+
91
+ > **Note:** The Shoptet API hard limit is 300 items per request. `batchUpdateStock` returns `Result.error` if `items.length` exceeds `maxBatchSize` (configurable via options, default 300).
92
+
93
+ ### `batchUpdatePrices(items)`
94
+
95
+ Updates selling prices (with VAT) on the default pricelist.
96
+
97
+ ```ts
98
+ const result = await client.batchUpdatePrices([
99
+ { code: "SKU-123", price: 299.9 },
100
+ { code: "SKU-456", price: 149.0 },
101
+ ]);
102
+ ```
103
+
104
+ Same `{ success, failed, errors }` shape as `batchUpdateStock`.
105
+
106
+ ### `validateWebhookSignature(payload, signature)`
107
+
108
+ Validates the `Shoptet-Webhook-Signature` header using HMAC-SHA1. Returns `true` if valid.
109
+
110
+ ```ts
111
+ app.post("/webhook", (req, res) => {
112
+ const signature = req.headers["shoptet-webhook-signature"] ?? "";
113
+ if (!client.validateWebhookSignature(req.rawBody, signature)) {
114
+ return res.status(401).json({ error: "Invalid signature" });
115
+ }
116
+ // process webhook...
117
+ });
118
+ ```
119
+
120
+ ### `getDefaultStockId()` / `getDefaultPricelistId()`
121
+
122
+ Returns the ID of the first warehouse / pricelist found in Shoptet. Result is cached after the first successful call. Useful if you need the IDs for custom requests.
123
+
124
+ ## Result type
125
+
126
+ All methods return `Result<T>`, re-exported from `@vencav/result`:
127
+
128
+ ```ts
129
+ type Result<T, E = Error> =
130
+ | { status: "ok"; data: T }
131
+ | { status: "error"; error: E };
132
+ ```
133
+
134
+ Check with `result.status === "ok"` or use the exported `Result.isOk()` / `Result.isError()` helpers.
135
+
136
+ ```ts
137
+ import { Result } from "@vencav/shoptet-client";
138
+ ```
139
+
140
+ ## Security
141
+
142
+ - Webhook signatures are validated using `crypto.timingSafeEqual` to prevent timing attacks.
143
+ - The API token is sent in a request header (`Shoptet-Private-API-Token`). If you use error-reporting tools (Sentry, Datadog), configure them to scrub this header from outgoing HTTP request metadata.
144
+
145
+ ## TypeScript
146
+
147
+ Full type definitions are included. Key exported types: `ShoptetCredentials`, `ShoptetClientOptions`, `ShoptetOrder`, `ShoptetOrderItem`, `CreateOrderRequest`, `CreateOrderOptions`.
148
+
149
+ ## License
150
+
151
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,271 @@
1
+ 'use strict';
2
+
3
+ var axios = require('axios');
4
+ var crypto = require('crypto');
5
+ var result = require('@vencav/result');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var axios__default = /*#__PURE__*/_interopDefault(axios);
10
+ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
11
+
12
+ // src/client.ts
13
+ var SHOPTET_API_BATCH_LIMIT = 300;
14
+ var SHOPTET_API_TIMEOUT_MS = 3e4;
15
+ var SUPPRESS_FLAGS = [
16
+ "suppressDocumentGeneration",
17
+ "suppressEmailSending",
18
+ "suppressProductChecking",
19
+ "suppressStockMovements",
20
+ "suppressHistoricalMandatoryFields",
21
+ "suppressHistoricalPaymentChecking",
22
+ "suppressHistoricalShippingChecking"
23
+ ];
24
+ var requireNonEmpty = (ctx, value, name) => {
25
+ if (!value) throw new Error(`${ctx}: ${name} must not be empty`);
26
+ };
27
+ var resolvePositiveInt = (ctx, value, fallback, name) => {
28
+ const resolved = value ?? fallback;
29
+ if (!Number.isInteger(resolved) || resolved < 1) {
30
+ throw new Error(`${ctx}: ${name} must be a positive integer`);
31
+ }
32
+ return resolved;
33
+ };
34
+ var validateHttpsUrl = (ctx, raw, fieldName = "url") => {
35
+ let url;
36
+ try {
37
+ url = new URL(raw);
38
+ } catch {
39
+ throw new Error(`${ctx}: ${fieldName} is not a valid URL`);
40
+ }
41
+ if (url.protocol !== "https:") throw new Error(`${ctx}: ${fieldName} must use HTTPS`);
42
+ if (url.username || url.password) throw new Error(`${ctx}: ${fieldName} must not contain credentials`);
43
+ };
44
+ var apiCall = async (promise) => {
45
+ try {
46
+ return result.Result.ok((await promise).data);
47
+ } catch (error) {
48
+ if (axios__default.default.isAxiosError(error)) {
49
+ const apiErrors = error.response?.data?.errors;
50
+ if (Array.isArray(apiErrors) && apiErrors.length > 0) {
51
+ const msg = joinMessages(apiErrors);
52
+ return result.Result.error(new Error(`Shoptet API error (${error.response?.status}): ${msg}`));
53
+ }
54
+ return result.Result.error(new Error(error.message || "Unknown error"));
55
+ }
56
+ return result.Result.error(error instanceof Error ? error : new Error(String(error)));
57
+ }
58
+ };
59
+ var joinMessages = (errors) => errors.map((e) => e.message).join(", ");
60
+ var parseResponseErrors = (raw) => (raw ?? []).map((e) => ({ code: e.instance ?? "unknown", error: e.message ?? "Unknown error" }));
61
+ var buildSuppressParams = (options) => {
62
+ const params = new URLSearchParams();
63
+ for (const flag of SUPPRESS_FLAGS) {
64
+ if (options[flag]) params.set(flag, "true");
65
+ }
66
+ return params;
67
+ };
68
+ var scrubSensitiveHeaders = (headers) => {
69
+ if (!headers || typeof headers !== "object") return;
70
+ const axiosHeaders = headers;
71
+ if (typeof axiosHeaders.delete === "function") {
72
+ axiosHeaders.delete("Shoptet-Private-API-Token");
73
+ return;
74
+ }
75
+ const headerEntries = Object.keys(headers);
76
+ for (const key of headerEntries) {
77
+ if (key.toLowerCase() === "shoptet-private-api-token") {
78
+ delete headers[key];
79
+ }
80
+ }
81
+ };
82
+ var checkWebhookSignature = (secret, payload, signature) => {
83
+ const expected = crypto__default.default.createHmac("sha1", secret).update(payload).digest("hex");
84
+ try {
85
+ return crypto__default.default.timingSafeEqual(
86
+ Uint8Array.from(Buffer.from(signature, "hex")),
87
+ Uint8Array.from(Buffer.from(expected, "hex"))
88
+ );
89
+ } catch {
90
+ return false;
91
+ }
92
+ };
93
+ var fetchOrder = async (client, orderCode) => {
94
+ const result$1 = await apiCall(
95
+ client.get(`/api/orders/${encodeURIComponent(orderCode)}`)
96
+ );
97
+ if (result.Result.isError(result$1)) return result$1;
98
+ if (result$1.data.errors?.length) {
99
+ const msg = joinMessages(result$1.data.errors);
100
+ return result.Result.error(new Error(`Shoptet API error: ${msg}`));
101
+ }
102
+ const order = result$1.data.data?.order;
103
+ if (!order) {
104
+ return result.Result.error(new Error("Shoptet API returned no order data"));
105
+ }
106
+ return result.Result.ok(order);
107
+ };
108
+ var fetchStockId = async (client) => {
109
+ const result$1 = await apiCall(
110
+ client.get("/api/stocks")
111
+ );
112
+ if (result.Result.isError(result$1)) return result$1;
113
+ const stocks = result$1.data.data?.stocks;
114
+ if (!stocks || stocks.length === 0) {
115
+ return result.Result.error(new Error("No stocks (warehouses) found in Shoptet"));
116
+ }
117
+ return result.Result.ok(stocks[0].id);
118
+ };
119
+ var fetchPricelistId = async (client) => {
120
+ const result$1 = await apiCall(
121
+ client.get("/api/pricelists")
122
+ );
123
+ if (result.Result.isError(result$1)) return result$1;
124
+ const pricelists = result$1.data.data?.priceLists ?? result$1.data.data?.pricelists;
125
+ if (!pricelists || pricelists.length === 0) {
126
+ return result.Result.error(new Error("No pricelists found in Shoptet"));
127
+ }
128
+ return result.Result.ok(pricelists[0].id);
129
+ };
130
+ var updateStock = async (client, getStockId, maxBatchSize, items) => {
131
+ if (items.length === 0) return result.Result.ok({ success: 0, failed: 0, errors: [] });
132
+ if (items.length > maxBatchSize) {
133
+ return result.Result.error(new Error(`batchUpdateStock: too many items (${items.length} > ${maxBatchSize})`));
134
+ }
135
+ const invalidQuantity = items.find((item) => !Number.isFinite(item.quantity));
136
+ if (invalidQuantity) {
137
+ return result.Result.error(
138
+ new Error(`batchUpdateStock: invalid quantity for "${invalidQuantity.productCode}" (${invalidQuantity.quantity})`)
139
+ );
140
+ }
141
+ const stockIdResult = await getStockId();
142
+ if (result.Result.isError(stockIdResult)) {
143
+ return result.Result.ok({
144
+ success: 0,
145
+ failed: items.length,
146
+ errors: items.map((item) => ({ code: item.productCode, error: stockIdResult.error.message }))
147
+ });
148
+ }
149
+ const result$1 = await apiCall(
150
+ client.patch(
151
+ `/api/stocks/${stockIdResult.data}/movements`,
152
+ { data: items.map((item) => ({ productCode: item.productCode, quantity: item.quantity })) }
153
+ )
154
+ );
155
+ if (result.Result.isError(result$1)) {
156
+ return result.Result.ok({
157
+ success: 0,
158
+ failed: items.length,
159
+ errors: items.map((item) => ({ code: item.productCode, error: result$1.error.message }))
160
+ });
161
+ }
162
+ const errors = parseResponseErrors(result$1.data.errors);
163
+ return result.Result.ok({ success: items.length - errors.length, failed: errors.length, errors });
164
+ };
165
+ var updatePrices = async (client, getPricelistId, maxBatchSize, items) => {
166
+ if (items.length === 0) return result.Result.ok({ success: 0, failed: 0, errors: [] });
167
+ if (items.length > maxBatchSize) {
168
+ return result.Result.error(new Error(`batchUpdatePrices: too many items (${items.length} > ${maxBatchSize})`));
169
+ }
170
+ const invalidPrice = items.find((item) => !Number.isFinite(item.price) || item.price < 0);
171
+ if (invalidPrice) {
172
+ return result.Result.error(
173
+ new Error(`batchUpdatePrices: invalid price for "${invalidPrice.code}" (${invalidPrice.price})`)
174
+ );
175
+ }
176
+ const pricelistIdResult = await getPricelistId();
177
+ if (result.Result.isError(pricelistIdResult)) {
178
+ return result.Result.ok({
179
+ success: 0,
180
+ failed: items.length,
181
+ errors: items.map((item) => ({ code: item.code, error: pricelistIdResult.error.message }))
182
+ });
183
+ }
184
+ const result$1 = await apiCall(
185
+ client.patch(
186
+ `/api/pricelists/${pricelistIdResult.data}`,
187
+ {
188
+ data: items.map((item) => ({
189
+ code: item.code,
190
+ includingVat: true,
191
+ price: { price: item.price.toFixed(2) }
192
+ }))
193
+ }
194
+ )
195
+ );
196
+ if (result.Result.isError(result$1)) {
197
+ return result.Result.ok({
198
+ success: 0,
199
+ failed: items.length,
200
+ errors: items.map((item) => ({ code: item.code, error: result$1.error.message }))
201
+ });
202
+ }
203
+ const errors = parseResponseErrors(result$1.data.errors);
204
+ return result.Result.ok({ success: items.length - errors.length, failed: errors.length, errors });
205
+ };
206
+ var postOrder = async (client, order, orderOptions) => {
207
+ const qs = buildSuppressParams(orderOptions).toString();
208
+ const url = `/api/orders${qs ? `?${qs}` : ""}`;
209
+ const result$1 = await apiCall(
210
+ client.post(url, { data: order })
211
+ );
212
+ if (result.Result.isError(result$1)) return result$1;
213
+ if (result$1.data.errors?.length) {
214
+ const msg = joinMessages(result$1.data.errors);
215
+ return result.Result.error(new Error(`Shoptet API errors: ${msg}`));
216
+ }
217
+ const createdOrder = result$1.data.data?.order;
218
+ if (!createdOrder) {
219
+ return result.Result.error(new Error("Shoptet API returned no order data"));
220
+ }
221
+ return result.Result.ok(createdOrder);
222
+ };
223
+ var createIdResolver = (fetch) => {
224
+ let cached = null;
225
+ return async () => {
226
+ if (cached !== null) return result.Result.ok(cached);
227
+ const result$1 = await fetch();
228
+ if (result.Result.isOk(result$1)) cached = result$1.data;
229
+ return result$1;
230
+ };
231
+ };
232
+ var createShoptetClient = (credentials, options = {}) => {
233
+ validateHttpsUrl("ShoptetClient", credentials.apiUrl, "apiUrl");
234
+ requireNonEmpty("ShoptetClient", credentials.oauthAccessToken, "oauthAccessToken");
235
+ requireNonEmpty("ShoptetClient", credentials.webhookSecret, "webhookSecret");
236
+ const maxBatchSize = resolvePositiveInt("ShoptetClient", options.maxBatchSize, SHOPTET_API_BATCH_LIMIT, "maxBatchSize");
237
+ const timeoutMs = resolvePositiveInt("ShoptetClient", options.timeoutMs, SHOPTET_API_TIMEOUT_MS, "timeoutMs");
238
+ const client = axios__default.default.create({
239
+ baseURL: credentials.apiUrl,
240
+ timeout: timeoutMs,
241
+ headers: {
242
+ "Content-Type": "application/vnd.shoptet.v1.0+json",
243
+ "Shoptet-Private-API-Token": credentials.oauthAccessToken
244
+ }
245
+ });
246
+ client.interceptors.response.use(void 0, (error) => {
247
+ if (axios__default.default.isAxiosError(error)) {
248
+ scrubSensitiveHeaders(error.config?.headers);
249
+ }
250
+ return Promise.reject(error);
251
+ });
252
+ const getDefaultStockId = createIdResolver(() => fetchStockId(client));
253
+ const getDefaultPricelistId = createIdResolver(() => fetchPricelistId(client));
254
+ return {
255
+ validateWebhookSignature: (payload, signature) => checkWebhookSignature(credentials.webhookSecret, payload, signature),
256
+ getOrder: (orderCode) => fetchOrder(client, orderCode),
257
+ getDefaultStockId,
258
+ batchUpdateStock: (items) => updateStock(client, getDefaultStockId, maxBatchSize, items),
259
+ getDefaultPricelistId,
260
+ batchUpdatePrices: (items) => updatePrices(client, getDefaultPricelistId, maxBatchSize, items),
261
+ createOrder: (order, orderOptions = {}) => postOrder(client, order, orderOptions)
262
+ };
263
+ };
264
+
265
+ Object.defineProperty(exports, "Result", {
266
+ enumerable: true,
267
+ get: function () { return result.Result; }
268
+ });
269
+ exports.createShoptetClient = createShoptetClient;
270
+ //# sourceMappingURL=index.cjs.map
271
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"names":["Result","axios","crypto","result"],"mappings":"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","file":"index.cjs","sourcesContent":["import axios, { AxiosInstance, AxiosResponse } from \"axios\";\nimport crypto from \"crypto\";\nimport { Result } from \"@vencav/result\";\nimport type { CreateOrderOptions, CreateOrderRequest, ShoptetApiResponse, ShoptetOrder } from \"./types\";\n\nexport interface ShoptetCredentials {\n readonly oauthAccessToken: string;\n readonly apiUrl: string;\n readonly webhookSecret: string;\n}\n\nexport interface ShoptetClientOptions {\n /** Maximum items per batch request. Shoptet API hard limit is 300. Default: 300. */\n readonly maxBatchSize?: number;\n /** Request timeout in milliseconds. Default: 30000. */\n readonly timeoutMs?: number;\n}\n\nexport type BatchUpdateResult = {\n readonly success: number;\n readonly failed: number;\n readonly errors: ReadonlyArray<{ readonly code: string; readonly error: string }>;\n};\n\nexport type ShoptetClient = ReturnType<typeof createShoptetClient>;\n\nconst SHOPTET_API_BATCH_LIMIT = 300;\nconst SHOPTET_API_TIMEOUT_MS = 30_000;\n\nconst SUPPRESS_FLAGS = [\n \"suppressDocumentGeneration\",\n \"suppressEmailSending\",\n \"suppressProductChecking\",\n \"suppressStockMovements\",\n \"suppressHistoricalMandatoryFields\",\n \"suppressHistoricalPaymentChecking\",\n \"suppressHistoricalShippingChecking\",\n] as const satisfies ReadonlyArray<keyof CreateOrderOptions>;\n\n// ── Validation helpers ──────────────────────────────────────\n\nconst requireNonEmpty = (ctx: string, value: string, name: string): void => {\n if (!value) throw new Error(`${ctx}: ${name} must not be empty`);\n};\n\nconst resolvePositiveInt = (ctx: string, value: number | undefined, fallback: number, name: string): number => {\n const resolved = value ?? fallback;\n if (!Number.isInteger(resolved) || resolved < 1) {\n throw new Error(`${ctx}: ${name} must be a positive integer`);\n }\n return resolved;\n};\n\nconst validateHttpsUrl = (ctx: string, raw: string, fieldName = \"url\"): void => {\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new Error(`${ctx}: ${fieldName} is not a valid URL`);\n }\n if (url.protocol !== \"https:\") throw new Error(`${ctx}: ${fieldName} must use HTTPS`);\n if (url.username || url.password) throw new Error(`${ctx}: ${fieldName} must not contain credentials`);\n};\n\n// ── Core API helper ─────────────────────────────────────────\n\nconst apiCall = async <T>(promise: Promise<AxiosResponse<T>>): Promise<Result<T>> => {\n try {\n return Result.ok((await promise).data);\n } catch (error: unknown) {\n if (axios.isAxiosError(error)) {\n const apiErrors = error.response?.data?.errors;\n if (Array.isArray(apiErrors) && apiErrors.length > 0) {\n const msg = joinMessages(apiErrors as Array<{ message: string }>);\n return Result.error(new Error(`Shoptet API error (${error.response?.status}): ${msg}`));\n }\n return Result.error(new Error(error.message || \"Unknown error\"));\n }\n return Result.error(error instanceof Error ? error : new Error(String(error)));\n }\n};\n\n// ── Shared helpers ──────────────────────────────────────────\n\nconst joinMessages = (errors: ReadonlyArray<{ message: string }>): string =>\n errors.map((e) => e.message).join(\", \");\n\nconst parseResponseErrors = (\n raw: Array<{ instance?: string; message?: string }> | null | undefined\n): Array<{ code: string; error: string }> =>\n (raw ?? []).map((e) => ({ code: e.instance ?? \"unknown\", error: e.message ?? \"Unknown error\" }));\n\nconst buildSuppressParams = (options: CreateOrderOptions): URLSearchParams => {\n const params = new URLSearchParams();\n for (const flag of SUPPRESS_FLAGS) {\n if (options[flag]) params.set(flag, \"true\");\n }\n return params;\n};\n\nconst scrubSensitiveHeaders = (headers: unknown): void => {\n if (!headers || typeof headers !== \"object\") return;\n\n const axiosHeaders = headers as { delete?: (name: string) => void };\n if (typeof axiosHeaders.delete === \"function\") {\n axiosHeaders.delete(\"Shoptet-Private-API-Token\");\n return;\n }\n\n const headerEntries = Object.keys(headers as Record<string, unknown>);\n for (const key of headerEntries) {\n if (key.toLowerCase() === \"shoptet-private-api-token\") {\n delete (headers as Record<string, unknown>)[key];\n }\n }\n};\n\n// ── Pure API functions ──────────────────────────────────────\n\nconst checkWebhookSignature = (secret: string, payload: string, signature: string): boolean => {\n const expected = crypto.createHmac(\"sha1\", secret).update(payload).digest(\"hex\");\n try {\n return crypto.timingSafeEqual(\n Uint8Array.from(Buffer.from(signature, \"hex\")),\n Uint8Array.from(Buffer.from(expected, \"hex\"))\n );\n } catch {\n return false;\n }\n};\n\nconst fetchOrder = async (client: AxiosInstance, orderCode: string): Promise<Result<ShoptetOrder>> => {\n const result = await apiCall(\n client.get<ShoptetApiResponse<{ order: ShoptetOrder }>>(`/api/orders/${encodeURIComponent(orderCode)}`)\n );\n if (Result.isError(result)) return result;\n if (result.data.errors?.length) {\n const msg = joinMessages(result.data.errors);\n return Result.error(new Error(`Shoptet API error: ${msg}`));\n }\n const order = result.data.data?.order;\n if (!order) {\n return Result.error(new Error(\"Shoptet API returned no order data\"));\n }\n return Result.ok(order);\n};\n\nconst fetchStockId = async (client: AxiosInstance): Promise<Result<number>> => {\n const result = await apiCall(\n client.get<{ data: { stocks: Array<{ id: number; title: string }> } }>(\"/api/stocks\")\n );\n if (Result.isError(result)) return result;\n const stocks = result.data.data?.stocks;\n if (!stocks || stocks.length === 0) {\n return Result.error(new Error(\"No stocks (warehouses) found in Shoptet\"));\n }\n return Result.ok(stocks[0].id);\n};\n\nconst fetchPricelistId = async (client: AxiosInstance): Promise<Result<number>> => {\n const result = await apiCall(\n client.get<{\n data: {\n priceLists?: Array<{ id: number; name: string }>;\n pricelists?: Array<{ id: number; name: string }>;\n };\n }>(\"/api/pricelists\")\n );\n if (Result.isError(result)) return result;\n const pricelists = result.data.data?.priceLists ?? result.data.data?.pricelists;\n if (!pricelists || pricelists.length === 0) {\n return Result.error(new Error(\"No pricelists found in Shoptet\"));\n }\n return Result.ok(pricelists[0].id);\n};\n\nconst updateStock = async (\n client: AxiosInstance,\n getStockId: () => Promise<Result<number>>,\n maxBatchSize: number,\n items: Array<{ productCode: string; quantity: number }>\n): Promise<Result<BatchUpdateResult>> => {\n if (items.length === 0) return Result.ok({ success: 0, failed: 0, errors: [] });\n if (items.length > maxBatchSize) {\n return Result.error(new Error(`batchUpdateStock: too many items (${items.length} > ${maxBatchSize})`));\n }\n\n const invalidQuantity = items.find((item) => !Number.isFinite(item.quantity));\n if (invalidQuantity) {\n return Result.error(\n new Error(`batchUpdateStock: invalid quantity for \"${invalidQuantity.productCode}\" (${invalidQuantity.quantity})`)\n );\n }\n\n const stockIdResult = await getStockId();\n if (Result.isError(stockIdResult)) {\n return Result.ok({\n success: 0,\n failed: items.length,\n errors: items.map((item) => ({ code: item.productCode, error: stockIdResult.error.message })),\n });\n }\n\n const result = await apiCall(\n client.patch<{ errors?: Array<{ instance?: string; message?: string }> }>(\n `/api/stocks/${stockIdResult.data}/movements`,\n { data: items.map((item) => ({ productCode: item.productCode, quantity: item.quantity })) }\n )\n );\n\n if (Result.isError(result)) {\n return Result.ok({\n success: 0,\n failed: items.length,\n errors: items.map((item) => ({ code: item.productCode, error: result.error.message })),\n });\n }\n\n const errors = parseResponseErrors(result.data.errors);\n return Result.ok({ success: items.length - errors.length, failed: errors.length, errors });\n};\n\nconst updatePrices = async (\n client: AxiosInstance,\n getPricelistId: () => Promise<Result<number>>,\n maxBatchSize: number,\n items: Array<{ code: string; price: number }>\n): Promise<Result<BatchUpdateResult>> => {\n if (items.length === 0) return Result.ok({ success: 0, failed: 0, errors: [] });\n if (items.length > maxBatchSize) {\n return Result.error(new Error(`batchUpdatePrices: too many items (${items.length} > ${maxBatchSize})`));\n }\n\n const invalidPrice = items.find((item) => !Number.isFinite(item.price) || item.price < 0);\n if (invalidPrice) {\n return Result.error(\n new Error(`batchUpdatePrices: invalid price for \"${invalidPrice.code}\" (${invalidPrice.price})`)\n );\n }\n\n const pricelistIdResult = await getPricelistId();\n if (Result.isError(pricelistIdResult)) {\n return Result.ok({\n success: 0,\n failed: items.length,\n errors: items.map((item) => ({ code: item.code, error: pricelistIdResult.error.message })),\n });\n }\n\n const result = await apiCall(\n client.patch<{ errors?: Array<{ instance?: string; message?: string }> }>(\n `/api/pricelists/${pricelistIdResult.data}`,\n {\n data: items.map((item) => ({\n code: item.code,\n includingVat: true,\n price: { price: item.price.toFixed(2) },\n })),\n }\n )\n );\n\n if (Result.isError(result)) {\n return Result.ok({\n success: 0,\n failed: items.length,\n errors: items.map((item) => ({ code: item.code, error: result.error.message })),\n });\n }\n\n const errors = parseResponseErrors(result.data.errors);\n return Result.ok({ success: items.length - errors.length, failed: errors.length, errors });\n};\n\nconst postOrder = async (\n client: AxiosInstance,\n order: CreateOrderRequest,\n orderOptions: CreateOrderOptions\n): Promise<Result<ShoptetOrder>> => {\n const qs = buildSuppressParams(orderOptions).toString();\n const url = `/api/orders${qs ? `?${qs}` : \"\"}`;\n\n const result = await apiCall(\n client.post<ShoptetApiResponse<{ order: ShoptetOrder }>>(url, { data: order })\n );\n if (Result.isError(result)) return result;\n if (result.data.errors?.length) {\n const msg = joinMessages(result.data.errors);\n return Result.error(new Error(`Shoptet API errors: ${msg}`));\n }\n const createdOrder = result.data.data?.order;\n if (!createdOrder) {\n return Result.error(new Error(\"Shoptet API returned no order data\"));\n }\n return Result.ok(createdOrder);\n};\n\n// ── Caching resolvers ───────────────────────────────────────\n\nconst createIdResolver = (fetch: () => Promise<Result<number>>) => {\n let cached: number | null = null;\n return async (): Promise<Result<number>> => {\n if (cached !== null) return Result.ok(cached);\n const result = await fetch();\n if (Result.isOk(result)) cached = result.data;\n return result;\n };\n};\n\n// ── Factory ─────────────────────────────────────────────────\n\nexport const createShoptetClient = (\n credentials: ShoptetCredentials,\n options: ShoptetClientOptions = {}\n) => {\n validateHttpsUrl(\"ShoptetClient\", credentials.apiUrl, \"apiUrl\");\n requireNonEmpty(\"ShoptetClient\", credentials.oauthAccessToken, \"oauthAccessToken\");\n requireNonEmpty(\"ShoptetClient\", credentials.webhookSecret, \"webhookSecret\");\n\n const maxBatchSize = resolvePositiveInt(\"ShoptetClient\", options.maxBatchSize, SHOPTET_API_BATCH_LIMIT, \"maxBatchSize\");\n const timeoutMs = resolvePositiveInt(\"ShoptetClient\", options.timeoutMs, SHOPTET_API_TIMEOUT_MS, \"timeoutMs\");\n\n const client = axios.create({\n baseURL: credentials.apiUrl,\n timeout: timeoutMs,\n headers: {\n \"Content-Type\": \"application/vnd.shoptet.v1.0+json\",\n \"Shoptet-Private-API-Token\": credentials.oauthAccessToken,\n },\n });\n\n client.interceptors.response.use(undefined, (error: unknown) => {\n if (axios.isAxiosError(error)) {\n scrubSensitiveHeaders(error.config?.headers);\n }\n return Promise.reject(error);\n });\n\n const getDefaultStockId = createIdResolver(() => fetchStockId(client));\n const getDefaultPricelistId = createIdResolver(() => fetchPricelistId(client));\n\n return {\n validateWebhookSignature: (payload: string, signature: string) =>\n checkWebhookSignature(credentials.webhookSecret, payload, signature),\n getOrder: (orderCode: string) =>\n fetchOrder(client, orderCode),\n getDefaultStockId,\n batchUpdateStock: (items: Array<{ productCode: string; quantity: number }>) =>\n updateStock(client, getDefaultStockId, maxBatchSize, items),\n getDefaultPricelistId,\n batchUpdatePrices: (items: Array<{ code: string; price: number }>) =>\n updatePrices(client, getDefaultPricelistId, maxBatchSize, items),\n createOrder: (order: CreateOrderRequest, orderOptions: CreateOrderOptions = {}) =>\n postOrder(client, order, orderOptions),\n };\n};\n"]}