@godaddy/gd-commerce-server 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/dist/index.js ADDED
@@ -0,0 +1,1567 @@
1
+ import { Router } from "express";
2
+ import { createHash } from "node:crypto";
3
+
4
+ //#region src/lib/commerce/checkout-config.ts
5
+ const DEFAULT_CHECKOUT_CONFIGURATION = {
6
+ enablePromotionCodes: false,
7
+ enableTaxCollection: false,
8
+ enableShipping: false
9
+ };
10
+ function parseCommerceCheckoutConfiguration(raw) {
11
+ if (!raw?.trim()) return DEFAULT_CHECKOUT_CONFIGURATION;
12
+ let value;
13
+ try {
14
+ value = JSON.parse(raw);
15
+ } catch {
16
+ return DEFAULT_CHECKOUT_CONFIGURATION;
17
+ }
18
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return DEFAULT_CHECKOUT_CONFIGURATION;
19
+ const candidate = value;
20
+ for (const key of [
21
+ "enablePromotionCodes",
22
+ "enableTaxCollection",
23
+ "enableShipping"
24
+ ]) if (typeof candidate[key] !== "boolean") return DEFAULT_CHECKOUT_CONFIGURATION;
25
+ const shipping = candidate.shipping;
26
+ if (shipping !== void 0) {
27
+ if (shipping === null || typeof shipping !== "object" || Array.isArray(shipping)) return DEFAULT_CHECKOUT_CONFIGURATION;
28
+ if ((shipping.originAddress !== null && typeof shipping.originAddress === "object" && !Array.isArray(shipping.originAddress)) === (typeof shipping.fulfillmentLocationId === "string" && shipping.fulfillmentLocationId.trim() !== "")) return DEFAULT_CHECKOUT_CONFIGURATION;
29
+ }
30
+ return {
31
+ enablePromotionCodes: candidate.enablePromotionCodes,
32
+ enableTaxCollection: candidate.enableTaxCollection,
33
+ enableShipping: candidate.enableShipping,
34
+ ...shipping ? { shipping } : {}
35
+ };
36
+ }
37
+
38
+ //#endregion
39
+ //#region src/lib/commerce/config.ts
40
+ const DEFAULT_API_BASE_URL = "https://api.godaddy.com";
41
+ function normalizeApiBaseUrl(raw) {
42
+ let url;
43
+ try {
44
+ url = new URL(raw);
45
+ } catch {
46
+ throw new Error("Commerce config: apiBaseUrl must be a valid HTTPS origin.");
47
+ }
48
+ if (url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw new Error("Commerce config: apiBaseUrl must be an HTTPS origin without credentials, path, query, or fragment.");
49
+ return url.origin;
50
+ }
51
+ function requireValue(environment, key) {
52
+ const value = environment[key]?.trim();
53
+ if (!value) throw new Error(`Commerce config: ${key} is missing. Configure it in the server environment.`);
54
+ return value;
55
+ }
56
+ /** Read server environment values on each call. Loading files or secret stores belongs to the host. */
57
+ function readCommerceConfig(options = {}) {
58
+ const environment = options.environment ?? process.env;
59
+ return {
60
+ clientId: requireValue(environment, "GODADDY_OAUTH_CLIENT_ID"),
61
+ clientSecret: requireValue(environment, "GODADDY_OAUTH_CLIENT_SECRET"),
62
+ storeId: requireValue(environment, "GODADDY_STORE_ID"),
63
+ channelId: requireValue(environment, "GODADDY_CHANNEL_ID"),
64
+ currencyCode: requireValue(environment, "GODADDY_CURRENCY_CODE"),
65
+ apiBaseUrl: normalizeApiBaseUrl(options.apiBaseUrl ?? DEFAULT_API_BASE_URL),
66
+ ...options.sourceApp ? { sourceApp: options.sourceApp } : {},
67
+ ...options.owner ? { owner: options.owner } : {}
68
+ };
69
+ }
70
+ function createRuntimeCommerceConfiguration(options = {}) {
71
+ return {
72
+ read: () => readCommerceConfig(options),
73
+ readCheckout: () => parseCommerceCheckoutConfiguration((options.environment ?? process.env).GODADDY_CHECKOUT_CONFIGURATION)
74
+ };
75
+ }
76
+ function commerceConfigurationForResponse(res) {
77
+ const configuration = res.locals.commerceConfiguration;
78
+ if (configuration && typeof configuration === "object" && "read" in configuration && typeof configuration.read === "function" && "readCheckout" in configuration && typeof configuration.readCheckout === "function") return configuration;
79
+ return createRuntimeCommerceConfiguration();
80
+ }
81
+ function readCommerceConfigForResponse(res) {
82
+ return commerceConfigurationForResponse(res).read();
83
+ }
84
+
85
+ //#endregion
86
+ //#region src/lib/commerce/checkout-subgraph.ts
87
+ /**
88
+ * The hosted checkout page will not render a card form without this. GDC's
89
+ * checkout GraphQL accepts a session with no payment methods and returns 200,
90
+ * so an omitted or deleted default fails silently — the shopper sees "No
91
+ * payment methods available" with no error anywhere in the request/response
92
+ * cycle. Do not remove this while debugging an unrelated checkout issue.
93
+ */
94
+ const DEFAULT_CHECKOUT_PAYMENT_METHODS = { card: {
95
+ processor: "godaddy",
96
+ checkoutTypes: ["standard"]
97
+ } };
98
+ function stripUndefined(value) {
99
+ return Object.fromEntries(Object.entries(value).filter(([, entryValue]) => entryValue !== void 0));
100
+ }
101
+ function applyCheckoutDefaults(base, overrides = {}) {
102
+ const definedOverrides = stripUndefined(overrides);
103
+ const result = {
104
+ ...base,
105
+ ...definedOverrides
106
+ };
107
+ if (result.paymentMethods === void 0) result.paymentMethods = DEFAULT_CHECKOUT_PAYMENT_METHODS;
108
+ if (result.enablePaymentMethodCollection === void 0) result.enablePaymentMethodCollection = true;
109
+ if (result.enableBillingAddressCollection === void 0) result.enableBillingAddressCollection = true;
110
+ if (result.enableShipping === void 0) result.enableShipping = false;
111
+ if (result.enableShippingAddressCollection === void 0) result.enableShippingAddressCollection = false;
112
+ if (result.enableLocalPickup === void 0) result.enableLocalPickup = false;
113
+ if (result.enablePhoneCollection === void 0) result.enablePhoneCollection = false;
114
+ if (result.enableTaxCollection === void 0) result.enableTaxCollection = false;
115
+ return result;
116
+ }
117
+ function checkoutGraphqlEndpoint({ apiBaseUrl }) {
118
+ const { host, protocol } = new URL(apiBaseUrl);
119
+ return `${protocol}//checkout.commerce.${host}`;
120
+ }
121
+ function authorizationHeaders({ accessToken }) {
122
+ return { Authorization: `Bearer ${accessToken}` };
123
+ }
124
+ async function getOAuthAccessToken({ clientId, clientSecret, apiBaseUrl, scope = "commerce.product:read", fetch: fetchImplementation }) {
125
+ if (!clientId || !clientSecret) throw new Error("clientId and clientSecret are required");
126
+ const requestFetch = fetchImplementation ?? fetch;
127
+ const body = new URLSearchParams();
128
+ body.append("grant_type", "client_credentials");
129
+ body.append("client_id", clientId);
130
+ body.append("client_secret", clientSecret);
131
+ body.append("scope", scope);
132
+ const response = await requestFetch(new URL("/v2/oauth2/token", apiBaseUrl).toString(), {
133
+ method: "POST",
134
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
135
+ body: body.toString(),
136
+ cache: "no-store"
137
+ });
138
+ if (!response.ok) throw new Error(`Failed to get access token: ${response.status} ${response.statusText}`);
139
+ return await response.json();
140
+ }
141
+ function buildBuyNowCheckoutInput({ storeId, skuId, quantity = 1, returnUrl, successUrl, channelId }, overrides = {}) {
142
+ return applyCheckoutDefaults({
143
+ storeId,
144
+ returnUrl,
145
+ successUrl,
146
+ channelId,
147
+ lineItems: [{
148
+ skuId,
149
+ quantity
150
+ }]
151
+ }, overrides);
152
+ }
153
+ function buildCartCheckoutInput({ storeId, draftOrderId, returnUrl, successUrl, channelId }, overrides = {}) {
154
+ return applyCheckoutDefaults({
155
+ storeId,
156
+ returnUrl,
157
+ successUrl,
158
+ channelId,
159
+ draftOrderId
160
+ }, overrides);
161
+ }
162
+ function buildNonCatalogCheckoutInput({ storeId, lineItemData, quantity = 1, returnUrl, successUrl, channelId }, overrides = {}) {
163
+ return applyCheckoutDefaults({
164
+ storeId,
165
+ returnUrl,
166
+ successUrl,
167
+ channelId,
168
+ lineItems: [{
169
+ lineItemData,
170
+ quantity
171
+ }]
172
+ }, overrides);
173
+ }
174
+
175
+ //#endregion
176
+ //#region src/lib/commerce/gql.ts
177
+ var GraphQLErrorWithCodes = class extends Error {
178
+ constructor(errors, status) {
179
+ const errorMessage = errors.length === 1 ? `${errors[0]?.message || "Unknown error"}` : errors.map((error) => error.message).filter(Boolean).join("; ");
180
+ super(errorMessage);
181
+ this.errors = errors;
182
+ this.status = status;
183
+ this.name = "GraphQLErrorWithCodes";
184
+ }
185
+ get codes() {
186
+ return this.errors.map((error) => error.code).filter(Boolean);
187
+ }
188
+ get messages() {
189
+ return this.errors.map((error) => error.message).filter(Boolean);
190
+ }
191
+ get statuses() {
192
+ return this.errors.map((error) => error.status).filter((status) => typeof status === "number");
193
+ }
194
+ };
195
+ function storefrontHeaders({ storeId, clientId }) {
196
+ return {
197
+ "X-Store-ID": storeId,
198
+ "X-Client-ID": clientId
199
+ };
200
+ }
201
+ async function gqlRequest({ endpoint, query, variables, headers: headersInit, fetch: fetchImplementation }) {
202
+ const requestHeaders = new Headers(headersInit);
203
+ requestHeaders.set("Accept", "application/json");
204
+ requestHeaders.set("Content-Type", "application/json");
205
+ const response = await (fetchImplementation ?? fetch)(endpoint, {
206
+ method: "POST",
207
+ headers: requestHeaders,
208
+ body: JSON.stringify({
209
+ query,
210
+ variables: variables ?? {}
211
+ }),
212
+ cache: "no-store"
213
+ });
214
+ let result;
215
+ try {
216
+ result = await response.json();
217
+ } catch {
218
+ throw new GraphQLErrorWithCodes([{
219
+ message: `GraphQL request failed: ${response.status} ${response.statusText}`,
220
+ status: response.status
221
+ }], response.ok ? void 0 : response.status);
222
+ }
223
+ if (result.errors?.length) throw new GraphQLErrorWithCodes(result.errors.map((error) => ({
224
+ message: error.message,
225
+ code: error.extensions?.code,
226
+ status: error.extensions?.status ?? error.extensions?.http?.status ?? (response.ok ? void 0 : response.status)
227
+ })), response.ok ? void 0 : response.status);
228
+ if (!response.ok) throw new GraphQLErrorWithCodes([{
229
+ message: `GraphQL request failed: ${response.status} ${response.statusText}`,
230
+ status: response.status
231
+ }], response.status);
232
+ if (result.data === void 0) throw new Error("GraphQL response did not include data");
233
+ return result.data;
234
+ }
235
+
236
+ //#endregion
237
+ //#region src/lib/commerce/create-checkout-session.ts
238
+ const createCheckoutSessionMutation = `
239
+ mutation CreateCheckoutSession($input: MutationCreateCheckoutSessionInput!) {
240
+ createCheckoutSession(input: $input) {
241
+ id
242
+ token
243
+ url
244
+ sourceApp
245
+ returnUrl
246
+ successUrl
247
+ storeId
248
+ businessId
249
+ channelId
250
+ customerId
251
+ storeName
252
+ environment
253
+ enableTips
254
+ enabledLocales
255
+ enableSurcharge
256
+ enableLocalPickup
257
+ enableShipping
258
+ enablePhoneCollection
259
+ enableNotesCollection
260
+ enablePromotionCodes
261
+ enableTaxCollection
262
+ enableShippingAddressCollection
263
+ enableBillingAddressCollection
264
+ enableAddressAutocomplete
265
+ paymentMethods {
266
+ card {
267
+ processor
268
+ checkoutTypes
269
+ }
270
+ ccavenue {
271
+ processor
272
+ checkoutTypes
273
+ }
274
+ express {
275
+ processor
276
+ checkoutTypes
277
+ }
278
+ applePay {
279
+ processor
280
+ checkoutTypes
281
+ }
282
+ googlePay {
283
+ processor
284
+ checkoutTypes
285
+ }
286
+ paypal {
287
+ processor
288
+ checkoutTypes
289
+ }
290
+ paze {
291
+ processor
292
+ checkoutTypes
293
+ }
294
+ offline {
295
+ processor
296
+ checkoutTypes
297
+ }
298
+ mercadopago {
299
+ processor
300
+ checkoutTypes
301
+ }
302
+ ach {
303
+ processor
304
+ checkoutTypes
305
+ }
306
+ }
307
+ draftOrder {
308
+ id
309
+ statuses {
310
+ status
311
+ }
312
+ totals {
313
+ total {
314
+ currencyCode
315
+ value
316
+ }
317
+ }
318
+ }
319
+ }
320
+ }
321
+ `;
322
+ function promotionCodesEnabled(configuration) {
323
+ return "enablePromotionCodes" in configuration && configuration.enablePromotionCodes === true;
324
+ }
325
+ async function createCheckoutSession(params, configuration = createRuntimeCommerceConfiguration()) {
326
+ const { draftOrderId, skuId, quantity, lineItemData, returnUrl, successUrl } = params;
327
+ if (!returnUrl || !successUrl) throw new Error("createCheckoutSession: returnUrl and successUrl are required");
328
+ if ([
329
+ draftOrderId,
330
+ skuId,
331
+ lineItemData
332
+ ].filter(Boolean).length !== 1) throw new Error("createCheckoutSession: exactly one of draftOrderId, skuId, or lineItemData is required");
333
+ const { storeId, channelId, clientId, clientSecret, apiBaseUrl, sourceApp, owner, currencyCode: configCurrencyCode } = configuration.read();
334
+ const checkoutConfiguration = configuration.readCheckout();
335
+ const enablePromotionCodes = promotionCodesEnabled(checkoutConfiguration);
336
+ const catalogShippingEnabled = lineItemData === void 0 && checkoutConfiguration.enableShipping;
337
+ const token = await getOAuthAccessToken({
338
+ clientId,
339
+ clientSecret,
340
+ apiBaseUrl,
341
+ scope: "commerce.product:read"
342
+ });
343
+ const attribution = {
344
+ sourceApp,
345
+ owner
346
+ };
347
+ const catalogCheckoutOverrides = {
348
+ ...attribution,
349
+ enablePromotionCodes,
350
+ enableTaxCollection: checkoutConfiguration.enableTaxCollection,
351
+ enableShipping: checkoutConfiguration.enableShipping,
352
+ enableShippingAddressCollection: checkoutConfiguration.enableShipping,
353
+ paymentMethods: DEFAULT_CHECKOUT_PAYMENT_METHODS,
354
+ shipping: catalogShippingEnabled ? checkoutConfiguration.shipping : void 0
355
+ };
356
+ const resolvedLineItemData = lineItemData !== void 0 ? {
357
+ ...lineItemData,
358
+ priceData: {
359
+ ...lineItemData.priceData,
360
+ currencyCode: configCurrencyCode
361
+ }
362
+ } : void 0;
363
+ const input = draftOrderId ? buildCartCheckoutInput({
364
+ storeId,
365
+ channelId,
366
+ draftOrderId,
367
+ returnUrl,
368
+ successUrl
369
+ }, catalogCheckoutOverrides) : resolvedLineItemData ? buildNonCatalogCheckoutInput({
370
+ storeId,
371
+ channelId,
372
+ lineItemData: resolvedLineItemData,
373
+ quantity: quantity ?? 1,
374
+ returnUrl,
375
+ successUrl
376
+ }, {
377
+ ...attribution,
378
+ enableTaxCollection: checkoutConfiguration.enableTaxCollection,
379
+ paymentMethods: DEFAULT_CHECKOUT_PAYMENT_METHODS
380
+ }) : buildBuyNowCheckoutInput({
381
+ storeId,
382
+ channelId,
383
+ skuId,
384
+ quantity: quantity ?? 1,
385
+ returnUrl,
386
+ successUrl
387
+ }, catalogCheckoutOverrides);
388
+ let result;
389
+ try {
390
+ result = await gqlRequest({
391
+ endpoint: checkoutGraphqlEndpoint({ apiBaseUrl }),
392
+ query: createCheckoutSessionMutation,
393
+ variables: { input },
394
+ headers: authorizationHeaders({ accessToken: token.access_token })
395
+ });
396
+ } catch (error) {
397
+ throw new Error("Commerce checkout session could not be created", { cause: error });
398
+ }
399
+ const session = result.createCheckoutSession;
400
+ if (!session?.url || !session.id) throw new Error("Checkout session was not created");
401
+ if (session.storeId !== storeId || session.channelId !== channelId) throw new Error(`Checkout session binding mismatch: expected store ${storeId} and channel ${channelId}, received store ${session.storeId ?? "missing"} and channel ${session.channelId ?? "missing"}`);
402
+ const expectedShipping = lineItemData === void 0 && checkoutConfiguration.enableShipping;
403
+ if (lineItemData === void 0 && enablePromotionCodes && session.enablePromotionCodes !== true) throw new Error("Checkout session did not enable configured promotion codes");
404
+ if (checkoutConfiguration.enableTaxCollection && session.enableTaxCollection !== true) throw new Error("Checkout session did not enable configured tax collection");
405
+ if (expectedShipping && (session.enableShipping !== true || session.enableShippingAddressCollection !== true)) throw new Error("Checkout session did not enable configured shipping and address collection");
406
+ if (!session.paymentMethods?.card?.processor) throw new Error("Checkout session did not configure payment methods.");
407
+ return {
408
+ url: session.url,
409
+ id: session.id,
410
+ draftOrderId: session.draftOrder?.id ?? null,
411
+ storeId,
412
+ channelId,
413
+ businessId: session.businessId ?? null,
414
+ storeName: session.storeName ?? null,
415
+ sourceApp: session.sourceApp ?? null
416
+ };
417
+ }
418
+
419
+ //#endregion
420
+ //#region src/lib/commerce/get-order-status.ts
421
+ async function getOrderStatus(orderId, configuration = createRuntimeCommerceConfiguration()) {
422
+ if (typeof orderId !== "string" || !orderId.trim() || orderId === "." || orderId === "..") throw new Error("getOrderStatus: a valid orderId is required");
423
+ const { storeId, channelId, clientId, clientSecret, apiBaseUrl, currencyCode } = configuration.read();
424
+ const token = await getOAuthAccessToken({
425
+ clientId,
426
+ clientSecret,
427
+ apiBaseUrl,
428
+ scope: "commerce.order:read"
429
+ });
430
+ const response = await fetch(new URL(`/v1/commerce/stores/${encodeURIComponent(storeId)}/orders/${encodeURIComponent(orderId)}`, apiBaseUrl), {
431
+ method: "GET",
432
+ headers: {
433
+ ...authorizationHeaders({ accessToken: token.access_token }),
434
+ Accept: "application/json"
435
+ },
436
+ cache: "no-store"
437
+ });
438
+ if (!response.ok) throw new Error(`Failed to load order: upstream returned ${response.status}`);
439
+ const order = (await response.json())?.order;
440
+ if (!order?.id || order.id !== orderId) throw new Error("Order lookup did not return the requested order");
441
+ if (order.context?.storeId !== storeId || order.context?.channelId !== channelId) throw new Error("Order lookup returned a different store or channel");
442
+ const total = order.totals?.total;
443
+ return {
444
+ id: order.id,
445
+ status: order.statuses?.paymentStatus ?? "unknown",
446
+ amount: total?.value ?? 0,
447
+ currency: total?.currencyCode ?? currencyCode,
448
+ createdAt: order.createdAt,
449
+ updatedAt: order.updatedAt,
450
+ lineItems: (order.lineItems ?? []).map(({ id, title, quantity }) => ({
451
+ id,
452
+ name: title,
453
+ quantity
454
+ }))
455
+ };
456
+ }
457
+
458
+ //#endregion
459
+ //#region src/lib/commerce/checkout-return-urls.ts
460
+ function parseDestination(value) {
461
+ if (typeof value !== "string" || /[\s\\]/.test(value)) return null;
462
+ try {
463
+ const url = new URL(value);
464
+ if (url.protocol !== "https:" || url.username || url.password || url.hash) return null;
465
+ return url;
466
+ } catch {
467
+ return null;
468
+ }
469
+ }
470
+ function destinationKey(url) {
471
+ const canonical = new URL(url);
472
+ canonical.searchParams.sort();
473
+ return canonical.href;
474
+ }
475
+ function createCheckoutReturnUrlValidator(policy) {
476
+ function allowedDestinations(values) {
477
+ return new Set(values.map((value) => {
478
+ const url = parseDestination(value);
479
+ if (!url || url.searchParams.has("orderId")) throw new Error("Checkout return destinations must be absolute HTTPS URLs without credentials, fragments, or orderId.");
480
+ return destinationKey(url);
481
+ }));
482
+ }
483
+ const returnUrls = allowedDestinations(policy.returnUrls);
484
+ const successUrls = allowedDestinations(policy.successUrls);
485
+ return (returnUrl, successUrl) => {
486
+ const cancel = parseDestination(returnUrl);
487
+ const success = parseDestination(successUrl);
488
+ if (!cancel || !success || success.searchParams.getAll("orderId").length > 1) return null;
489
+ const successDestination = new URL(success);
490
+ successDestination.searchParams.delete("orderId");
491
+ if (!returnUrls.has(destinationKey(cancel)) || !successUrls.has(destinationKey(successDestination))) return null;
492
+ return {
493
+ returnUrl: cancel.href,
494
+ successUrl: success.href
495
+ };
496
+ };
497
+ }
498
+
499
+ //#endregion
500
+ //#region src/lib/commerce/cart-scope.ts
501
+ /** Public cache/storage scope, not an authorization credential. */
502
+ function getCommerceCartScope(config) {
503
+ return createHash("sha256").update(JSON.stringify([
504
+ config.apiBaseUrl,
505
+ config.storeId,
506
+ config.channelId,
507
+ config.currencyCode
508
+ ])).digest("hex").slice(0, 32);
509
+ }
510
+ /** Existing custom clients may omit the header; managed components always send it. */
511
+ function validateCommerceCartScope(req, res, config) {
512
+ const suppliedScope = req.headers?.["x-commerce-scope"];
513
+ if (suppliedScope === void 0 || suppliedScope === getCommerceCartScope(config)) return true;
514
+ res.status(409).json({ error: "The connected store changed. Reload the page before continuing." });
515
+ return false;
516
+ }
517
+
518
+ //#endregion
519
+ //#region src/lib/commerce/order-subgraph.ts
520
+ function createMoney(value, currencyCode) {
521
+ return {
522
+ value,
523
+ currencyCode
524
+ };
525
+ }
526
+ function orderStorefrontEndpoint({ apiBaseUrl }) {
527
+ return new URL("/v1/commerce/order-storefront-subgraph", apiBaseUrl).toString();
528
+ }
529
+ function buildEmptyCartOrderInput({ storeId, channelId, currencyCode = "USD", owner }) {
530
+ return {
531
+ context: {
532
+ storeId,
533
+ channelId: channelId || "",
534
+ ...owner ? { owner } : {}
535
+ },
536
+ totals: {
537
+ subTotal: createMoney(0, currencyCode),
538
+ shippingTotal: createMoney(0, currencyCode),
539
+ discountTotal: createMoney(0, currencyCode),
540
+ feeTotal: createMoney(0, currencyCode),
541
+ taxTotal: createMoney(0, currencyCode),
542
+ total: createMoney(0, currencyCode)
543
+ }
544
+ };
545
+ }
546
+ function buildAddLineItemBySkuIdInput(orderId, item) {
547
+ return {
548
+ orderId,
549
+ skuId: item.skuId,
550
+ name: item.name,
551
+ quantity: item.quantity,
552
+ fulfillmentMode: "NONE",
553
+ status: "DRAFT"
554
+ };
555
+ }
556
+ const getCartOrderQuery = `
557
+ query GetCartOrder($id: ID!) {
558
+ orderById(id: $id) {
559
+ id
560
+ customerId
561
+ createdAt
562
+ updatedAt
563
+ context {
564
+ storeId
565
+ channelId
566
+ }
567
+ lineItems {
568
+ id
569
+ name
570
+ quantity
571
+ skuId
572
+ type
573
+ fulfillmentMode
574
+ details {
575
+ productAssetUrl
576
+ sku
577
+ unitOfMeasure
578
+ selectedOptions {
579
+ attribute
580
+ values
581
+ }
582
+ selectedAddons {
583
+ attribute
584
+ sku
585
+ values {
586
+ name
587
+ costAdjustment {
588
+ value
589
+ currencyCode
590
+ }
591
+ }
592
+ }
593
+ }
594
+ totals {
595
+ subTotal {
596
+ value
597
+ currencyCode
598
+ }
599
+ taxTotal {
600
+ value
601
+ currencyCode
602
+ }
603
+ discountTotal {
604
+ value
605
+ currencyCode
606
+ }
607
+ feeTotal {
608
+ value
609
+ currencyCode
610
+ }
611
+ }
612
+ discounts {
613
+ id
614
+ name
615
+ code
616
+ amount {
617
+ value
618
+ currencyCode
619
+ }
620
+ ratePercentage
621
+ }
622
+ taxes {
623
+ id
624
+ name
625
+ amount {
626
+ value
627
+ currencyCode
628
+ }
629
+ ratePercentage
630
+ }
631
+ notes {
632
+ id
633
+ content
634
+ author
635
+ authorType
636
+ }
637
+ }
638
+ totals {
639
+ subTotal {
640
+ value
641
+ currencyCode
642
+ }
643
+ shippingTotal {
644
+ value
645
+ currencyCode
646
+ }
647
+ taxTotal {
648
+ value
649
+ currencyCode
650
+ }
651
+ discountTotal {
652
+ value
653
+ currencyCode
654
+ }
655
+ productDiscountTotal {
656
+ value
657
+ currencyCode
658
+ }
659
+ shippingDiscountTotal {
660
+ value
661
+ currencyCode
662
+ }
663
+ feeTotal {
664
+ value
665
+ currencyCode
666
+ }
667
+ total {
668
+ value
669
+ currencyCode
670
+ }
671
+ }
672
+ discounts {
673
+ id
674
+ name
675
+ code
676
+ amount {
677
+ value
678
+ currencyCode
679
+ }
680
+ ratePercentage
681
+ appliedBeforeTax
682
+ }
683
+ taxes {
684
+ id
685
+ name
686
+ amount {
687
+ value
688
+ currencyCode
689
+ }
690
+ ratePercentage
691
+ included
692
+ exempted
693
+ }
694
+ shipping {
695
+ firstName
696
+ lastName
697
+ email
698
+ phone
699
+ companyName
700
+ address {
701
+ addressLine1
702
+ addressLine2
703
+ addressLine3
704
+ adminArea1
705
+ adminArea2
706
+ adminArea3
707
+ adminArea4
708
+ postalCode
709
+ countryCode
710
+ }
711
+ }
712
+ notes {
713
+ id
714
+ content
715
+ author
716
+ authorType
717
+ createdAt
718
+ }
719
+ tags
720
+ }
721
+ }
722
+ `;
723
+
724
+ //#endregion
725
+ //#region src/server/api/commerce/cart/[id]/discounts/POST.ts
726
+ const applyDiscountCodesMutation = `
727
+ mutation ApplyDiscountCodes($input: ApplyDiscountCodesInput!) {
728
+ applyDiscountCodes(input: $input) {
729
+ id
730
+ }
731
+ }
732
+ `;
733
+ async function handler(req, res) {
734
+ try {
735
+ const cartId = req.params.id;
736
+ if (typeof cartId !== "string" || !cartId) {
737
+ res.status(400).json({ error: "Missing cart id" });
738
+ return;
739
+ }
740
+ const body = req.body ?? {};
741
+ const codes = Array.isArray(body.discountCodes) ? body.discountCodes.filter((code) => typeof code === "string" && code.length > 0) : [];
742
+ if (codes.length === 0) {
743
+ res.status(400).json({ error: "discountCodes must be a non-empty string array" });
744
+ return;
745
+ }
746
+ const config = readCommerceConfigForResponse(res);
747
+ if (!validateCommerceCartScope(req, res, config)) return;
748
+ const { storeId, clientId, apiBaseUrl } = config;
749
+ const endpoint = orderStorefrontEndpoint({ apiBaseUrl });
750
+ const headers = storefrontHeaders({
751
+ storeId,
752
+ clientId
753
+ });
754
+ await gqlRequest({
755
+ endpoint,
756
+ query: applyDiscountCodesMutation,
757
+ variables: { input: {
758
+ orderId: cartId,
759
+ discountCodes: codes
760
+ } },
761
+ headers
762
+ });
763
+ const hydrated = await gqlRequest({
764
+ endpoint,
765
+ query: getCartOrderQuery,
766
+ variables: { id: cartId },
767
+ headers
768
+ });
769
+ res.json({ cart: hydrated.orderById ?? null });
770
+ } catch (error) {
771
+ res.status(500).json({
772
+ error: "Failed to apply discount codes",
773
+ message: error instanceof Error ? error.message : String(error)
774
+ });
775
+ }
776
+ }
777
+
778
+ //#endregion
779
+ //#region src/server/api/commerce/cart/[id]/GET.ts
780
+ function isCartNotFoundError(error) {
781
+ if (!(error instanceof GraphQLErrorWithCodes)) return false;
782
+ const isFailure = (status) => status !== void 0 && status >= 400 && status !== 404 && status !== 410;
783
+ if (isFailure(error.status) || error.errors.length === 0) return false;
784
+ return error.errors.every(({ code, message, status }) => {
785
+ if (isFailure(status)) return false;
786
+ if (code === "INTERNAL_SERVER_ERROR" && message === "Order not found") return true;
787
+ if (code && /^(?:DRAFT[_-]?)?(?:ORDER|CART)[_-]?(?:NOT[_-]?FOUND|EXPIRED)$/i.test(code)) return true;
788
+ if (code && !/^(?:NOT[_-]?FOUND|EXPIRED)$/i.test(code)) return false;
789
+ return /\b(?:cart|(?:draft[ -])?order)\s+(?:(?:is|was|has)\s+)?(?:not found|expired)\b/i.test(message ?? "");
790
+ });
791
+ }
792
+ async function handler$1(req, res) {
793
+ try {
794
+ const cartId = req.params.id;
795
+ if (typeof cartId !== "string" || !cartId) {
796
+ res.status(400).json({ error: "Missing cart id" });
797
+ return;
798
+ }
799
+ const config = readCommerceConfigForResponse(res);
800
+ if (!validateCommerceCartScope(req, res, config)) return;
801
+ const { storeId, clientId, apiBaseUrl } = config;
802
+ const data = await gqlRequest({
803
+ endpoint: orderStorefrontEndpoint({ apiBaseUrl }),
804
+ query: getCartOrderQuery,
805
+ variables: { id: cartId },
806
+ headers: storefrontHeaders({
807
+ storeId,
808
+ clientId
809
+ })
810
+ });
811
+ res.json({ cart: data.orderById ?? null });
812
+ } catch (error) {
813
+ if (isCartNotFoundError(error)) {
814
+ res.status(200).json({ cart: null });
815
+ return;
816
+ }
817
+ res.status(500).json({
818
+ error: "Failed to load cart",
819
+ message: error instanceof Error ? error.message : String(error)
820
+ });
821
+ }
822
+ }
823
+
824
+ //#endregion
825
+ //#region src/server/api/commerce/cart/[id]/items/[itemId]/DELETE.ts
826
+ const deleteLineItemByIdMutation = `
827
+ mutation DeleteLineItemById($id: ID!, $orderId: ID!) {
828
+ deleteLineItemById(id: $id, orderId: $orderId)
829
+ }
830
+ `;
831
+ async function handler$2(req, res) {
832
+ try {
833
+ const cartId = req.params.id;
834
+ const itemId = req.params.itemId;
835
+ if (typeof cartId !== "string" || !cartId || typeof itemId !== "string" || !itemId) {
836
+ res.status(400).json({ error: "Missing cart id or item id" });
837
+ return;
838
+ }
839
+ const config = readCommerceConfigForResponse(res);
840
+ if (!validateCommerceCartScope(req, res, config)) return;
841
+ const { storeId, clientId, apiBaseUrl } = config;
842
+ const endpoint = orderStorefrontEndpoint({ apiBaseUrl });
843
+ const headers = storefrontHeaders({
844
+ storeId,
845
+ clientId
846
+ });
847
+ await gqlRequest({
848
+ endpoint,
849
+ query: deleteLineItemByIdMutation,
850
+ variables: {
851
+ id: itemId,
852
+ orderId: cartId
853
+ },
854
+ headers
855
+ });
856
+ const hydrated = await gqlRequest({
857
+ endpoint,
858
+ query: getCartOrderQuery,
859
+ variables: { id: cartId },
860
+ headers
861
+ });
862
+ res.json({ cart: hydrated.orderById ?? null });
863
+ } catch (error) {
864
+ res.status(500).json({
865
+ error: "Failed to delete line item",
866
+ message: error instanceof Error ? error.message : String(error)
867
+ });
868
+ }
869
+ }
870
+
871
+ //#endregion
872
+ //#region src/server/api/commerce/cart/[id]/items/[itemId]/PATCH.ts
873
+ const updateLineItemByIdMutation = `
874
+ mutation UpdateLineItemById($input: UpdateLineItemByIdInput!) {
875
+ updateLineItemById(input: $input) {
876
+ id
877
+ }
878
+ }
879
+ `;
880
+ async function handler$3(req, res) {
881
+ try {
882
+ const cartId = req.params.id;
883
+ const itemId = req.params.itemId;
884
+ if (typeof cartId !== "string" || !cartId || typeof itemId !== "string" || !itemId) {
885
+ res.status(400).json({ error: "Missing cart id or item id" });
886
+ return;
887
+ }
888
+ const body = req.body ?? {};
889
+ const config = readCommerceConfigForResponse(res);
890
+ if (!validateCommerceCartScope(req, res, config)) return;
891
+ const { storeId, clientId, apiBaseUrl } = config;
892
+ const endpoint = orderStorefrontEndpoint({ apiBaseUrl });
893
+ const headers = storefrontHeaders({
894
+ storeId,
895
+ clientId
896
+ });
897
+ await gqlRequest({
898
+ endpoint,
899
+ query: updateLineItemByIdMutation,
900
+ variables: { input: {
901
+ id: itemId,
902
+ orderId: cartId,
903
+ name: body.name,
904
+ quantity: body.quantity,
905
+ fulfillmentMode: body.fulfillmentMode,
906
+ status: body.status,
907
+ type: body.type,
908
+ details: body.details
909
+ } },
910
+ headers
911
+ });
912
+ const hydrated = await gqlRequest({
913
+ endpoint,
914
+ query: getCartOrderQuery,
915
+ variables: { id: cartId },
916
+ headers
917
+ });
918
+ res.json({ cart: hydrated.orderById ?? null });
919
+ } catch (error) {
920
+ res.status(500).json({
921
+ error: "Failed to update line item",
922
+ message: error instanceof Error ? error.message : String(error)
923
+ });
924
+ }
925
+ }
926
+
927
+ //#endregion
928
+ //#region src/server/api/commerce/cart/[id]/items/POST.ts
929
+ const addLineItemBySkuIdMutation$1 = `
930
+ mutation AddLineItemBySkuId($input: AddLineItemInput!) {
931
+ addLineItemBySkuId(input: $input) {
932
+ id
933
+ }
934
+ }
935
+ `;
936
+ async function handler$4(req, res) {
937
+ try {
938
+ const cartId = req.params.id;
939
+ if (typeof cartId !== "string" || !cartId) {
940
+ res.status(400).json({ error: "Missing cart id" });
941
+ return;
942
+ }
943
+ const body = req.body ?? {};
944
+ if (!body.skuId || !body.name || typeof body.quantity !== "number") {
945
+ res.status(400).json({ error: "Missing required fields: skuId, name, quantity" });
946
+ return;
947
+ }
948
+ const config = readCommerceConfigForResponse(res);
949
+ if (!validateCommerceCartScope(req, res, config)) return;
950
+ const { storeId, clientId, apiBaseUrl } = config;
951
+ const endpoint = orderStorefrontEndpoint({ apiBaseUrl });
952
+ const headers = storefrontHeaders({
953
+ storeId,
954
+ clientId
955
+ });
956
+ await gqlRequest({
957
+ endpoint,
958
+ query: addLineItemBySkuIdMutation$1,
959
+ variables: { input: buildAddLineItemBySkuIdInput(cartId, {
960
+ skuId: body.skuId,
961
+ name: body.name,
962
+ quantity: body.quantity
963
+ }) },
964
+ headers
965
+ });
966
+ const hydrated = await gqlRequest({
967
+ endpoint,
968
+ query: getCartOrderQuery,
969
+ variables: { id: cartId },
970
+ headers
971
+ });
972
+ res.status(201).json({ cart: hydrated.orderById ?? null });
973
+ } catch (error) {
974
+ res.status(500).json({
975
+ error: "Failed to add line item",
976
+ message: error instanceof Error ? error.message : String(error)
977
+ });
978
+ }
979
+ }
980
+
981
+ //#endregion
982
+ //#region src/server/api/commerce/cart/POST.ts
983
+ const addCartOrderMutation = `
984
+ mutation AddCartOrder($input: AddDraftOrderInput!) {
985
+ addDraftOrder(input: $input) {
986
+ id
987
+ }
988
+ }
989
+ `;
990
+ const addLineItemBySkuIdMutation = `
991
+ mutation AddLineItemBySkuId($input: AddLineItemInput!) {
992
+ addLineItemBySkuId(input: $input) {
993
+ id
994
+ }
995
+ }
996
+ `;
997
+ async function handler$5(req, res) {
998
+ try {
999
+ const body = req.body ?? {};
1000
+ const initialItems = Array.isArray(body.lineItems) ? body.lineItems : [];
1001
+ for (const item of initialItems) if (!item.skuId || !item.name || typeof item.quantity !== "number") {
1002
+ res.status(400).json({ error: "Each lineItem must have skuId, a non-empty name, and a numeric quantity" });
1003
+ return;
1004
+ }
1005
+ const config = readCommerceConfigForResponse(res);
1006
+ if (!validateCommerceCartScope(req, res, config)) return;
1007
+ const { storeId, channelId, clientId, apiBaseUrl, currencyCode } = config;
1008
+ const endpoint = orderStorefrontEndpoint({ apiBaseUrl });
1009
+ const headers = storefrontHeaders({
1010
+ storeId,
1011
+ clientId
1012
+ });
1013
+ const newCartId = (await gqlRequest({
1014
+ endpoint,
1015
+ query: addCartOrderMutation,
1016
+ variables: { input: buildEmptyCartOrderInput({
1017
+ storeId,
1018
+ channelId,
1019
+ owner: config.owner,
1020
+ currencyCode: body.currencyCode ?? currencyCode
1021
+ }) },
1022
+ headers
1023
+ })).addDraftOrder?.id;
1024
+ if (!newCartId) {
1025
+ res.status(500).json({ error: "Failed to create cart: missing id in mutation response" });
1026
+ return;
1027
+ }
1028
+ for (const item of initialItems) await gqlRequest({
1029
+ endpoint,
1030
+ query: addLineItemBySkuIdMutation,
1031
+ variables: { input: buildAddLineItemBySkuIdInput(newCartId, item) },
1032
+ headers
1033
+ });
1034
+ const hydrated = await gqlRequest({
1035
+ endpoint,
1036
+ query: getCartOrderQuery,
1037
+ variables: { id: newCartId },
1038
+ headers
1039
+ });
1040
+ res.status(201).json({ cart: hydrated.orderById ?? null });
1041
+ } catch (error) {
1042
+ res.status(500).json({
1043
+ error: "Failed to create cart",
1044
+ message: error instanceof Error ? error.message : String(error)
1045
+ });
1046
+ }
1047
+ }
1048
+
1049
+ //#endregion
1050
+ //#region src/server/api/commerce/checkout/POST.ts
1051
+ async function handler$6(req, res) {
1052
+ try {
1053
+ const configuration = commerceConfigurationForResponse(res);
1054
+ const { draftOrderId, skuId, quantity, lineItemData, returnUrl, successUrl } = req.body ?? {};
1055
+ if (lineItemData !== void 0) {
1056
+ res.status(400).json({ error: "Non-catalog checkout must be created by the server." });
1057
+ return;
1058
+ }
1059
+ if ([draftOrderId, skuId].filter(Boolean).length !== 1 || [draftOrderId, skuId].some((value) => value !== void 0 && (typeof value !== "string" || !value.trim()))) {
1060
+ res.status(400).json({ error: "exactly one non-empty draftOrderId or skuId is required" });
1061
+ return;
1062
+ }
1063
+ if (quantity !== void 0 && (!Number.isSafeInteger(quantity) || quantity < 1)) {
1064
+ res.status(400).json({ error: "quantity must be a positive whole number" });
1065
+ return;
1066
+ }
1067
+ if (req.headers?.["x-commerce-scope"] !== void 0) {
1068
+ if (!validateCommerceCartScope(req, res, configuration.read())) return;
1069
+ }
1070
+ const validateReturnUrls = res.locals.commerceCheckoutReturnUrlValidator;
1071
+ if (!validateReturnUrls) {
1072
+ res.status(503).json({ error: "Checkout return destinations are not configured." });
1073
+ return;
1074
+ }
1075
+ const destinations = validateReturnUrls(returnUrl, successUrl);
1076
+ if (!destinations) {
1077
+ res.status(400).json({ error: "Checkout return destinations are not allowed." });
1078
+ return;
1079
+ }
1080
+ const session = await createCheckoutSession({
1081
+ draftOrderId,
1082
+ skuId,
1083
+ quantity,
1084
+ ...destinations
1085
+ }, configuration);
1086
+ res.status(200).json(session);
1087
+ } catch (error) {
1088
+ res.status(500).json({
1089
+ error: "Failed to create checkout session",
1090
+ message: error instanceof Error ? error.message : String(error)
1091
+ });
1092
+ }
1093
+ }
1094
+
1095
+ //#endregion
1096
+ //#region src/server/api/commerce/config/GET.ts
1097
+ async function handler$7(_req, res) {
1098
+ res.setHeader("Cache-Control", "no-store");
1099
+ try {
1100
+ const config = commerceConfigurationForResponse(res).read();
1101
+ res.json({
1102
+ cartScope: getCommerceCartScope(config),
1103
+ currencyCode: config.currencyCode
1104
+ });
1105
+ } catch (cause) {
1106
+ res.status(503).json({
1107
+ error: "Commerce configuration is unavailable. Complete the store connection before continuing.",
1108
+ message: cause instanceof Error ? cause.message : "Invalid Commerce configuration."
1109
+ });
1110
+ }
1111
+ }
1112
+
1113
+ //#endregion
1114
+ //#region src/server/api/commerce/order-status/GET.ts
1115
+ async function handler$8(req, res) {
1116
+ try {
1117
+ const { orderId } = req.query;
1118
+ if (!orderId || typeof orderId !== "string") {
1119
+ res.status(400).json({
1120
+ success: false,
1121
+ error: "missing or invalid orderId query parameter"
1122
+ });
1123
+ return;
1124
+ }
1125
+ const order = await getOrderStatus(orderId, commerceConfigurationForResponse(res));
1126
+ res.status(200).json({
1127
+ success: true,
1128
+ order
1129
+ });
1130
+ } catch (error) {
1131
+ res.status(500).json({
1132
+ success: false,
1133
+ error: "Failed to get order status",
1134
+ message: error instanceof Error ? error.message : String(error)
1135
+ });
1136
+ }
1137
+ }
1138
+
1139
+ //#endregion
1140
+ //#region src/lib/commerce/catalog-subgraph.ts
1141
+ function catalogStorefrontEndpoint({ storeId, apiBaseUrl }) {
1142
+ return new URL(`/v2/commerce/stores/${storeId}/catalog-subgraph/storefront`, apiBaseUrl).toString();
1143
+ }
1144
+ function buildSkuGroupsVariables(input) {
1145
+ const productIds = input.productIds?.filter(Boolean) ?? [];
1146
+ const categoryIds = input.categoryIds?.filter(Boolean) ?? [];
1147
+ const hasExplicitFilters = productIds.length > 0 || categoryIds.length > 0;
1148
+ return {
1149
+ ...input.first !== void 0 && { first: input.first },
1150
+ ...input.after && { after: input.after },
1151
+ ...productIds.length > 0 && { id: { in: [...productIds] } },
1152
+ ...categoryIds.length > 0 && { listId: { in: [...categoryIds] } },
1153
+ ...!hasExplicitFilters && input.searchQuery && { label: { contains: input.searchQuery } }
1154
+ };
1155
+ }
1156
+ function buildSkuGroupVariables({ productId, selectedAttributeValues, skuGroupFirst }) {
1157
+ const attributeValues = selectedAttributeValues ? [...selectedAttributeValues] : [];
1158
+ return {
1159
+ id: productId,
1160
+ attributeValues,
1161
+ ...!attributeValues.length && { first: skuGroupFirst ?? 50 }
1162
+ };
1163
+ }
1164
+
1165
+ //#endregion
1166
+ //#region src/server/api/commerce/products/[id]/GET.ts
1167
+ const skuGroupQuery = `
1168
+ query SkuGroup($id: String!, $first: Int, $attributeValues: [String!] = []) {
1169
+ skuGroup(id: $id) {
1170
+ id
1171
+ status
1172
+ name
1173
+ label
1174
+ description
1175
+ htmlDescription
1176
+ type
1177
+ priceRange(status: { eq: "ACTIVE" }) {
1178
+ min
1179
+ max
1180
+ }
1181
+ compareAtPriceRange(status: { eq: "ACTIVE" }) {
1182
+ min
1183
+ max
1184
+ }
1185
+ mediaObjects(first: 25) {
1186
+ edges {
1187
+ node {
1188
+ url
1189
+ type
1190
+ }
1191
+ }
1192
+ }
1193
+ attributes(first: 50, orderBy: { position: ASC }) {
1194
+ edges {
1195
+ node {
1196
+ id
1197
+ name
1198
+ label
1199
+ description
1200
+ htmlDescription
1201
+ values(first: 50, orderBy: { position: ASC }) {
1202
+ edges {
1203
+ node {
1204
+ id
1205
+ name
1206
+ label
1207
+ }
1208
+ }
1209
+ }
1210
+ }
1211
+ }
1212
+ }
1213
+ skus(attributeValues: { has: $attributeValues }, first: $first, status: { eq: "ACTIVE" }) {
1214
+ pageInfo { hasNextPage }
1215
+ totalCount
1216
+ edges {
1217
+ node {
1218
+ id
1219
+ label
1220
+ name
1221
+ description
1222
+ prices(first: 10) {
1223
+ edges {
1224
+ node {
1225
+ value { value currencyCode }
1226
+ compareAtValue { value currencyCode }
1227
+ }
1228
+ }
1229
+ }
1230
+ mediaObjects(first: 25) {
1231
+ edges { node { url type } }
1232
+ }
1233
+ inventoryCounts {
1234
+ edges {
1235
+ node {
1236
+ id
1237
+ quantity
1238
+ type
1239
+ }
1240
+ }
1241
+ }
1242
+ }
1243
+ }
1244
+ }
1245
+ }
1246
+ }
1247
+ `;
1248
+ function asStringArray$1(value) {
1249
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
1250
+ if (typeof value === "string" && value.length > 0) return [value];
1251
+ return [];
1252
+ }
1253
+ function asNumber$1(value) {
1254
+ if (typeof value !== "string" || value.length === 0) return void 0;
1255
+ const n = Number(value);
1256
+ return Number.isFinite(n) ? n : void 0;
1257
+ }
1258
+ async function handler$9(req, res) {
1259
+ try {
1260
+ const productId = req.params.id;
1261
+ if (typeof productId !== "string" || !productId) {
1262
+ res.status(400).json({ error: "Missing product id" });
1263
+ return;
1264
+ }
1265
+ const config = readCommerceConfigForResponse(res);
1266
+ if (!validateCommerceCartScope(req, res, config)) return;
1267
+ const { storeId, clientId, apiBaseUrl } = config;
1268
+ const variables = buildSkuGroupVariables({
1269
+ productId,
1270
+ selectedAttributeValues: asStringArray$1(req.query.attributeValues),
1271
+ skuGroupFirst: asNumber$1(req.query.skuGroupFirst)
1272
+ });
1273
+ const data = await gqlRequest({
1274
+ endpoint: catalogStorefrontEndpoint({
1275
+ storeId,
1276
+ apiBaseUrl
1277
+ }),
1278
+ query: skuGroupQuery,
1279
+ variables,
1280
+ headers: storefrontHeaders({
1281
+ storeId,
1282
+ clientId
1283
+ })
1284
+ });
1285
+ res.json(data);
1286
+ } catch (error) {
1287
+ res.status(500).json({
1288
+ error: "Failed to load product",
1289
+ message: error instanceof Error ? error.message : String(error)
1290
+ });
1291
+ }
1292
+ }
1293
+
1294
+ //#endregion
1295
+ //#region src/server/api/commerce/products/GET.ts
1296
+ const skuGroupsQuery = `
1297
+ query SkuGroups($first: Int, $after: String, $id: SKUGroupIdsFilter, $listId: ListIdFilter, $label: LabelFilter) {
1298
+ skuGroups(
1299
+ first: $first
1300
+ after: $after
1301
+ id: $id
1302
+ listId: $listId
1303
+ label: $label
1304
+ status: { eq: "ACTIVE" }
1305
+ ) {
1306
+ edges {
1307
+ cursor
1308
+ node {
1309
+ id
1310
+ name
1311
+ label
1312
+ description
1313
+ htmlDescription
1314
+ type
1315
+ priceRange(status: { eq: "ACTIVE" }) {
1316
+ min
1317
+ max
1318
+ }
1319
+ compareAtPriceRange(status: { eq: "ACTIVE" }) {
1320
+ min
1321
+ max
1322
+ }
1323
+ mediaObjects(first: 25) {
1324
+ edges {
1325
+ node {
1326
+ url
1327
+ type
1328
+ }
1329
+ }
1330
+ }
1331
+ attributes(first: 50, orderBy: { position: ASC }) {
1332
+ edges {
1333
+ node {
1334
+ id
1335
+ name
1336
+ label
1337
+ description
1338
+ htmlDescription
1339
+ values(first: 50, orderBy: { position: ASC }) {
1340
+ edges {
1341
+ node {
1342
+ id
1343
+ name
1344
+ label
1345
+ }
1346
+ }
1347
+ }
1348
+ }
1349
+ }
1350
+ }
1351
+ skus(first: 2, status: { eq: "ACTIVE" }) {
1352
+ pageInfo { hasNextPage }
1353
+ totalCount
1354
+ edges {
1355
+ node {
1356
+ id
1357
+ label
1358
+ name
1359
+ inventoryCounts {
1360
+ edges {
1361
+ node {
1362
+ id
1363
+ quantity
1364
+ type
1365
+ }
1366
+ }
1367
+ }
1368
+ }
1369
+ }
1370
+ }
1371
+ }
1372
+ }
1373
+ pageInfo {
1374
+ hasNextPage
1375
+ hasPreviousPage
1376
+ startCursor
1377
+ endCursor
1378
+ }
1379
+ totalCount
1380
+ }
1381
+ }
1382
+ `;
1383
+ function asStringArray(value) {
1384
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
1385
+ if (typeof value === "string" && value.length > 0) return [value];
1386
+ return [];
1387
+ }
1388
+ function asNumber(value) {
1389
+ if (typeof value !== "string" || value.length === 0) return void 0;
1390
+ const n = Number(value);
1391
+ return Number.isFinite(n) ? n : void 0;
1392
+ }
1393
+ async function handler$10(req, res) {
1394
+ try {
1395
+ const config = readCommerceConfigForResponse(res);
1396
+ if (!validateCommerceCartScope(req, res, config)) return;
1397
+ const { storeId, clientId, apiBaseUrl } = config;
1398
+ const variables = buildSkuGroupsVariables({
1399
+ first: asNumber(req.query.first) ?? 24,
1400
+ after: typeof req.query.after === "string" ? req.query.after : void 0,
1401
+ searchQuery: typeof req.query.searchQuery === "string" ? req.query.searchQuery : void 0,
1402
+ productIds: asStringArray(req.query.productIds),
1403
+ categoryIds: asStringArray(req.query.categoryIds)
1404
+ });
1405
+ const data = await gqlRequest({
1406
+ endpoint: catalogStorefrontEndpoint({
1407
+ storeId,
1408
+ apiBaseUrl
1409
+ }),
1410
+ query: skuGroupsQuery,
1411
+ variables,
1412
+ headers: storefrontHeaders({
1413
+ storeId,
1414
+ clientId
1415
+ })
1416
+ });
1417
+ res.json(data);
1418
+ } catch (error) {
1419
+ res.status(500).json({
1420
+ error: "Failed to load products",
1421
+ message: error instanceof Error ? error.message : String(error)
1422
+ });
1423
+ }
1424
+ }
1425
+
1426
+ //#endregion
1427
+ //#region src/server/api/commerce/skus/[id]/GET.ts
1428
+ const skuQuery = `
1429
+ query Sku($id: String!) {
1430
+ sku(id: $id) {
1431
+ id
1432
+ label
1433
+ name
1434
+ description
1435
+ htmlDescription
1436
+ code
1437
+ prices {
1438
+ edges {
1439
+ node {
1440
+ id
1441
+ value {
1442
+ value
1443
+ currencyCode
1444
+ }
1445
+ compareAtValue {
1446
+ value
1447
+ currencyCode
1448
+ }
1449
+ }
1450
+ }
1451
+ }
1452
+ inventoryCounts {
1453
+ edges {
1454
+ node {
1455
+ id
1456
+ quantity
1457
+ type
1458
+ }
1459
+ }
1460
+ }
1461
+ mediaObjects {
1462
+ edges {
1463
+ node {
1464
+ id
1465
+ url
1466
+ type
1467
+ label
1468
+ position
1469
+ }
1470
+ }
1471
+ }
1472
+ attributeValues {
1473
+ edges {
1474
+ node {
1475
+ id
1476
+ name
1477
+ label
1478
+ }
1479
+ }
1480
+ }
1481
+ }
1482
+ }
1483
+ `;
1484
+ async function handler$11(req, res) {
1485
+ try {
1486
+ const skuId = req.params.id;
1487
+ if (typeof skuId !== "string" || !skuId) {
1488
+ res.status(400).json({ error: "Missing sku id" });
1489
+ return;
1490
+ }
1491
+ const config = readCommerceConfigForResponse(res);
1492
+ if (!validateCommerceCartScope(req, res, config)) return;
1493
+ const { storeId, clientId, apiBaseUrl } = config;
1494
+ const data = await gqlRequest({
1495
+ endpoint: catalogStorefrontEndpoint({
1496
+ storeId,
1497
+ apiBaseUrl
1498
+ }),
1499
+ query: skuQuery,
1500
+ variables: { id: skuId },
1501
+ headers: storefrontHeaders({
1502
+ storeId,
1503
+ clientId
1504
+ })
1505
+ });
1506
+ res.json(data);
1507
+ } catch (error) {
1508
+ res.status(500).json({
1509
+ error: "Failed to load sku",
1510
+ message: error instanceof Error ? error.message : String(error)
1511
+ });
1512
+ }
1513
+ }
1514
+
1515
+ //#endregion
1516
+ //#region src/router.ts
1517
+ function createCommerceRouter(options = {}) {
1518
+ const router = Router();
1519
+ const configuration = options.configuration ?? createRuntimeCommerceConfiguration();
1520
+ const catalogEnabled = options.features?.catalog ?? true;
1521
+ const paymentsEnabled = options.features?.payments ?? true;
1522
+ const validateCheckoutReturnUrls = options.checkoutReturnUrls ? createCheckoutReturnUrlValidator(options.checkoutReturnUrls) : void 0;
1523
+ router.use((_req, res, next) => {
1524
+ res.locals.commerceConfiguration = configuration;
1525
+ res.locals.commerceCheckoutReturnUrlValidator = validateCheckoutReturnUrls;
1526
+ next();
1527
+ });
1528
+ if (catalogEnabled) {
1529
+ router.get("/config", handler$7);
1530
+ router.get("/products", handler$10);
1531
+ router.get("/products/:id", handler$9);
1532
+ router.get("/skus/:id", handler$11);
1533
+ router.post("/cart", handler$5);
1534
+ router.get("/cart/:id", handler$1);
1535
+ router.post("/cart/:id/items", handler$4);
1536
+ router.patch("/cart/:id/items/:itemId", handler$3);
1537
+ router.delete("/cart/:id/items/:itemId", handler$2);
1538
+ router.post("/cart/:id/discounts", handler);
1539
+ }
1540
+ if (paymentsEnabled) {
1541
+ router.post("/checkout", handler$6);
1542
+ router.get("/order-status", handler$8);
1543
+ }
1544
+ return router;
1545
+ }
1546
+ function createCommerceCatalogRouter(configuration) {
1547
+ return createCommerceRouter({
1548
+ configuration,
1549
+ features: {
1550
+ catalog: true,
1551
+ payments: false
1552
+ }
1553
+ });
1554
+ }
1555
+ function createGoDaddyPaymentsRouter(configuration, checkoutReturnUrls) {
1556
+ return createCommerceRouter({
1557
+ configuration,
1558
+ checkoutReturnUrls,
1559
+ features: {
1560
+ catalog: false,
1561
+ payments: true
1562
+ }
1563
+ });
1564
+ }
1565
+
1566
+ //#endregion
1567
+ export { createCheckoutSession, createCommerceCatalogRouter, createCommerceRouter, createGoDaddyPaymentsRouter, createRuntimeCommerceConfiguration, getOrderStatus, parseCommerceCheckoutConfiguration, readCommerceConfig };