@atomic-solutions/woocommerce-api-client 0.2.0 → 0.3.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,2103 @@
1
+ 'use strict';
2
+
3
+ var axios = require('axios');
4
+ var zod = require('zod');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var axios__default = /*#__PURE__*/_interopDefault(axios);
9
+
10
+ // src/client/createClient.ts
11
+
12
+ // src/errors/classes/BaseError.ts
13
+ var BaseError = class extends Error {
14
+ constructor(options) {
15
+ super(options.message);
16
+ this.options = options;
17
+ Object.setPrototypeOf(this, new.target.prototype);
18
+ this.name = this.constructor.name;
19
+ if (Error.captureStackTrace) {
20
+ Error.captureStackTrace(this, this.constructor);
21
+ }
22
+ if (this.options.cause instanceof Error && this.options.cause.stack) {
23
+ this.stack = `${this.stack}
24
+ Caused by: ${this.options.cause.stack}`;
25
+ }
26
+ }
27
+ /**
28
+ * Get reportable data for error tracking/logging
29
+ * Subclasses should override this to add their own reportable fields
30
+ */
31
+ getReportableData() {
32
+ const { code, operation, userMessage, retryable } = this.options;
33
+ return {
34
+ code,
35
+ operation,
36
+ userMessage,
37
+ retryable
38
+ };
39
+ }
40
+ /**
41
+ * Custom JSON serialization
42
+ * Makes the error properly serializable for logging/reporting
43
+ */
44
+ toJSON() {
45
+ return {
46
+ ...this.getReportableData(),
47
+ name: this.name,
48
+ message: this.message,
49
+ stack: this.stack
50
+ };
51
+ }
52
+ };
53
+
54
+ // src/errors/mapping/mapWooCommerceCode.ts
55
+ var mapWooCommerceCode = (apiCode) => {
56
+ const directMatches = {
57
+ woocommerce_rest_cart_empty: "cart_empty",
58
+ woocommerce_rest_cart_coupon_error: "invalid_coupon",
59
+ woocommerce_rest_missing_nonce: "missing_nonce",
60
+ woocommerce_rest_invalid_nonce: "invalid_nonce",
61
+ rest_invalid_param: "invalid_params",
62
+ woocommerce_rest_product_invalid_id: "product_not_found",
63
+ woocommerce_rest_product_out_of_stock: "out_of_stock",
64
+ woocommerce_rest_product_not_enough_stock: "insufficient_stock",
65
+ woocommerce_rest_checkout_invalid_payment_method: "invalid_payment_method"
66
+ };
67
+ if (directMatches[apiCode]) {
68
+ return directMatches[apiCode];
69
+ }
70
+ if (apiCode.includes("nonce")) return "invalid_nonce";
71
+ if (apiCode.includes("coupon")) return "invalid_coupon";
72
+ if (apiCode.includes("stock")) return "insufficient_stock";
73
+ if (apiCode.includes("checkout")) return "checkout_error";
74
+ if (apiCode.includes("payment")) return "invalid_payment_method";
75
+ return "unknown_error";
76
+ };
77
+
78
+ // src/errors/mapping/constants.ts
79
+ var WOOCOMMERCE_ERROR_METADATA = {
80
+ cart_empty: {
81
+ operation: "get_cart",
82
+ retryable: false,
83
+ userMessage: "Your cart is empty"
84
+ },
85
+ invalid_coupon: {
86
+ operation: "apply_coupon",
87
+ retryable: false,
88
+ userMessage: "Invalid coupon code"
89
+ },
90
+ missing_nonce: {
91
+ operation: "cart_request",
92
+ retryable: true
93
+ // Auto-retry with new nonce
94
+ },
95
+ invalid_nonce: {
96
+ operation: "cart_request",
97
+ retryable: true
98
+ // Auto-retry with new nonce
99
+ },
100
+ invalid_params: {
101
+ operation: "api_request",
102
+ retryable: false,
103
+ userMessage: "Invalid request parameters"
104
+ },
105
+ product_not_found: {
106
+ operation: "get_product",
107
+ retryable: false,
108
+ userMessage: "Product not found"
109
+ },
110
+ out_of_stock: {
111
+ operation: "add_to_cart",
112
+ retryable: false,
113
+ userMessage: "This product is out of stock"
114
+ },
115
+ insufficient_stock: {
116
+ operation: "add_to_cart",
117
+ retryable: false,
118
+ userMessage: "Not enough stock available"
119
+ },
120
+ invalid_payment_method: {
121
+ operation: "process_checkout",
122
+ retryable: false,
123
+ userMessage: "Invalid payment method"
124
+ },
125
+ checkout_error: {
126
+ operation: "process_checkout",
127
+ retryable: false
128
+ },
129
+ network_error: {
130
+ operation: "network_request",
131
+ retryable: true
132
+ },
133
+ unknown_error: {
134
+ operation: "unknown",
135
+ retryable: false
136
+ }
137
+ };
138
+
139
+ // src/errors/classes/WooCommerceApiError.ts
140
+ var WooCommerceApiError = class extends BaseError {
141
+ constructor(options) {
142
+ const metadata = WOOCOMMERCE_ERROR_METADATA[options.code] ?? {
143
+ operation: "woocommerce_api_request",
144
+ retryable: false
145
+ };
146
+ const enrichedOptions = {
147
+ ...options,
148
+ operation: options.operation || metadata.operation,
149
+ userMessage: options.userMessage || metadata.userMessage,
150
+ retryable: options.retryable ?? metadata.retryable
151
+ };
152
+ super(enrichedOptions);
153
+ }
154
+ // Convenient getters for frequently accessed fields (no duplication)
155
+ get wooCode() {
156
+ return this.options.code;
157
+ }
158
+ get originalCode() {
159
+ return this.options.originalCode;
160
+ }
161
+ get statusCode() {
162
+ return this.options.statusCode;
163
+ }
164
+ get url() {
165
+ return this.options.url;
166
+ }
167
+ get method() {
168
+ return this.options.method;
169
+ }
170
+ get requestBody() {
171
+ return this.options.requestBody;
172
+ }
173
+ get responseBody() {
174
+ return this.options.responseBody;
175
+ }
176
+ get data() {
177
+ return this.options.data;
178
+ }
179
+ /**
180
+ * Get reportable data for error tracking/logging
181
+ * Includes HTTP context and WooCommerce-specific fields
182
+ */
183
+ getReportableData() {
184
+ return {
185
+ code: this.options.code,
186
+ operation: this.options.operation,
187
+ userMessage: this.options.userMessage,
188
+ retryable: this.options.retryable,
189
+ statusCode: this.options.statusCode,
190
+ url: this.options.url,
191
+ method: this.options.method,
192
+ wooCode: this.options.code,
193
+ originalCode: this.options.originalCode
194
+ };
195
+ }
196
+ };
197
+
198
+ // src/errors/classes/WooCommerceDataValidationError.ts
199
+ var WooCommerceDataValidationError = class _WooCommerceDataValidationError extends BaseError {
200
+ constructor(options) {
201
+ const fieldErrors = options.zodError ? _WooCommerceDataValidationError.extractFieldErrorsFromZod(options.zodError) : void 0;
202
+ super({
203
+ code: "validation_error",
204
+ message: options.message,
205
+ operation: options.operation,
206
+ userMessage: options.userMessage || "Received unexpected data from WooCommerce",
207
+ retryable: false,
208
+ cause: options.cause,
209
+ url: options.url,
210
+ zodError: options.zodError,
211
+ value: options.value,
212
+ fieldErrors
213
+ });
214
+ }
215
+ // Convenient getters for validation-specific fields (no duplication)
216
+ get url() {
217
+ return this.options.url;
218
+ }
219
+ get zodError() {
220
+ return this.options.zodError;
221
+ }
222
+ get invalidValue() {
223
+ return this.options.value;
224
+ }
225
+ get fieldErrors() {
226
+ return this.options.fieldErrors;
227
+ }
228
+ /**
229
+ * Get reportable data for error tracking/logging
230
+ * Includes validation-specific fields
231
+ */
232
+ getReportableData() {
233
+ return {
234
+ code: this.options.code,
235
+ operation: this.options.operation,
236
+ userMessage: this.options.userMessage,
237
+ retryable: false,
238
+ fieldErrors: this.options.fieldErrors,
239
+ url: this.options.url
240
+ };
241
+ }
242
+ /**
243
+ * Extract field errors from Zod error
244
+ */
245
+ static extractFieldErrorsFromZod(zodError) {
246
+ if (!zodError?.errors) {
247
+ return void 0;
248
+ }
249
+ const fieldErrors = {};
250
+ for (const issue of zodError.errors) {
251
+ const path = issue.path.join(".");
252
+ if (!fieldErrors[path]) {
253
+ fieldErrors[path] = [];
254
+ }
255
+ fieldErrors[path].push(issue.message);
256
+ }
257
+ return fieldErrors;
258
+ }
259
+ /**
260
+ * Get all error messages as a flat array
261
+ */
262
+ getMessages() {
263
+ if (!this.fieldErrors) {
264
+ return [this.message];
265
+ }
266
+ const messages = [];
267
+ for (const fieldMessages of Object.values(this.fieldErrors)) {
268
+ messages.push(...fieldMessages);
269
+ }
270
+ return messages;
271
+ }
272
+ /**
273
+ * Check if a specific field has errors
274
+ */
275
+ hasFieldError(field) {
276
+ return this.fieldErrors ? field in this.fieldErrors : false;
277
+ }
278
+ /**
279
+ * Get error messages for a specific field
280
+ */
281
+ getFieldError(field) {
282
+ return this.fieldErrors?.[field];
283
+ }
284
+ };
285
+
286
+ // src/http/handleApiResponse.ts
287
+ var handleApiResponse = (response, schema, options) => {
288
+ const validationMode = options?.validationMode ?? "strict";
289
+ if (validationMode === "warn") {
290
+ const result = schema.safeParse(response.data);
291
+ if (!result.success) {
292
+ const validationError = new WooCommerceDataValidationError({
293
+ message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
294
+ url: response.config.url,
295
+ zodError: result.error,
296
+ value: response.data,
297
+ userMessage: "Received unexpected data from WooCommerce"
298
+ });
299
+ options?.onValidationError?.(validationError);
300
+ options?.errorReporter?.(validationError);
301
+ console.warn("[woocommerce-utils] Validation warning:", validationError.message);
302
+ return response.data;
303
+ }
304
+ return result.data;
305
+ }
306
+ try {
307
+ return schema.parse(response.data);
308
+ } catch (error2) {
309
+ if (error2 && typeof error2 === "object" && "issues" in error2) {
310
+ const validationError = new WooCommerceDataValidationError({
311
+ message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
312
+ url: response.config.url,
313
+ zodError: error2,
314
+ value: response.data,
315
+ userMessage: "Received unexpected data from WooCommerce"
316
+ });
317
+ options?.onValidationError?.(validationError);
318
+ options?.errorReporter?.(validationError);
319
+ throw validationError;
320
+ }
321
+ throw error2;
322
+ }
323
+ };
324
+
325
+ // src/utils/calculatePagination.ts
326
+ var calculatePagination = (input) => {
327
+ const { page, perPage, total, totalPages } = input;
328
+ const hasNextPage = page < totalPages;
329
+ const hasPrevPage = page > 1;
330
+ return {
331
+ page,
332
+ perPage,
333
+ total,
334
+ totalPages,
335
+ hasNextPage,
336
+ hasPrevPage,
337
+ nextPage: hasNextPage ? page + 1 : null,
338
+ prevPage: hasPrevPage ? page - 1 : null
339
+ };
340
+ };
341
+
342
+ // src/utils/getPaginationMeta.ts
343
+ var getPaginationMeta = (headers) => {
344
+ if (!headers) {
345
+ return {
346
+ total: 0,
347
+ totalPages: 0
348
+ };
349
+ }
350
+ const wpTotal = headers["x-wp-total"];
351
+ const wpTotalPages = headers["x-wp-totalpages"];
352
+ if (!wpTotal || !wpTotalPages) {
353
+ return {
354
+ total: 0,
355
+ totalPages: 0
356
+ };
357
+ }
358
+ const total = parseInt(wpTotal, 10);
359
+ const totalPages = parseInt(wpTotalPages, 10);
360
+ return {
361
+ total,
362
+ totalPages
363
+ };
364
+ };
365
+
366
+ // src/http/handlePaginatedApiResponse.ts
367
+ var handlePaginatedApiResponse = (response, schema, params = {}, options) => {
368
+ const validationMode = options?.validationMode ?? "strict";
369
+ let data;
370
+ if (validationMode === "warn") {
371
+ const result = schema.safeParse(response.data);
372
+ if (!result.success) {
373
+ const validationError = new WooCommerceDataValidationError({
374
+ message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
375
+ url: response.config.url,
376
+ zodError: result.error,
377
+ value: response.data,
378
+ userMessage: "Received unexpected data from WooCommerce"
379
+ });
380
+ options?.onValidationError?.(validationError);
381
+ options?.errorReporter?.(validationError);
382
+ console.warn("[woocommerce-utils] Validation warning:", validationError.message);
383
+ data = Array.isArray(response.data) ? response.data : [];
384
+ } else {
385
+ data = result.data;
386
+ }
387
+ } else {
388
+ try {
389
+ data = schema.parse(response.data);
390
+ } catch (error2) {
391
+ if (error2 && typeof error2 === "object" && "issues" in error2) {
392
+ const validationError = new WooCommerceDataValidationError({
393
+ message: `WooCommerce API response validation failed for ${response.config.url || "unknown endpoint"}`,
394
+ url: response.config.url,
395
+ zodError: error2,
396
+ value: response.data,
397
+ userMessage: "Received unexpected data from WooCommerce"
398
+ });
399
+ options?.onValidationError?.(validationError);
400
+ options?.errorReporter?.(validationError);
401
+ throw validationError;
402
+ }
403
+ throw error2;
404
+ }
405
+ }
406
+ const { total, totalPages } = getPaginationMeta(response.headers);
407
+ const page = params.page || 1;
408
+ const perPage = params.per_page || 10;
409
+ const pagination = calculatePagination({
410
+ page,
411
+ perPage,
412
+ total,
413
+ totalPages
414
+ });
415
+ return {
416
+ data,
417
+ pagination
418
+ };
419
+ };
420
+
421
+ // src/logging/logger.ts
422
+ var DEBUG_PREFIX = "[woocommerce-utils]";
423
+ var isDebugEnabled = () => {
424
+ if (typeof process !== "undefined" && process.env) {
425
+ return process.env.DEBUG === "true" || process.env.DEBUG === "1";
426
+ }
427
+ return false;
428
+ };
429
+ var debug = (...args) => {
430
+ if (isDebugEnabled()) {
431
+ console.log(DEBUG_PREFIX, ...args);
432
+ }
433
+ };
434
+ var error = (...args) => {
435
+ console.error(DEBUG_PREFIX, ...args);
436
+ };
437
+
438
+ // src/http/interceptors/request.ts
439
+ var requiresAuth = (url) => {
440
+ if (!url) return false;
441
+ const authEndpoints = [
442
+ "/jwt-auth/",
443
+ "/customers/me",
444
+ "/wp/v2/users/me",
445
+ // Store API non-cart endpoints: inject JWT when available so authenticated
446
+ // users can access their own resources (e.g. orders, personalised product data)
447
+ "/wc/store/v1/order"
448
+ ];
449
+ return authEndpoints.some((endpoint) => url.includes(endpoint));
450
+ };
451
+ var requiresCartAuth = (url) => {
452
+ if (!url) return false;
453
+ return /\/wc\/store\/(v\d+\/)?cart/.test(url) || /\/wc\/store\/(v\d+\/)?checkout/.test(url);
454
+ };
455
+ var setupRequestInterceptor = (axiosInstance, config, client) => {
456
+ axiosInstance.interceptors.request.use(
457
+ async (requestConfig) => {
458
+ const url = requestConfig.url;
459
+ if (config.jwtToken) {
460
+ const token = await config.jwtToken.get();
461
+ if (token && (requiresAuth(url) || requiresCartAuth(url))) {
462
+ requestConfig.headers.Authorization = `Bearer ${token}`;
463
+ if (config.debug) {
464
+ debug("Injected JWT token for", url);
465
+ }
466
+ }
467
+ }
468
+ if (requiresCartAuth(url)) {
469
+ const headers = await config.cartHeaders.get();
470
+ const isAuthenticated = !!await config.jwtToken?.get();
471
+ if (headers.nonce) {
472
+ requestConfig.headers.Nonce = headers.nonce;
473
+ }
474
+ if (headers.cartToken && !isAuthenticated) {
475
+ requestConfig.headers["Cart-Token"] = headers.cartToken;
476
+ }
477
+ requestConfig.headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
478
+ requestConfig.headers.Pragma = "no-cache";
479
+ requestConfig.headers.Expires = "0";
480
+ if (config.debug) {
481
+ debug("Injected cart headers for", url, {
482
+ hasNonce: !!headers.nonce,
483
+ hasCartToken: !!headers.cartToken
484
+ });
485
+ }
486
+ }
487
+ if (config.debug) {
488
+ debug(
489
+ `${requestConfig.method?.toUpperCase()} ${requestConfig.url}`,
490
+ requestConfig.params || requestConfig.data
491
+ );
492
+ }
493
+ if (config.onRequest) {
494
+ return config.onRequest(requestConfig, client);
495
+ }
496
+ return requestConfig;
497
+ },
498
+ (error2) => {
499
+ return Promise.reject(error2);
500
+ }
501
+ );
502
+ };
503
+
504
+ // src/http/interceptors/response.ts
505
+ var isCartResponse = (url) => {
506
+ if (!url) return false;
507
+ return /\/wc\/store\/(v\d+\/)?cart/.test(url) || /\/wc\/store\/(v\d+\/)?checkout/.test(url);
508
+ };
509
+ var setupResponseInterceptor = (axiosInstance, config, client) => {
510
+ axiosInstance.interceptors.response.use(
511
+ async (response) => {
512
+ if (isCartResponse(response.config.url)) {
513
+ const nonce = response.headers["nonce"] || response.headers["Nonce"];
514
+ const cartToken = response.headers["cart-token"] || response.headers["Cart-Token"];
515
+ if (nonce || cartToken) {
516
+ await config.cartHeaders.save({
517
+ nonce: nonce || void 0,
518
+ cartToken: cartToken || void 0
519
+ });
520
+ if (config.debug) {
521
+ debug(
522
+ "Saved updated cart headers:",
523
+ response.config.url,
524
+ "nonce:",
525
+ !!nonce,
526
+ "cartToken:",
527
+ !!cartToken
528
+ );
529
+ }
530
+ }
531
+ }
532
+ if (config.debug) {
533
+ debug("Response:", response.config.url, response.status);
534
+ }
535
+ if (config.onResponse) {
536
+ return config.onResponse(response, client);
537
+ }
538
+ return response;
539
+ },
540
+ (error2) => {
541
+ return Promise.reject(error2);
542
+ }
543
+ );
544
+ };
545
+
546
+ // src/utils/initializeCartSession.ts
547
+ var initializeCartSession = async (client, cartHeaders) => {
548
+ try {
549
+ debug("session", "Initializing cart session");
550
+ const response = await client.get("/wc/store/v1/cart");
551
+ const nonce = response.headers["nonce"] || response.headers["Nonce"];
552
+ const cartToken = response.headers["cart-token"] || response.headers["Cart-Token"];
553
+ if (nonce || cartToken) {
554
+ await cartHeaders.save({
555
+ nonce: nonce || void 0,
556
+ cartToken: cartToken || void 0
557
+ });
558
+ debug("session", "Cart session initialized", {
559
+ hasNonce: !!nonce,
560
+ hasCartToken: !!cartToken
561
+ });
562
+ }
563
+ } catch (error2) {
564
+ debug("session", "Failed to initialize cart session", error2);
565
+ throw error2;
566
+ }
567
+ };
568
+
569
+ // src/http/interceptors/error.ts
570
+ var setupErrorInterceptor = (axiosInstance, config, client) => {
571
+ axiosInstance.interceptors.response.use(
572
+ (response) => response,
573
+ async (axiosError) => {
574
+ const status = axiosError.response?.status || 500;
575
+ const errorData = axiosError.response?.data;
576
+ const apiCode = errorData?.code || "unknown_error";
577
+ const wpMessage = errorData?.message || axiosError.message || "Unknown error";
578
+ const errorCode = mapWooCommerceCode(apiCode);
579
+ const errorMetadata = WOOCOMMERCE_ERROR_METADATA[errorCode];
580
+ const wcError = new WooCommerceApiError({
581
+ message: wpMessage,
582
+ statusCode: status,
583
+ code: errorCode,
584
+ // Transformed app-level code
585
+ originalCode: apiCode,
586
+ // Original WooCommerce API code
587
+ retryable: errorMetadata.retryable,
588
+ userMessage: errorMetadata.userMessage,
589
+ operation: errorMetadata.operation,
590
+ url: axiosError.config?.url || "",
591
+ method: axiosError.config?.method?.toUpperCase() || "GET",
592
+ requestBody: axiosError.config?.data,
593
+ responseBody: errorData,
594
+ data: errorData?.data,
595
+ cause: axiosError
596
+ });
597
+ if (config.debug) {
598
+ debug("WooCommerce error:", apiCode, status, wpMessage);
599
+ error(wcError);
600
+ }
601
+ if ((status === 401 || status === 403) && config.onAuthError && axios.isAxiosError(axiosError) && !axiosError.config?._retry) {
602
+ if (config.debug) {
603
+ debug("Auth error detected, calling onAuthError handler");
604
+ }
605
+ try {
606
+ const shouldRetry = await config.onAuthError(wcError, client);
607
+ if (shouldRetry) {
608
+ if (config.debug) {
609
+ debug("Retrying request after auth error");
610
+ }
611
+ axiosError.config._retry = true;
612
+ return axiosInstance.request(axiosError.config);
613
+ }
614
+ } catch (authErrorHandlerError) {
615
+ if (config.debug) {
616
+ debug("onAuthError handler threw error", authErrorHandlerError);
617
+ }
618
+ }
619
+ }
620
+ if (config.onError) {
621
+ config.onError(wcError, client);
622
+ }
623
+ const isNonceError = errorCode === "missing_nonce" || errorCode === "invalid_nonce";
624
+ if (isNonceError && axios.isAxiosError(axiosError) && !axiosError.config?._retry) {
625
+ if (config.debug) {
626
+ debug("Nonce error detected, retrying with new session");
627
+ }
628
+ try {
629
+ await initializeCartSession(axiosInstance, config.cartHeaders);
630
+ axiosError.config._retry = true;
631
+ return axiosInstance.request(axiosError.config);
632
+ } catch (_retryError) {
633
+ config.errorReporter?.report(wcError);
634
+ return Promise.reject(wcError);
635
+ }
636
+ }
637
+ config.errorReporter?.report(wcError);
638
+ return Promise.reject(wcError);
639
+ }
640
+ );
641
+ };
642
+ var addressSchema = zod.z.object({
643
+ /** First name */
644
+ first_name: zod.z.string().min(1, "First name is required"),
645
+ /** Last name */
646
+ last_name: zod.z.string().min(1, "Last name is required"),
647
+ /** Company name (optional) */
648
+ company: zod.z.string(),
649
+ /** Address line 1 */
650
+ address_1: zod.z.string().min(1, "Address is required"),
651
+ /** Address line 2 (optional) */
652
+ address_2: zod.z.string(),
653
+ /** City */
654
+ city: zod.z.string().min(1, "City is required"),
655
+ /** State/Province/Region */
656
+ state: zod.z.string(),
657
+ /** Postal code */
658
+ postcode: zod.z.string().min(1, "Postal code is required"),
659
+ /** Country code (ISO 3166-1 alpha-2) */
660
+ country: zod.z.string(),
661
+ /** Phone number */
662
+ phone: zod.z.string().min(1, "Phone number is required")
663
+ });
664
+ var addressSchemaNonMandatory = zod.z.object({
665
+ /** First name */
666
+ first_name: zod.z.string(),
667
+ /** Last name */
668
+ last_name: zod.z.string(),
669
+ /** Company name (optional) */
670
+ company: zod.z.string(),
671
+ /** Address line 1 */
672
+ address_1: zod.z.string(),
673
+ /** Address line 2 (optional) */
674
+ address_2: zod.z.string(),
675
+ /** City */
676
+ city: zod.z.string(),
677
+ /** State/Province/Region */
678
+ state: zod.z.string(),
679
+ /** Postal code */
680
+ postcode: zod.z.string(),
681
+ /** Country code (ISO 3166-1 alpha-2) */
682
+ country: zod.z.string(),
683
+ /** Phone number */
684
+ phone: zod.z.string()
685
+ });
686
+ var billingAddressSchemaMandatory = addressSchema.extend({
687
+ /** Email address (required for billing) */
688
+ email: zod.z.string().email("Valid email is required")
689
+ });
690
+ var billingAddressSchemaNonMandatory = addressSchemaNonMandatory.extend({
691
+ /** Email address (can be null for guest carts) */
692
+ email: zod.z.string().nullable()
693
+ });
694
+ var moneySchema = zod.z.object({
695
+ /** Currency code (e.g., 'USD', 'EUR', 'BAM') */
696
+ currency_code: zod.z.string(),
697
+ /** Currency symbol (e.g., '$', '€', 'KM') */
698
+ currency_symbol: zod.z.string(),
699
+ /** Number of decimal places (e.g., 2 for dollars/euros) */
700
+ currency_minor_unit: zod.z.number(),
701
+ /** Decimal separator character (e.g., '.', ',') */
702
+ currency_decimal_separator: zod.z.string(),
703
+ /** Thousands separator character (e.g., ',', '.') */
704
+ currency_thousand_separator: zod.z.string(),
705
+ /** Currency symbol prefix (empty if symbol is suffix) */
706
+ currency_prefix: zod.z.string(),
707
+ /** Currency symbol suffix (empty if symbol is prefix) */
708
+ currency_suffix: zod.z.string()
709
+ });
710
+ var productImageSchema = zod.z.object({
711
+ /** Alternative text for the image */
712
+ alt: zod.z.string(),
713
+ /** Image unique identifier */
714
+ id: zod.z.number(),
715
+ /** Image filename */
716
+ name: zod.z.string(),
717
+ /** Available image sizes in JSON format */
718
+ sizes: zod.z.string(),
719
+ /** Full-size image URL */
720
+ src: zod.z.string().url(),
721
+ /** Srcset attribute for responsive images */
722
+ srcset: zod.z.string(),
723
+ /** Thumbnail image URL */
724
+ thumbnail: zod.z.string().url()
725
+ });
726
+ var cartItemImageSchema = zod.z.object({
727
+ /** Image ID */
728
+ id: zod.z.number(),
729
+ /** Full image URL */
730
+ src: zod.z.string(),
731
+ /** Thumbnail URL */
732
+ thumbnail: zod.z.string(),
733
+ /** Responsive image srcset */
734
+ srcset: zod.z.string(),
735
+ /** Responsive image sizes */
736
+ sizes: zod.z.string(),
737
+ /** Image name */
738
+ name: zod.z.string(),
739
+ /** Image alt text */
740
+ alt: zod.z.string()
741
+ });
742
+ var pricesSchema = zod.z.object({
743
+ /** Current active price (sale price if on sale, otherwise regular) */
744
+ price: zod.z.string(),
745
+ /** Regular price (before any discounts) */
746
+ regular_price: zod.z.string(),
747
+ /** Sale price (empty string if not on sale) */
748
+ sale_price: zod.z.string(),
749
+ /** Price range for variable products (null for simple products) */
750
+ price_range: zod.z.object({
751
+ min_amount: zod.z.string(),
752
+ max_amount: zod.z.string()
753
+ }).nullable(),
754
+ /** Raw price data without formatting */
755
+ raw_prices: zod.z.object({
756
+ /** Decimal precision */
757
+ precision: zod.z.number(),
758
+ /** Raw price value */
759
+ price: zod.z.string(),
760
+ /** Raw regular price value */
761
+ regular_price: zod.z.string(),
762
+ /** Raw sale price value */
763
+ sale_price: zod.z.string()
764
+ }).optional()
765
+ }).merge(moneySchema);
766
+
767
+ // ../schemas/src/woocommerce/store-api/cart-item.schema.ts
768
+ var itemTotalsSchema = zod.z.object({
769
+ /** Subtotal before taxes */
770
+ line_subtotal: zod.z.string(),
771
+ /** Tax amount on subtotal */
772
+ line_subtotal_tax: zod.z.string(),
773
+ /** Total after discounts, before taxes */
774
+ line_total: zod.z.string(),
775
+ /** Tax amount on total */
776
+ line_total_tax: zod.z.string()
777
+ }).merge(moneySchema);
778
+ var cartItemSchema = zod.z.object({
779
+ /** Unique cart item key (use this for update/remove operations) */
780
+ key: zod.z.string(),
781
+ /** Product ID */
782
+ id: zod.z.number(),
783
+ /** Quantity of this item in cart */
784
+ quantity: zod.z.number(),
785
+ /** Quantity limits for this product */
786
+ quantity_limits: zod.z.object({
787
+ /** Minimum quantity allowed */
788
+ minimum: zod.z.number(),
789
+ /** Maximum quantity allowed */
790
+ maximum: zod.z.number(),
791
+ /** Quantity must be multiple of this value */
792
+ multiple_of: zod.z.number(),
793
+ /** Whether quantity can be changed */
794
+ editable: zod.z.boolean()
795
+ }),
796
+ /** Product name */
797
+ name: zod.z.string(),
798
+ /** Short product description */
799
+ short_description: zod.z.string(),
800
+ /** Full product description */
801
+ description: zod.z.string(),
802
+ /** Product SKU */
803
+ sku: zod.z.string(),
804
+ /** Remaining stock count if low (null if not low or unlimited) */
805
+ low_stock_remaining: zod.z.number().nullable(),
806
+ /** Whether backorders are allowed */
807
+ backorders_allowed: zod.z.boolean(),
808
+ /** Whether to show backorder badge */
809
+ show_backorder_badge: zod.z.boolean(),
810
+ /** Whether product can only be purchased individually */
811
+ sold_individually: zod.z.boolean(),
812
+ /** Product permalink URL */
813
+ permalink: zod.z.string(),
814
+ /** Product images */
815
+ images: zod.z.array(cartItemImageSchema),
816
+ /** Variation attributes (for variable products) */
817
+ variation: zod.z.array(
818
+ zod.z.object({
819
+ /** Attribute name (e.g., 'Color', 'Size') */
820
+ attribute: zod.z.string(),
821
+ /** Selected value (e.g., 'Red', 'Large') */
822
+ value: zod.z.string()
823
+ })
824
+ ),
825
+ /** Additional item data/metadata */
826
+ item_data: zod.z.array(
827
+ zod.z.object({
828
+ key: zod.z.string(),
829
+ value: zod.z.string()
830
+ })
831
+ ),
832
+ /** Product pricing information */
833
+ prices: pricesSchema,
834
+ /** Line item totals */
835
+ totals: itemTotalsSchema,
836
+ /** Catalog visibility setting */
837
+ catalog_visibility: zod.z.string(),
838
+ /** Product type */
839
+ type: zod.z.enum(["simple", "variable", "grouped", "external"]),
840
+ /** Extension data from plugins */
841
+ extensions: zod.z.record(zod.z.string(), zod.z.unknown())
842
+ });
843
+ var cartTotalsSchema = zod.z.object({
844
+ /** Total of all cart items (before discounts and taxes) */
845
+ total_items: zod.z.string(),
846
+ /** Tax on cart items */
847
+ total_items_tax: zod.z.string(),
848
+ /** Total fees */
849
+ total_fees: zod.z.string(),
850
+ /** Tax on fees */
851
+ total_fees_tax: zod.z.string(),
852
+ /** Total discount amount from coupons */
853
+ total_discount: zod.z.string(),
854
+ /** Tax on discounts */
855
+ total_discount_tax: zod.z.string(),
856
+ /** Shipping cost (null if not calculated yet) */
857
+ total_shipping: zod.z.string().nullable(),
858
+ /** Shipping tax (null if not calculated yet) */
859
+ total_shipping_tax: zod.z.string().nullable(),
860
+ /** Final total price (includes all taxes and fees) */
861
+ total_price: zod.z.string(),
862
+ /** Total tax amount */
863
+ total_tax: zod.z.string(),
864
+ /** Tax breakdown by rate */
865
+ tax_lines: zod.z.array(
866
+ zod.z.object({
867
+ /** Tax rate name */
868
+ name: zod.z.string(),
869
+ /** Tax amount */
870
+ price: zod.z.string(),
871
+ /** Tax rate percentage */
872
+ rate: zod.z.string()
873
+ })
874
+ )
875
+ }).merge(moneySchema);
876
+ var cartCouponSchema = zod.z.object({
877
+ /** Coupon code */
878
+ code: zod.z.string(),
879
+ /** Discount totals for this coupon */
880
+ totals: zod.z.object({
881
+ /** Total discount amount (before tax) */
882
+ total_discount: zod.z.string(),
883
+ /** Tax amount on discount */
884
+ total_discount_tax: zod.z.string()
885
+ }).merge(moneySchema)
886
+ });
887
+ var shippingRateSchema = zod.z.object({
888
+ /** Unique rate identifier (format: instance_id:method_id) */
889
+ rate_id: zod.z.string(),
890
+ /** Shipping method name */
891
+ name: zod.z.string(),
892
+ /** Shipping method description */
893
+ description: zod.z.string(),
894
+ /** Estimated delivery time */
895
+ delivery_time: zod.z.string(),
896
+ /** Shipping cost */
897
+ price: zod.z.string(),
898
+ /** Shipping instance ID */
899
+ instance_id: zod.z.number(),
900
+ /** Shipping method ID */
901
+ method_id: zod.z.string(),
902
+ /** Additional metadata for this shipping method */
903
+ meta_data: zod.z.array(
904
+ zod.z.object({
905
+ key: zod.z.string(),
906
+ value: zod.z.string()
907
+ })
908
+ ),
909
+ /** Whether this rate is currently selected */
910
+ selected: zod.z.boolean(),
911
+ /** Currency code for the price */
912
+ currency_code: zod.z.string(),
913
+ /** Currency symbol for the price */
914
+ currency_symbol: zod.z.string()
915
+ });
916
+ var shippingPackageSchema = zod.z.object({
917
+ /** Package identifier (0-indexed) */
918
+ package_id: zod.z.number(),
919
+ /** Package name/label */
920
+ name: zod.z.string(),
921
+ /** Shipping destination address */
922
+ destination: zod.z.object({
923
+ address_1: zod.z.string(),
924
+ address_2: zod.z.string().optional(),
925
+ city: zod.z.string(),
926
+ state: zod.z.string(),
927
+ postcode: zod.z.string(),
928
+ country: zod.z.string()
929
+ }),
930
+ /** Items included in this package */
931
+ items: zod.z.array(
932
+ zod.z.object({
933
+ /** Cart item key */
934
+ key: zod.z.string(),
935
+ /** Product name */
936
+ name: zod.z.string(),
937
+ /** Quantity in package */
938
+ quantity: zod.z.number()
939
+ })
940
+ ),
941
+ /** Available shipping rates for this package */
942
+ shipping_rates: zod.z.array(shippingRateSchema)
943
+ });
944
+
945
+ // ../schemas/src/woocommerce/store-api/cart.ts
946
+ var cartSchema = zod.z.object({
947
+ items: zod.z.array(cartItemSchema),
948
+ items_count: zod.z.number(),
949
+ items_weight: zod.z.number(),
950
+ needs_payment: zod.z.boolean(),
951
+ needs_shipping: zod.z.boolean(),
952
+ payment_methods: zod.z.array(zod.z.enum(["cod", "monri"])),
953
+ payment_requirements: zod.z.array(zod.z.string()),
954
+ has_calculated_shipping: zod.z.boolean(),
955
+ shipping_address: addressSchemaNonMandatory,
956
+ billing_address: billingAddressSchemaNonMandatory,
957
+ shipping_rates: zod.z.array(shippingPackageSchema),
958
+ coupons: zod.z.array(cartCouponSchema),
959
+ totals: cartTotalsSchema,
960
+ errors: zod.z.array(
961
+ zod.z.object({
962
+ code: zod.z.string(),
963
+ message: zod.z.string()
964
+ })
965
+ ),
966
+ extensions: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
967
+ cross_sells: zod.z.array(zod.z.unknown()),
968
+ fees: zod.z.array(zod.z.unknown())
969
+ });
970
+ zod.z.object({
971
+ id: zod.z.number(),
972
+ quantity: zod.z.number().int().positive(),
973
+ variation: zod.z.array(
974
+ zod.z.object({
975
+ attribute: zod.z.string(),
976
+ value: zod.z.string()
977
+ })
978
+ ).optional()
979
+ });
980
+ zod.z.object({
981
+ key: zod.z.string(),
982
+ quantity: zod.z.number().int().positive()
983
+ });
984
+ zod.z.object({
985
+ key: zod.z.string()
986
+ });
987
+ zod.z.object({
988
+ code: zod.z.string().min(1)
989
+ });
990
+ zod.z.object({
991
+ billing_address: billingAddressSchemaMandatory.partial(),
992
+ shipping_address: addressSchema.partial()
993
+ }).partial();
994
+ zod.z.object({
995
+ package_id: zod.z.union([zod.z.number(), zod.z.string()]).default(0),
996
+ rate_id: zod.z.string()
997
+ });
998
+ var orderItemImageSchema = zod.z.object({
999
+ /** Image ID */
1000
+ id: zod.z.number(),
1001
+ /** Full image URL */
1002
+ src: zod.z.string().url(),
1003
+ /** Thumbnail URL */
1004
+ thumbnail: zod.z.string().url(),
1005
+ /** Responsive image srcset */
1006
+ srcset: zod.z.string(),
1007
+ /** Responsive image sizes attribute */
1008
+ sizes: zod.z.string(),
1009
+ /** Image name */
1010
+ name: zod.z.string(),
1011
+ /** Image alt text */
1012
+ alt: zod.z.string()
1013
+ });
1014
+ var orderItemPricesSchema = zod.z.object({
1015
+ /** Formatted price (current price) */
1016
+ price: zod.z.string(),
1017
+ /** Formatted regular price */
1018
+ regular_price: zod.z.string(),
1019
+ /** Formatted sale price */
1020
+ sale_price: zod.z.string(),
1021
+ /** Price range (null for simple products) */
1022
+ price_range: zod.z.null(),
1023
+ /** Currency code (e.g., 'USD', 'EUR') */
1024
+ currency_code: zod.z.string(),
1025
+ /** Currency symbol (e.g., '$', '€') */
1026
+ currency_symbol: zod.z.string(),
1027
+ /** Number of decimal places for currency */
1028
+ currency_minor_unit: zod.z.number(),
1029
+ /** Decimal separator (e.g., '.', ',') */
1030
+ currency_decimal_separator: zod.z.string(),
1031
+ /** Thousand separator (e.g., ',', '.') */
1032
+ currency_thousand_separator: zod.z.string(),
1033
+ /** Currency prefix (e.g., '$') */
1034
+ currency_prefix: zod.z.string(),
1035
+ /** Currency suffix (e.g., 'USD') */
1036
+ currency_suffix: zod.z.string(),
1037
+ /** Raw price values for calculations */
1038
+ raw_prices: zod.z.object({
1039
+ /** Precision for price calculations */
1040
+ precision: zod.z.number(),
1041
+ /** Raw price (in minor units) */
1042
+ price: zod.z.string(),
1043
+ /** Raw regular price (in minor units) */
1044
+ regular_price: zod.z.string(),
1045
+ /** Raw sale price (in minor units) */
1046
+ sale_price: zod.z.string()
1047
+ })
1048
+ });
1049
+ var orderItemTotalsSchema = zod.z.object({
1050
+ /** Line subtotal (before discounts) */
1051
+ line_subtotal: zod.z.string(),
1052
+ /** Line subtotal tax */
1053
+ line_subtotal_tax: zod.z.string(),
1054
+ /** Line total (after discounts) */
1055
+ line_total: zod.z.string(),
1056
+ /** Line total tax */
1057
+ line_total_tax: zod.z.string(),
1058
+ /** Currency code */
1059
+ currency_code: zod.z.string(),
1060
+ /** Currency symbol */
1061
+ currency_symbol: zod.z.string(),
1062
+ /** Currency minor unit */
1063
+ currency_minor_unit: zod.z.number(),
1064
+ /** Decimal separator */
1065
+ currency_decimal_separator: zod.z.string(),
1066
+ /** Thousand separator */
1067
+ currency_thousand_separator: zod.z.string(),
1068
+ /** Currency prefix */
1069
+ currency_prefix: zod.z.string(),
1070
+ /** Currency suffix */
1071
+ currency_suffix: zod.z.string()
1072
+ });
1073
+ var quantityLimitsSchema = zod.z.object({
1074
+ /** Minimum quantity allowed */
1075
+ minimum: zod.z.number(),
1076
+ /** Maximum quantity allowed */
1077
+ maximum: zod.z.number(),
1078
+ /** Quantity must be multiple of this value */
1079
+ multiple_of: zod.z.number(),
1080
+ /** Whether quantity is editable */
1081
+ editable: zod.z.boolean()
1082
+ });
1083
+ var orderItemSchema = zod.z.object({
1084
+ /** Unique cart/order item key */
1085
+ key: zod.z.string(),
1086
+ /** Product ID */
1087
+ id: zod.z.number(),
1088
+ /** Quantity ordered */
1089
+ quantity: zod.z.number(),
1090
+ /** Quantity limits for this product */
1091
+ quantity_limits: quantityLimitsSchema,
1092
+ /** Product name */
1093
+ name: zod.z.string(),
1094
+ /** Short description */
1095
+ short_description: zod.z.string(),
1096
+ /** Full description */
1097
+ description: zod.z.string(),
1098
+ /** Product SKU */
1099
+ sku: zod.z.string(),
1100
+ /** Low stock warning (null if not low) */
1101
+ low_stock_remaining: zod.z.null(),
1102
+ /** Whether backorders are allowed */
1103
+ backorders_allowed: zod.z.boolean(),
1104
+ /** Whether to show backorder badge */
1105
+ show_backorder_badge: zod.z.boolean(),
1106
+ /** Whether product can only be purchased individually */
1107
+ sold_individually: zod.z.boolean(),
1108
+ /** Product permalink URL */
1109
+ permalink: zod.z.string().url(),
1110
+ /** Product images */
1111
+ images: zod.z.array(orderItemImageSchema),
1112
+ /** Variation attributes (empty for simple products) */
1113
+ variation: zod.z.array(zod.z.unknown()),
1114
+ /** Custom item data/metadata */
1115
+ item_data: zod.z.array(zod.z.unknown()),
1116
+ /** Price information */
1117
+ prices: orderItemPricesSchema,
1118
+ /** Totals for this line item */
1119
+ totals: orderItemTotalsSchema,
1120
+ /** Catalog visibility setting */
1121
+ catalog_visibility: zod.z.string()
1122
+ });
1123
+ var orderTotalsSchema = zod.z.object({
1124
+ /** Subtotal (before discounts and fees) */
1125
+ subtotal: zod.z.string(),
1126
+ /** Total discount amount */
1127
+ total_discount: zod.z.string(),
1128
+ /** Total shipping cost */
1129
+ total_shipping: zod.z.string(),
1130
+ /** Total fees */
1131
+ total_fees: zod.z.string(),
1132
+ /** Total tax */
1133
+ total_tax: zod.z.string(),
1134
+ /** Total refund amount */
1135
+ total_refund: zod.z.string(),
1136
+ /** Final total price */
1137
+ total_price: zod.z.string(),
1138
+ /** Total items cost (subtotal after item-level discounts) */
1139
+ total_items: zod.z.string(),
1140
+ /** Tax on items */
1141
+ total_items_tax: zod.z.string(),
1142
+ /** Tax on fees */
1143
+ total_fees_tax: zod.z.string(),
1144
+ /** Tax on discounts */
1145
+ total_discount_tax: zod.z.string(),
1146
+ /** Tax on shipping */
1147
+ total_shipping_tax: zod.z.string(),
1148
+ /** Tax line items breakdown */
1149
+ tax_lines: zod.z.array(zod.z.unknown()),
1150
+ /** Currency code (e.g., 'USD', 'EUR', 'BAM') */
1151
+ currency_code: zod.z.string(),
1152
+ /** Currency symbol (e.g., '$', '€', 'KM') */
1153
+ currency_symbol: zod.z.string(),
1154
+ /** Number of decimal places for currency */
1155
+ currency_minor_unit: zod.z.number(),
1156
+ /** Decimal separator (e.g., '.', ',') */
1157
+ currency_decimal_separator: zod.z.string(),
1158
+ /** Thousand separator (e.g., ',', '.') */
1159
+ currency_thousand_separator: zod.z.string(),
1160
+ /** Currency prefix (e.g., '$') */
1161
+ currency_prefix: zod.z.string(),
1162
+ /** Currency suffix (e.g., 'USD') */
1163
+ currency_suffix: zod.z.string()
1164
+ });
1165
+
1166
+ // ../schemas/src/woocommerce/store-api/order.schema.ts
1167
+ var orderStatusEnum = zod.z.enum([
1168
+ "pending",
1169
+ // Order received, awaiting payment
1170
+ "processing",
1171
+ // Payment received, order is being processed
1172
+ "on-hold",
1173
+ // Order on hold, awaiting confirmation
1174
+ "completed",
1175
+ // Order fulfilled and complete
1176
+ "cancelled",
1177
+ // Order cancelled by customer or admin
1178
+ "refunded",
1179
+ // Order refunded
1180
+ "failed"
1181
+ // Payment failed or order declined
1182
+ ]);
1183
+ var storeApiOrderSchema = zod.z.object({
1184
+ /** Order ID */
1185
+ id: zod.z.number(),
1186
+ /** Order status */
1187
+ status: orderStatusEnum,
1188
+ /** Order line items (products purchased) */
1189
+ items: zod.z.array(orderItemSchema),
1190
+ /** Applied coupons */
1191
+ coupons: zod.z.array(zod.z.unknown()),
1192
+ /** Order fees */
1193
+ fees: zod.z.array(zod.z.unknown()),
1194
+ /** Order totals breakdown */
1195
+ totals: orderTotalsSchema,
1196
+ /** Shipping address */
1197
+ shipping_address: addressSchema,
1198
+ /** Billing address (includes email) */
1199
+ billing_address: billingAddressSchemaMandatory,
1200
+ /** Whether order still needs payment */
1201
+ needs_payment: zod.z.boolean(),
1202
+ /** Whether order needs shipping */
1203
+ needs_shipping: zod.z.boolean(),
1204
+ /** Payment method requirements */
1205
+ payment_requirements: zod.z.array(zod.z.string()),
1206
+ /** Order errors (if any) */
1207
+ errors: zod.z.array(zod.z.unknown()),
1208
+ /** Payment method (optional - returned from checkout) */
1209
+ payment_method: zod.z.string().optional(),
1210
+ /** Payment method title (optional - returned from checkout) */
1211
+ payment_method_title: zod.z.string().optional()
1212
+ });
1213
+ var checkoutSchema = zod.z.object({
1214
+ order_id: zod.z.number(),
1215
+ status: zod.z.string(),
1216
+ order_key: zod.z.string(),
1217
+ customer_note: zod.z.string(),
1218
+ customer_id: zod.z.number(),
1219
+ billing_address: billingAddressSchemaNonMandatory,
1220
+ shipping_address: addressSchemaNonMandatory,
1221
+ payment_method: zod.z.string(),
1222
+ payment_result: zod.z.object({
1223
+ payment_status: zod.z.string(),
1224
+ payment_details: zod.z.array(zod.z.unknown()),
1225
+ redirect_url: zod.z.string()
1226
+ })
1227
+ });
1228
+ zod.z.object({
1229
+ payment_method: zod.z.string(),
1230
+ payment_data: zod.z.array(
1231
+ zod.z.object({
1232
+ key: zod.z.string(),
1233
+ value: zod.z.union([zod.z.string(), zod.z.boolean()])
1234
+ })
1235
+ ).optional(),
1236
+ billing_address: billingAddressSchemaMandatory.optional(),
1237
+ shipping_address: addressSchema.optional(),
1238
+ customer_note: zod.z.string().optional(),
1239
+ create_account: zod.z.boolean().optional(),
1240
+ extensions: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
1241
+ });
1242
+ var addToCartSchema = zod.z.object({
1243
+ /** Description for add to cart action */
1244
+ description: zod.z.string(),
1245
+ /** Maximum quantity that can be added */
1246
+ maximum: zod.z.number(),
1247
+ /** Minimum quantity that can be added */
1248
+ minimum: zod.z.number(),
1249
+ /** Quantity must be a multiple of this number */
1250
+ multiple_of: zod.z.number(),
1251
+ /** Text for add to cart button */
1252
+ text: zod.z.string(),
1253
+ /** URL for add to cart action */
1254
+ url: zod.z.string()
1255
+ });
1256
+ var embeddedCategorySchema = zod.z.object({
1257
+ /** Category unique identifier */
1258
+ id: zod.z.number(),
1259
+ /** Category display name */
1260
+ name: zod.z.string(),
1261
+ /** URL-friendly category identifier */
1262
+ slug: zod.z.string(),
1263
+ /** Link to category page */
1264
+ link: zod.z.string(),
1265
+ /** Parent category ID (0 for root categories) - optional in embedded */
1266
+ parent: zod.z.number().optional(),
1267
+ /** Category description - optional in embedded */
1268
+ description: zod.z.string().optional(),
1269
+ /** Display type - optional in embedded */
1270
+ display: zod.z.string().optional(),
1271
+ /** Category image - optional in embedded */
1272
+ image: productImageSchema.nullable().optional(),
1273
+ /** Menu order for sorting - optional in embedded */
1274
+ menu_order: zod.z.number().optional(),
1275
+ /** Number of products in this category - optional in embedded */
1276
+ count: zod.z.number().optional()
1277
+ });
1278
+ var productCategorySchema = zod.z.object({
1279
+ /** Category unique identifier */
1280
+ id: zod.z.number(),
1281
+ /** Category display name */
1282
+ name: zod.z.string(),
1283
+ /** URL-friendly category identifier */
1284
+ slug: zod.z.string(),
1285
+ /** Parent category ID (0 for root categories) */
1286
+ parent: zod.z.number(),
1287
+ /** Category description */
1288
+ description: zod.z.string(),
1289
+ /** Category image */
1290
+ image: productImageSchema.nullable(),
1291
+ /** Menu order for sorting */
1292
+ menu_order: zod.z.number().optional(),
1293
+ /** Number of products in this category */
1294
+ count: zod.z.number()
1295
+ });
1296
+
1297
+ // ../schemas/src/woocommerce/store-api/products.ts
1298
+ var productSchema = zod.z.object({
1299
+ id: zod.z.number(),
1300
+ name: zod.z.string(),
1301
+ slug: zod.z.string(),
1302
+ type: zod.z.string(),
1303
+ variation: zod.z.string(),
1304
+ permalink: zod.z.string().url(),
1305
+ sku: zod.z.string(),
1306
+ short_description: zod.z.string(),
1307
+ description: zod.z.string(),
1308
+ is_purchasable: zod.z.boolean(),
1309
+ is_in_stock: zod.z.boolean(),
1310
+ is_on_backorder: zod.z.boolean(),
1311
+ low_stock_remaining: zod.z.number().nullable(),
1312
+ sold_individually: zod.z.boolean(),
1313
+ add_to_cart: addToCartSchema,
1314
+ has_options: zod.z.boolean(),
1315
+ on_sale: zod.z.boolean(),
1316
+ parent: zod.z.number(),
1317
+ attributes: zod.z.array(zod.z.unknown()),
1318
+ categories: zod.z.array(embeddedCategorySchema),
1319
+ tags: zod.z.array(zod.z.unknown()),
1320
+ images: zod.z.array(productImageSchema),
1321
+ variations: zod.z.array(zod.z.unknown()),
1322
+ price_html: zod.z.string(),
1323
+ prices: pricesSchema,
1324
+ average_rating: zod.z.string(),
1325
+ review_count: zod.z.number(),
1326
+ extensions: zod.z.record(zod.z.string(), zod.z.unknown())
1327
+ });
1328
+ zod.z.object({
1329
+ /** Attribution source type (e.g., 'mobile_app', 'organic', 'referral') */
1330
+ source_type: zod.z.string(),
1331
+ /** UTM source identifier (e.g., 'moja-apoteka-app', 'google') */
1332
+ utm_source: zod.z.string(),
1333
+ /** Device type (e.g., 'mobile', 'desktop', 'tablet') */
1334
+ device_type: zod.z.string()
1335
+ });
1336
+ var ORDER_ATTRIBUTION_NAMESPACE = "woocommerce/order-attribution";
1337
+
1338
+ // src/api/cart.ts
1339
+ var createCartAPI = (client, endpoints, options) => ({
1340
+ get: async () => {
1341
+ const response = await client.get(endpoints.cart);
1342
+ return handleApiResponse(response, cartSchema, options);
1343
+ },
1344
+ addItem: async (input) => {
1345
+ const response = await client.post(endpoints.addItem, input);
1346
+ return handleApiResponse(response, cartSchema, options);
1347
+ },
1348
+ updateItem: async (input) => {
1349
+ const response = await client.post(endpoints.updateItem, input);
1350
+ return handleApiResponse(response, cartSchema, options);
1351
+ },
1352
+ removeItem: async (input) => {
1353
+ const response = await client.post(endpoints.removeItem, input);
1354
+ return handleApiResponse(response, cartSchema, options);
1355
+ },
1356
+ applyCoupon: async (input) => {
1357
+ const response = await client.post(endpoints.applyCoupon, input);
1358
+ return handleApiResponse(response, cartSchema, options);
1359
+ },
1360
+ removeCoupon: async (input) => {
1361
+ const response = await client.post(endpoints.removeCoupon, input);
1362
+ return handleApiResponse(response, cartSchema, options);
1363
+ },
1364
+ updateCustomer: async (input) => {
1365
+ const response = await client.post(endpoints.updateCustomer, input);
1366
+ return handleApiResponse(response, cartSchema, options);
1367
+ },
1368
+ selectShippingRate: async (input) => {
1369
+ const response = await client.post(endpoints.selectShippingRate, input);
1370
+ return handleApiResponse(response, cartSchema, options);
1371
+ }
1372
+ });
1373
+
1374
+ // src/api/checkout.ts
1375
+ var createCheckoutAPI = (client, endpoints, options, orderAttribution) => ({
1376
+ get: async () => {
1377
+ const response = await client.get(endpoints.checkout);
1378
+ return handleApiResponse(response, checkoutSchema, options);
1379
+ },
1380
+ process: async (input) => {
1381
+ const body = orderAttribution ? {
1382
+ ...input,
1383
+ extensions: {
1384
+ [ORDER_ATTRIBUTION_NAMESPACE]: orderAttribution,
1385
+ ...input.extensions
1386
+ }
1387
+ } : input;
1388
+ const response = await client.post(endpoints.checkout, body);
1389
+ return handleApiResponse(response, checkoutSchema, options);
1390
+ }
1391
+ });
1392
+
1393
+ // src/api/orders.ts
1394
+ var createOrdersAPI = (client, endpoints, options) => ({
1395
+ /**
1396
+ * Get a single order by ID
1397
+ * Uses Store API - supports guest lookup with billing_email + key
1398
+ */
1399
+ get: async (input) => {
1400
+ const { id, billing_email, key } = input;
1401
+ const params = billing_email && key ? { billing_email, key } : void 0;
1402
+ const response = await client.get(`${endpoints.order}/${id}`, { params });
1403
+ return handleApiResponse(response, storeApiOrderSchema, options);
1404
+ }
1405
+ });
1406
+ var createProductsAPI = (client, endpoints, options) => ({
1407
+ list: async (params) => {
1408
+ const response = await client.get(endpoints.products, { params });
1409
+ return handlePaginatedApiResponse(response, zod.z.array(productSchema), params, options);
1410
+ },
1411
+ get: async (id) => {
1412
+ const response = await client.get(`${endpoints.products}/${id}`);
1413
+ return handleApiResponse(response, productSchema, options);
1414
+ },
1415
+ categories: async () => {
1416
+ const response = await client.get(endpoints.categories);
1417
+ return handleApiResponse(response, zod.z.array(productCategorySchema), options);
1418
+ }
1419
+ });
1420
+
1421
+ // src/constants/endpoints.ts
1422
+ var createEndpoints = (version = "legacy") => {
1423
+ const prefix = version === "v1" ? "/wc/store/v1" : "/wc/store";
1424
+ return {
1425
+ // Products
1426
+ products: `${prefix}/products`,
1427
+ categories: `${prefix}/products/categories`,
1428
+ // Cart
1429
+ cart: `${prefix}/cart`,
1430
+ addItem: `${prefix}/cart/add-item`,
1431
+ updateItem: `${prefix}/cart/update-item`,
1432
+ removeItem: `${prefix}/cart/remove-item`,
1433
+ applyCoupon: `${prefix}/cart/apply-coupon`,
1434
+ removeCoupon: `${prefix}/cart/remove-coupon`,
1435
+ updateCustomer: `${prefix}/cart/update-customer`,
1436
+ selectShippingRate: `${prefix}/cart/select-shipping-rate`,
1437
+ // Checkout
1438
+ checkout: `${prefix}/checkout`,
1439
+ // Orders
1440
+ order: `${prefix}/order`
1441
+ };
1442
+ };
1443
+
1444
+ // src/client/createClient.ts
1445
+ var DEFAULT_ERROR_REPORTER = {
1446
+ report: (error2) => {
1447
+ console.error("[woocommerce-utils]", error2);
1448
+ }
1449
+ };
1450
+ var createClient = (config) => {
1451
+ const fullConfig = {
1452
+ ...config,
1453
+ errorReporter: config.errorReporter ?? DEFAULT_ERROR_REPORTER
1454
+ };
1455
+ const axiosInstance = axios__default.default.create({
1456
+ baseURL: fullConfig.baseURL,
1457
+ timeout: fullConfig.timeout || 3e4,
1458
+ headers: {
1459
+ "Content-Type": "application/json",
1460
+ ...fullConfig.headers
1461
+ }
1462
+ });
1463
+ const endpoints = createEndpoints(fullConfig.storeApiVersion ?? "legacy");
1464
+ const responseOptions = {
1465
+ validationMode: fullConfig.validationMode,
1466
+ errorReporter: fullConfig.errorReporter?.report,
1467
+ onValidationError: fullConfig.onValidationError
1468
+ };
1469
+ const client = {
1470
+ config: fullConfig,
1471
+ axios: axiosInstance,
1472
+ products: createProductsAPI(axiosInstance, endpoints, responseOptions),
1473
+ cart: createCartAPI(axiosInstance, endpoints, responseOptions),
1474
+ checkout: createCheckoutAPI(
1475
+ axiosInstance,
1476
+ endpoints,
1477
+ responseOptions,
1478
+ fullConfig.orderAttribution
1479
+ ),
1480
+ orders: createOrdersAPI(axiosInstance, endpoints, responseOptions)
1481
+ };
1482
+ setupRequestInterceptor(axiosInstance, fullConfig, client);
1483
+ setupResponseInterceptor(axiosInstance, fullConfig, client);
1484
+ setupErrorInterceptor(axiosInstance, fullConfig, client);
1485
+ return client;
1486
+ };
1487
+ function createAdminClient(options) {
1488
+ const client = axios__default.default.create({
1489
+ baseURL: options.baseURL,
1490
+ auth: {
1491
+ username: options.consumerKey,
1492
+ password: options.consumerSecret
1493
+ },
1494
+ timeout: 15e3
1495
+ });
1496
+ return {
1497
+ deleteOrder: async (id, force = true) => {
1498
+ await client.delete(`/wc/v3/orders/${id}`, {
1499
+ params: { force }
1500
+ });
1501
+ }
1502
+ };
1503
+ }
1504
+ var TEST_BILLING_ADDRESS = {
1505
+ first_name: "E2E",
1506
+ last_name: "Test",
1507
+ company: "",
1508
+ address_1: "Ferhadija 10",
1509
+ address_2: "",
1510
+ city: "Sarajevo",
1511
+ state: "",
1512
+ postcode: "71000",
1513
+ country: "BA",
1514
+ phone: "+38761000000",
1515
+ email: "e2e-test@atomic.ba"
1516
+ };
1517
+ var TEST_SHIPPING_ADDRESS = {
1518
+ first_name: "E2E",
1519
+ last_name: "Test",
1520
+ company: "",
1521
+ address_1: "Ferhadija 10",
1522
+ address_2: "",
1523
+ city: "Sarajevo",
1524
+ state: "",
1525
+ postcode: "71000",
1526
+ country: "BA",
1527
+ phone: "+38761000000"
1528
+ };
1529
+ function createInMemoryCartAdapter() {
1530
+ let stored = {};
1531
+ return {
1532
+ get: async () => stored,
1533
+ save: async (headers) => {
1534
+ stored = { ...stored, ...headers };
1535
+ },
1536
+ clear: async () => {
1537
+ stored = {};
1538
+ }
1539
+ };
1540
+ }
1541
+ function createStaticJwtAdapter(token) {
1542
+ return {
1543
+ get: async () => token
1544
+ };
1545
+ }
1546
+ async function loginForJwt(baseURL, username, password) {
1547
+ const response = await axios__default.default.post(`${baseURL}/jwt-auth/v1/token`, {
1548
+ username,
1549
+ password
1550
+ });
1551
+ return response.data.token;
1552
+ }
1553
+ function assertAddressMatch(actual, expected, expectFn) {
1554
+ const fields = ["first_name", "last_name", "address_1", "city", "postcode", "country", "phone"];
1555
+ for (const field of fields) {
1556
+ expectFn(actual[field]).toBe(expected[field]);
1557
+ }
1558
+ }
1559
+
1560
+ // src/testing/suites/authenticated-checkout.suite.ts
1561
+ function authenticatedCheckoutSuite(options) {
1562
+ const { timeout = 3e4 } = options;
1563
+ const g = globalThis;
1564
+ const describeFn = options.describe || g.describe;
1565
+ const itFn = options.it || g.it;
1566
+ const expectFn = options.expect || g.expect;
1567
+ const beforeAllFn = options.beforeAll || g.beforeAll;
1568
+ const afterAllFn = options.afterAll || g.afterAll;
1569
+ if (!options.wpUsername || !options.wpPassword) {
1570
+ describeFn("Authenticated Checkout E2E", () => {
1571
+ itFn("skipped \u2014 no WP credentials provided", () => {
1572
+ console.warn("Authenticated checkout E2E skipped. Set wpUsername and wpPassword to run.");
1573
+ });
1574
+ });
1575
+ return;
1576
+ }
1577
+ describeFn("Authenticated Checkout E2E", () => {
1578
+ let client;
1579
+ let adminClient;
1580
+ let createdOrderId = null;
1581
+ beforeAllFn(async () => {
1582
+ const token = await loginForJwt(options.baseURL, options.wpUsername, options.wpPassword);
1583
+ client = createClient({
1584
+ baseURL: options.baseURL,
1585
+ storeApiVersion: options.storeApiVersion ?? "v1",
1586
+ cartHeaders: createInMemoryCartAdapter(),
1587
+ jwtToken: createStaticJwtAdapter(token),
1588
+ validationMode: "strict"
1589
+ });
1590
+ adminClient = createAdminClient({
1591
+ baseURL: options.baseURL,
1592
+ consumerKey: options.consumerKey,
1593
+ consumerSecret: options.consumerSecret
1594
+ });
1595
+ }, timeout);
1596
+ afterAllFn(async () => {
1597
+ if (createdOrderId) {
1598
+ try {
1599
+ await adminClient.deleteOrder(createdOrderId);
1600
+ } catch {
1601
+ console.warn(`[e2e cleanup] Failed to delete order ${createdOrderId}`);
1602
+ }
1603
+ }
1604
+ }, timeout);
1605
+ itFn(
1606
+ "should clear any existing cart items",
1607
+ async () => {
1608
+ const cart = await client.cart.get();
1609
+ for (const item of cart.items) {
1610
+ await client.cart.removeItem({ key: item.key });
1611
+ }
1612
+ const cleared = await client.cart.get();
1613
+ expectFn(cleared.items_count).toBe(0);
1614
+ },
1615
+ timeout
1616
+ );
1617
+ let productId;
1618
+ itFn(
1619
+ "should fetch products",
1620
+ async () => {
1621
+ const result = await client.products.list({ page: 1, per_page: 5 });
1622
+ expectFn(result.data.length).toBeGreaterThan(0);
1623
+ productId = result.data[0].id;
1624
+ },
1625
+ timeout
1626
+ );
1627
+ let cartItemKey;
1628
+ itFn(
1629
+ "should add item to cart with JWT auth",
1630
+ async () => {
1631
+ const cart = await client.cart.addItem({ id: productId, quantity: 1 });
1632
+ expectFn(cart.items_count).toBe(1);
1633
+ expectFn(cart.items[0].id).toBe(productId);
1634
+ cartItemKey = cart.items[0].key;
1635
+ },
1636
+ timeout
1637
+ );
1638
+ itFn(
1639
+ "should fetch cart",
1640
+ async () => {
1641
+ const cart = await client.cart.get();
1642
+ expectFn(cart.items_count).toBe(1);
1643
+ expectFn(cart.needs_payment).toBe(true);
1644
+ },
1645
+ timeout
1646
+ );
1647
+ itFn(
1648
+ "should update customer addresses",
1649
+ async () => {
1650
+ const cart = await client.cart.updateCustomer({
1651
+ billing_address: TEST_BILLING_ADDRESS,
1652
+ shipping_address: TEST_SHIPPING_ADDRESS
1653
+ });
1654
+ assertAddressMatch(cart.billing_address, TEST_BILLING_ADDRESS, expectFn);
1655
+ assertAddressMatch(cart.shipping_address, TEST_SHIPPING_ADDRESS, expectFn);
1656
+ expectFn(cart.billing_address.email).toBe(TEST_BILLING_ADDRESS.email);
1657
+ },
1658
+ timeout
1659
+ );
1660
+ itFn(
1661
+ "should return persisted addresses on cart fetch",
1662
+ async () => {
1663
+ const cart = await client.cart.get();
1664
+ expectFn(cart.billing_address.email).toBe(TEST_BILLING_ADDRESS.email);
1665
+ assertAddressMatch(cart.shipping_address, TEST_SHIPPING_ADDRESS, expectFn);
1666
+ },
1667
+ timeout
1668
+ );
1669
+ itFn(
1670
+ "should remove item from cart",
1671
+ async () => {
1672
+ const cart = await client.cart.removeItem({ key: cartItemKey });
1673
+ expectFn(cart.items_count).toBe(0);
1674
+ expectFn(cart.items.length).toBe(0);
1675
+ },
1676
+ timeout
1677
+ );
1678
+ itFn(
1679
+ "should re-add item to cart for checkout",
1680
+ async () => {
1681
+ const cart = await client.cart.addItem({ id: productId, quantity: 1 });
1682
+ expectFn(cart.items_count).toBe(1);
1683
+ },
1684
+ timeout
1685
+ );
1686
+ itFn(
1687
+ "should fetch checkout state",
1688
+ async () => {
1689
+ const checkout = await client.checkout.get();
1690
+ expectFn(checkout).toHaveProperty("order_id");
1691
+ expectFn(checkout).toHaveProperty("billing_address");
1692
+ expectFn(checkout).toHaveProperty("shipping_address");
1693
+ expectFn(checkout).toHaveProperty("payment_method");
1694
+ },
1695
+ timeout
1696
+ );
1697
+ itFn(
1698
+ "should process checkout with COD payment",
1699
+ async () => {
1700
+ const checkout = await client.checkout.process({
1701
+ payment_method: "cod",
1702
+ billing_address: TEST_BILLING_ADDRESS,
1703
+ shipping_address: TEST_SHIPPING_ADDRESS
1704
+ });
1705
+ expectFn(checkout.order_id).toBeGreaterThan(0);
1706
+ expectFn(checkout.order_key).toMatch(/^wc_order_/);
1707
+ expectFn(checkout.status).toBeTruthy();
1708
+ createdOrderId = checkout.order_id;
1709
+ },
1710
+ timeout
1711
+ );
1712
+ itFn(
1713
+ "should retrieve order using JWT authentication",
1714
+ async () => {
1715
+ const order = await client.orders.get({
1716
+ id: createdOrderId
1717
+ });
1718
+ expectFn(order.id).toBe(createdOrderId);
1719
+ assertAddressMatch(order.billing_address, TEST_BILLING_ADDRESS, expectFn);
1720
+ assertAddressMatch(order.shipping_address, TEST_SHIPPING_ADDRESS, expectFn);
1721
+ expectFn(order.billing_address.email).toBe(TEST_BILLING_ADDRESS.email);
1722
+ expectFn(order.items.length).toBeGreaterThan(0);
1723
+ },
1724
+ timeout
1725
+ );
1726
+ });
1727
+ }
1728
+
1729
+ // src/testing/suites/checkout-validation.suite.ts
1730
+ function checkoutValidationSuite(options) {
1731
+ const { timeout = 3e4 } = options;
1732
+ const g = globalThis;
1733
+ const describeFn = options.describe || g.describe;
1734
+ const itFn = options.it || g.it;
1735
+ const expectFn = options.expect || g.expect;
1736
+ const afterAllFn = options.afterAll || g.afterAll;
1737
+ describeFn("Checkout Validation E2E", () => {
1738
+ const leakedOrderIds = [];
1739
+ const adminClient = createAdminClient({
1740
+ baseURL: options.baseURL,
1741
+ consumerKey: options.consumerKey,
1742
+ consumerSecret: options.consumerSecret
1743
+ });
1744
+ afterAllFn(async () => {
1745
+ for (const id of leakedOrderIds) {
1746
+ try {
1747
+ await adminClient.deleteOrder(id);
1748
+ } catch {
1749
+ console.warn(`[e2e cleanup] Failed to delete leaked order ${id}`);
1750
+ }
1751
+ }
1752
+ }, timeout);
1753
+ async function createReadyClient() {
1754
+ const client = createClient({
1755
+ baseURL: options.baseURL,
1756
+ storeApiVersion: options.storeApiVersion ?? "v1",
1757
+ cartHeaders: createInMemoryCartAdapter(),
1758
+ validationMode: "strict"
1759
+ });
1760
+ const products = await client.products.list({ page: 1, per_page: 1 });
1761
+ if (products.data.length === 0) {
1762
+ throw new Error("No products available for checkout validation tests");
1763
+ }
1764
+ await client.cart.addItem({ id: products.data[0].id, quantity: 1 });
1765
+ return client;
1766
+ }
1767
+ async function expectRejection(label, checkoutCall) {
1768
+ let thrown = null;
1769
+ let result = null;
1770
+ try {
1771
+ result = await checkoutCall();
1772
+ } catch (error2) {
1773
+ thrown = error2;
1774
+ }
1775
+ if (result) {
1776
+ if (result.order_id > 0) leakedOrderIds.push(result.order_id);
1777
+ throw new Error(
1778
+ `${label}: expected server rejection but checkout succeeded (order_id=${result.order_id})`
1779
+ );
1780
+ }
1781
+ expectFn(thrown).toBeInstanceOf(WooCommerceApiError);
1782
+ const wcError = thrown;
1783
+ expectFn(wcError.statusCode).toBe(400);
1784
+ }
1785
+ itFn(
1786
+ "should reject checkout when billing email is empty",
1787
+ async () => {
1788
+ const client = await createReadyClient();
1789
+ await expectRejection(
1790
+ "empty billing email",
1791
+ () => client.checkout.process({
1792
+ payment_method: "cod",
1793
+ billing_address: { ...TEST_BILLING_ADDRESS, email: "" },
1794
+ shipping_address: TEST_SHIPPING_ADDRESS
1795
+ })
1796
+ );
1797
+ },
1798
+ timeout
1799
+ );
1800
+ itFn(
1801
+ "should reject checkout when billing email is invalid",
1802
+ async () => {
1803
+ const client = await createReadyClient();
1804
+ await expectRejection(
1805
+ "invalid billing email",
1806
+ () => client.checkout.process({
1807
+ payment_method: "cod",
1808
+ billing_address: { ...TEST_BILLING_ADDRESS, email: "not-an-email" },
1809
+ shipping_address: TEST_SHIPPING_ADDRESS
1810
+ })
1811
+ );
1812
+ },
1813
+ timeout
1814
+ );
1815
+ const billingRequiredFields = [
1816
+ "first_name",
1817
+ "last_name",
1818
+ "address_1",
1819
+ "city",
1820
+ "postcode"
1821
+ ];
1822
+ for (const field of billingRequiredFields) {
1823
+ itFn(
1824
+ `should reject checkout when billing ${field} is empty`,
1825
+ async () => {
1826
+ const client = await createReadyClient();
1827
+ await expectRejection(
1828
+ `empty billing ${field}`,
1829
+ () => client.checkout.process({
1830
+ payment_method: "cod",
1831
+ billing_address: { ...TEST_BILLING_ADDRESS, [field]: "" },
1832
+ shipping_address: TEST_SHIPPING_ADDRESS
1833
+ })
1834
+ );
1835
+ },
1836
+ timeout
1837
+ );
1838
+ }
1839
+ itFn(
1840
+ "should document server behavior for empty billing phone",
1841
+ async () => {
1842
+ const client = await createReadyClient();
1843
+ let serverAccepted = false;
1844
+ let orderId = 0;
1845
+ try {
1846
+ const result = await client.checkout.process({
1847
+ payment_method: "cod",
1848
+ billing_address: { ...TEST_BILLING_ADDRESS, phone: "" },
1849
+ shipping_address: { ...TEST_SHIPPING_ADDRESS, phone: "" }
1850
+ });
1851
+ serverAccepted = result.order_id > 0;
1852
+ orderId = result.order_id;
1853
+ } catch (error2) {
1854
+ expectFn(error2).toBeInstanceOf(WooCommerceApiError);
1855
+ }
1856
+ if (serverAccepted) {
1857
+ leakedOrderIds.push(orderId);
1858
+ }
1859
+ console.log(
1860
+ `[checkout-validation] Server ${serverAccepted ? "ACCEPTED" : "REJECTED"} checkout with empty phone`
1861
+ );
1862
+ expectFn(typeof serverAccepted).toBe("boolean");
1863
+ },
1864
+ timeout
1865
+ );
1866
+ const shippingRequiredFields = [
1867
+ "first_name",
1868
+ "last_name",
1869
+ "address_1",
1870
+ "city",
1871
+ "postcode"
1872
+ ];
1873
+ for (const field of shippingRequiredFields) {
1874
+ itFn(
1875
+ `should reject checkout when shipping ${field} is empty`,
1876
+ async () => {
1877
+ const client = await createReadyClient();
1878
+ await expectRejection(
1879
+ `empty shipping ${field}`,
1880
+ () => client.checkout.process({
1881
+ payment_method: "cod",
1882
+ billing_address: TEST_BILLING_ADDRESS,
1883
+ shipping_address: { ...TEST_SHIPPING_ADDRESS, [field]: "" }
1884
+ })
1885
+ );
1886
+ },
1887
+ timeout
1888
+ );
1889
+ }
1890
+ itFn(
1891
+ "should document server behavior for empty shipping phone",
1892
+ async () => {
1893
+ const client = await createReadyClient();
1894
+ let serverAccepted = false;
1895
+ let orderId = 0;
1896
+ try {
1897
+ const result = await client.checkout.process({
1898
+ payment_method: "cod",
1899
+ billing_address: TEST_BILLING_ADDRESS,
1900
+ shipping_address: { ...TEST_SHIPPING_ADDRESS, phone: "" }
1901
+ });
1902
+ serverAccepted = result.order_id > 0;
1903
+ orderId = result.order_id;
1904
+ } catch (error2) {
1905
+ expectFn(error2).toBeInstanceOf(WooCommerceApiError);
1906
+ }
1907
+ if (serverAccepted) {
1908
+ leakedOrderIds.push(orderId);
1909
+ }
1910
+ console.log(
1911
+ `[checkout-validation] Server ${serverAccepted ? "ACCEPTED" : "REJECTED"} checkout with empty shipping phone`
1912
+ );
1913
+ expectFn(typeof serverAccepted).toBe("boolean");
1914
+ },
1915
+ timeout
1916
+ );
1917
+ itFn(
1918
+ "should reject checkout with empty payment method",
1919
+ async () => {
1920
+ const client = await createReadyClient();
1921
+ await expectRejection(
1922
+ "empty payment method",
1923
+ () => client.checkout.process({
1924
+ payment_method: "",
1925
+ billing_address: TEST_BILLING_ADDRESS,
1926
+ shipping_address: TEST_SHIPPING_ADDRESS
1927
+ })
1928
+ );
1929
+ },
1930
+ timeout
1931
+ );
1932
+ });
1933
+ }
1934
+
1935
+ // src/testing/suites/guest-checkout.suite.ts
1936
+ function guestCheckoutSuite(options) {
1937
+ const { timeout = 3e4 } = options;
1938
+ const g = globalThis;
1939
+ const describeFn = options.describe || g.describe;
1940
+ const itFn = options.it || g.it;
1941
+ const expectFn = options.expect || g.expect;
1942
+ const beforeAllFn = options.beforeAll || g.beforeAll;
1943
+ const afterAllFn = options.afterAll || g.afterAll;
1944
+ describeFn("Guest Checkout E2E", () => {
1945
+ let client;
1946
+ let adminClient;
1947
+ let createdOrderId = null;
1948
+ beforeAllFn(() => {
1949
+ client = createClient({
1950
+ baseURL: options.baseURL,
1951
+ storeApiVersion: options.storeApiVersion ?? "v1",
1952
+ cartHeaders: createInMemoryCartAdapter(),
1953
+ validationMode: "strict"
1954
+ });
1955
+ adminClient = createAdminClient({
1956
+ baseURL: options.baseURL,
1957
+ consumerKey: options.consumerKey,
1958
+ consumerSecret: options.consumerSecret
1959
+ });
1960
+ }, timeout);
1961
+ afterAllFn(async () => {
1962
+ if (createdOrderId) {
1963
+ try {
1964
+ await adminClient.deleteOrder(createdOrderId);
1965
+ } catch {
1966
+ console.warn(`[e2e cleanup] Failed to delete order ${createdOrderId}`);
1967
+ }
1968
+ }
1969
+ }, timeout);
1970
+ let productId;
1971
+ itFn(
1972
+ "should fetch products and find at least one",
1973
+ async () => {
1974
+ const result = await client.products.list({ page: 1, per_page: 5 });
1975
+ expectFn(result.data.length).toBeGreaterThan(0);
1976
+ expectFn(result.pagination).toHaveProperty("total");
1977
+ productId = result.data[0].id;
1978
+ },
1979
+ timeout
1980
+ );
1981
+ let cartItemKey;
1982
+ itFn(
1983
+ "should add item to cart",
1984
+ async () => {
1985
+ const cart = await client.cart.addItem({ id: productId, quantity: 1 });
1986
+ expectFn(cart.items_count).toBe(1);
1987
+ expectFn(cart.items.length).toBe(1);
1988
+ expectFn(cart.items[0].id).toBe(productId);
1989
+ cartItemKey = cart.items[0].key;
1990
+ },
1991
+ timeout
1992
+ );
1993
+ itFn(
1994
+ "should fetch cart with item present",
1995
+ async () => {
1996
+ const cart = await client.cart.get();
1997
+ expectFn(cart.items_count).toBe(1);
1998
+ expectFn(cart.needs_payment).toBe(true);
1999
+ },
2000
+ timeout
2001
+ );
2002
+ itFn(
2003
+ "should update customer billing and shipping addresses",
2004
+ async () => {
2005
+ const cart = await client.cart.updateCustomer({
2006
+ billing_address: TEST_BILLING_ADDRESS,
2007
+ shipping_address: TEST_SHIPPING_ADDRESS
2008
+ });
2009
+ assertAddressMatch(cart.billing_address, TEST_BILLING_ADDRESS, expectFn);
2010
+ assertAddressMatch(cart.shipping_address, TEST_SHIPPING_ADDRESS, expectFn);
2011
+ expectFn(cart.billing_address.email).toBe(TEST_BILLING_ADDRESS.email);
2012
+ },
2013
+ timeout
2014
+ );
2015
+ itFn(
2016
+ "should return persisted addresses on cart fetch",
2017
+ async () => {
2018
+ const cart = await client.cart.get();
2019
+ expectFn(cart.billing_address.email).toBe(TEST_BILLING_ADDRESS.email);
2020
+ assertAddressMatch(cart.shipping_address, TEST_SHIPPING_ADDRESS, expectFn);
2021
+ },
2022
+ timeout
2023
+ );
2024
+ itFn(
2025
+ "should remove item from cart",
2026
+ async () => {
2027
+ const cart = await client.cart.removeItem({ key: cartItemKey });
2028
+ expectFn(cart.items_count).toBe(0);
2029
+ expectFn(cart.items.length).toBe(0);
2030
+ },
2031
+ timeout
2032
+ );
2033
+ itFn(
2034
+ "should re-add item to cart for checkout",
2035
+ async () => {
2036
+ const cart = await client.cart.addItem({ id: productId, quantity: 1 });
2037
+ expectFn(cart.items_count).toBe(1);
2038
+ },
2039
+ timeout
2040
+ );
2041
+ itFn(
2042
+ "should fetch checkout state",
2043
+ async () => {
2044
+ const checkout = await client.checkout.get();
2045
+ expectFn(checkout).toHaveProperty("order_id");
2046
+ expectFn(checkout).toHaveProperty("billing_address");
2047
+ expectFn(checkout).toHaveProperty("shipping_address");
2048
+ expectFn(checkout).toHaveProperty("payment_method");
2049
+ },
2050
+ timeout
2051
+ );
2052
+ let orderKey;
2053
+ itFn(
2054
+ "should process checkout with COD payment",
2055
+ async () => {
2056
+ const checkout = await client.checkout.process({
2057
+ payment_method: "cod",
2058
+ billing_address: TEST_BILLING_ADDRESS,
2059
+ shipping_address: TEST_SHIPPING_ADDRESS
2060
+ });
2061
+ expectFn(checkout.order_id).toBeGreaterThan(0);
2062
+ expectFn(checkout.order_key).toMatch(/^wc_order_/);
2063
+ expectFn(checkout.status).toBeTruthy();
2064
+ createdOrderId = checkout.order_id;
2065
+ orderKey = checkout.order_key;
2066
+ },
2067
+ timeout
2068
+ );
2069
+ itFn(
2070
+ "should retrieve order as guest using billing email and order key",
2071
+ async () => {
2072
+ const order = await client.orders.get({
2073
+ id: createdOrderId,
2074
+ billing_email: TEST_BILLING_ADDRESS.email,
2075
+ key: orderKey
2076
+ });
2077
+ expectFn(order.id).toBe(createdOrderId);
2078
+ assertAddressMatch(order.billing_address, TEST_BILLING_ADDRESS, expectFn);
2079
+ assertAddressMatch(order.shipping_address, TEST_SHIPPING_ADDRESS, expectFn);
2080
+ expectFn(order.billing_address.email).toBe(TEST_BILLING_ADDRESS.email);
2081
+ expectFn(order.items.length).toBeGreaterThan(0);
2082
+ },
2083
+ timeout
2084
+ );
2085
+ });
2086
+ }
2087
+
2088
+ // src/testing/createTestSuite.ts
2089
+ function createWooCommerceTestSuite(options) {
2090
+ guestCheckoutSuite(options);
2091
+ authenticatedCheckoutSuite(options);
2092
+ checkoutValidationSuite(options);
2093
+ }
2094
+
2095
+ exports.TEST_BILLING_ADDRESS = TEST_BILLING_ADDRESS;
2096
+ exports.TEST_SHIPPING_ADDRESS = TEST_SHIPPING_ADDRESS;
2097
+ exports.createAdminClient = createAdminClient;
2098
+ exports.createInMemoryCartAdapter = createInMemoryCartAdapter;
2099
+ exports.createStaticJwtAdapter = createStaticJwtAdapter;
2100
+ exports.createWooCommerceTestSuite = createWooCommerceTestSuite;
2101
+ exports.loginForJwt = loginForJwt;
2102
+ //# sourceMappingURL=index.js.map
2103
+ //# sourceMappingURL=index.js.map