@godaddy/gd-commerce-server 0.2.0 → 0.3.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/README.md +28 -1
- package/dist/index.d.ts +104 -7
- package/dist/index.js +660 -494
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Router } from "express";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
|
|
4
4
|
//#region src/lib/commerce/checkout-config.ts
|
|
5
5
|
const DEFAULT_CHECKOUT_CONFIGURATION = {
|
|
@@ -35,6 +35,68 @@ function parseCommerceCheckoutConfiguration(raw) {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/lib/commerce/errors.ts
|
|
40
|
+
var CommerceError = class extends Error {
|
|
41
|
+
httpStatus;
|
|
42
|
+
code;
|
|
43
|
+
publicMessage;
|
|
44
|
+
details;
|
|
45
|
+
constructor(httpStatus, code, message, options = {}) {
|
|
46
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
47
|
+
this.name = "CommerceError";
|
|
48
|
+
this.httpStatus = httpStatus;
|
|
49
|
+
this.code = code;
|
|
50
|
+
this.publicMessage = options.publicMessage;
|
|
51
|
+
this.details = options.details;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var InvalidRequestError = class extends CommerceError {
|
|
55
|
+
constructor(publicMessage, { message = publicMessage,...options } = {}) {
|
|
56
|
+
super(400, "invalid_request", message, {
|
|
57
|
+
...options,
|
|
58
|
+
publicMessage
|
|
59
|
+
});
|
|
60
|
+
this.name = "InvalidRequestError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var ScopeMismatchError = class extends CommerceError {
|
|
64
|
+
constructor() {
|
|
65
|
+
const publicMessage = "The connected store changed. Reload the page before continuing.";
|
|
66
|
+
super(409, "scope_mismatch", publicMessage, { publicMessage });
|
|
67
|
+
this.name = "ScopeMismatchError";
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var NotFoundError = class extends CommerceError {
|
|
71
|
+
constructor(publicMessage, { message = publicMessage,...options } = {}) {
|
|
72
|
+
super(404, "not_found", message, {
|
|
73
|
+
...options,
|
|
74
|
+
publicMessage
|
|
75
|
+
});
|
|
76
|
+
this.name = "NotFoundError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var CommerceNotConfiguredError = class extends CommerceError {
|
|
80
|
+
constructor(message, { publicMessage = "Commerce configuration is unavailable. Complete the store connection before continuing.",...options } = {}) {
|
|
81
|
+
super(503, "not_configured", message, {
|
|
82
|
+
...options,
|
|
83
|
+
publicMessage
|
|
84
|
+
});
|
|
85
|
+
this.name = "CommerceNotConfiguredError";
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Commerce returned a failure, an unexpected response, or could not be reached. Always 502:
|
|
90
|
+
* even an upstream 401/403 concerns this server's client credentials, not the caller, and
|
|
91
|
+
* answering 401 would collide with the host's own authentication handling.
|
|
92
|
+
*/
|
|
93
|
+
var UpstreamError = class extends CommerceError {
|
|
94
|
+
constructor(message, { unauthorized = false,...options } = {}) {
|
|
95
|
+
super(502, unauthorized ? "upstream_unauthorized" : "upstream_error", message, options);
|
|
96
|
+
this.name = "UpstreamError";
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
38
100
|
//#endregion
|
|
39
101
|
//#region src/lib/commerce/config.ts
|
|
40
102
|
const DEFAULT_API_BASE_URL = "https://api.godaddy.com";
|
|
@@ -43,14 +105,14 @@ function normalizeApiBaseUrl(raw) {
|
|
|
43
105
|
try {
|
|
44
106
|
url = new URL(raw);
|
|
45
107
|
} catch {
|
|
46
|
-
throw new
|
|
108
|
+
throw new CommerceNotConfiguredError("Commerce config: apiBaseUrl must be a valid HTTPS origin.");
|
|
47
109
|
}
|
|
48
|
-
if (url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw new
|
|
110
|
+
if (url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw new CommerceNotConfiguredError("Commerce config: apiBaseUrl must be an HTTPS origin without credentials, path, query, or fragment.");
|
|
49
111
|
return url.origin;
|
|
50
112
|
}
|
|
51
113
|
function requireValue(environment, key) {
|
|
52
114
|
const value = environment[key]?.trim();
|
|
53
|
-
if (!value) throw new
|
|
115
|
+
if (!value) throw new CommerceNotConfiguredError(`Commerce config: ${key} is missing. Configure it in the server environment.`);
|
|
54
116
|
return value;
|
|
55
117
|
}
|
|
56
118
|
/** Read server environment values on each call. Loading files or secret stores belongs to the host. */
|
|
@@ -73,20 +135,81 @@ function createRuntimeCommerceConfiguration(options = {}) {
|
|
|
73
135
|
readCheckout: () => parseCommerceCheckoutConfiguration((options.environment ?? process.env).GODADDY_CHECKOUT_CONFIGURATION)
|
|
74
136
|
};
|
|
75
137
|
}
|
|
138
|
+
function fromHost(read) {
|
|
139
|
+
try {
|
|
140
|
+
return read();
|
|
141
|
+
} catch (cause) {
|
|
142
|
+
if (cause instanceof CommerceError) throw cause;
|
|
143
|
+
throw new CommerceNotConfiguredError("Commerce configuration could not be read", { cause });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const CONNECTION_STATES = new Set([
|
|
147
|
+
"unbound",
|
|
148
|
+
"connecting",
|
|
149
|
+
"ready"
|
|
150
|
+
]);
|
|
151
|
+
/** The host-reported connection state, or `undefined` when the host does not report one. */
|
|
152
|
+
function readCommerceConnectionState(configuration) {
|
|
153
|
+
const state = fromHost(() => configuration.readConnectionState?.());
|
|
154
|
+
if (state !== void 0 && !CONNECTION_STATES.has(state)) throw new CommerceNotConfiguredError("Invalid Commerce connection state.");
|
|
155
|
+
return state;
|
|
156
|
+
}
|
|
157
|
+
/** Nonready connection states must never use leftover credentials. */
|
|
158
|
+
function assertCommerceConnectionReady(configuration) {
|
|
159
|
+
const state = readCommerceConnectionState(configuration);
|
|
160
|
+
if (state === "unbound" || state === "connecting") throw new CommerceNotConfiguredError("Commerce store connection is not ready.", { details: { connectionState: state } });
|
|
161
|
+
}
|
|
162
|
+
function readReadyCommerceConfig(configuration) {
|
|
163
|
+
assertCommerceConnectionReady(configuration);
|
|
164
|
+
return fromHost(() => configuration.read());
|
|
165
|
+
}
|
|
166
|
+
function readReadyCheckoutConfig(configuration) {
|
|
167
|
+
assertCommerceConnectionReady(configuration);
|
|
168
|
+
return fromHost(() => configuration.readCheckout());
|
|
169
|
+
}
|
|
76
170
|
function commerceConfigurationForResponse(res) {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
return createRuntimeCommerceConfiguration();
|
|
171
|
+
const supplied = res.locals.commerceConfiguration;
|
|
172
|
+
return supplied && typeof supplied === "object" && "read" in supplied && typeof supplied.read === "function" && "readCheckout" in supplied && typeof supplied.readCheckout === "function" ? supplied : createRuntimeCommerceConfiguration();
|
|
80
173
|
}
|
|
81
174
|
function readCommerceConfigForResponse(res) {
|
|
82
|
-
|
|
83
|
-
assertCommerceConnectionReady(configuration);
|
|
84
|
-
return configuration.read();
|
|
175
|
+
return readReadyCommerceConfig(commerceConfigurationForResponse(res));
|
|
85
176
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/lib/commerce/upstream-http.ts
|
|
180
|
+
async function fetchUpstream(input, init, { operation, details }, fetchImplementation = fetch) {
|
|
181
|
+
try {
|
|
182
|
+
return await fetchImplementation(input, init);
|
|
183
|
+
} catch (cause) {
|
|
184
|
+
throw new UpstreamError(`${operation} could not reach Commerce`, {
|
|
185
|
+
cause,
|
|
186
|
+
details
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function readUpstreamJson(response, { operation, details }) {
|
|
191
|
+
try {
|
|
192
|
+
return await response.json();
|
|
193
|
+
} catch (cause) {
|
|
194
|
+
throw new UpstreamError(`${operation} returned a non-JSON response`, {
|
|
195
|
+
cause,
|
|
196
|
+
details: {
|
|
197
|
+
...details,
|
|
198
|
+
upstreamStatus: response.status
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** Builds the error for a non-2xx response. 401 and 403 always mean this server's credentials were rejected. */
|
|
204
|
+
async function upstreamStatusError(response, { operation, details }, { unauthorized = false } = {}) {
|
|
205
|
+
if (!response.bodyUsed) await response.body?.cancel();
|
|
206
|
+
return new UpstreamError(`${operation} failed: upstream returned ${response.status}`, {
|
|
207
|
+
unauthorized: unauthorized || response.status === 401 || response.status === 403,
|
|
208
|
+
details: {
|
|
209
|
+
...details,
|
|
210
|
+
upstreamStatus: response.status
|
|
211
|
+
}
|
|
212
|
+
});
|
|
90
213
|
}
|
|
91
214
|
|
|
92
215
|
//#endregion
|
|
@@ -128,22 +251,52 @@ function checkoutGraphqlEndpoint({ apiBaseUrl }) {
|
|
|
128
251
|
function authorizationHeaders({ accessToken }) {
|
|
129
252
|
return { Authorization: `Bearer ${accessToken}` };
|
|
130
253
|
}
|
|
254
|
+
const UNAUTHORIZED_OAUTH_ERRORS = new Set([
|
|
255
|
+
"invalid_client",
|
|
256
|
+
"invalid_grant",
|
|
257
|
+
"unauthorized_client",
|
|
258
|
+
"invalid_scope"
|
|
259
|
+
]);
|
|
260
|
+
async function readOAuthError(response) {
|
|
261
|
+
try {
|
|
262
|
+
const body = await response.json();
|
|
263
|
+
return body && typeof body === "object" && "error" in body && typeof body.error === "string" ? body.error : void 0;
|
|
264
|
+
} catch {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
131
268
|
async function getOAuthAccessToken({ clientId, clientSecret, apiBaseUrl, scope = "commerce.product:read", fetch: fetchImplementation }) {
|
|
132
|
-
if (!clientId || !clientSecret) throw new
|
|
133
|
-
const requestFetch = fetchImplementation ?? fetch;
|
|
269
|
+
if (!clientId || !clientSecret) throw new CommerceNotConfiguredError("clientId and clientSecret are required");
|
|
134
270
|
const body = new URLSearchParams();
|
|
135
271
|
body.append("grant_type", "client_credentials");
|
|
136
272
|
body.append("client_id", clientId);
|
|
137
273
|
body.append("client_secret", clientSecret);
|
|
138
274
|
body.append("scope", scope);
|
|
139
|
-
const
|
|
275
|
+
const endpoint = new URL("/v2/oauth2/token", apiBaseUrl);
|
|
276
|
+
const request = {
|
|
277
|
+
operation: "Access token request",
|
|
278
|
+
details: {
|
|
279
|
+
endpoint: endpoint.href,
|
|
280
|
+
scope
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
const response = await fetchUpstream(endpoint, {
|
|
140
284
|
method: "POST",
|
|
141
285
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
142
286
|
body: body.toString(),
|
|
143
287
|
cache: "no-store"
|
|
144
|
-
});
|
|
145
|
-
if (!response.ok)
|
|
146
|
-
|
|
288
|
+
}, request, fetchImplementation);
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
const oauthError = response.status === 400 ? await readOAuthError(response) : void 0;
|
|
291
|
+
throw await upstreamStatusError(response, {
|
|
292
|
+
...request,
|
|
293
|
+
details: {
|
|
294
|
+
...request.details,
|
|
295
|
+
oauthError
|
|
296
|
+
}
|
|
297
|
+
}, { unauthorized: oauthError !== void 0 && UNAUTHORIZED_OAUTH_ERRORS.has(oauthError) });
|
|
298
|
+
}
|
|
299
|
+
return readUpstreamJson(response, request);
|
|
147
300
|
}
|
|
148
301
|
function buildBuyNowCheckoutInput({ storeId, skuId, quantity = 1, returnUrl, successUrl, channelId }, overrides = {}) {
|
|
149
302
|
return applyCheckoutDefaults({
|
|
@@ -181,10 +334,18 @@ function buildNonCatalogCheckoutInput({ storeId, lineItemData, quantity = 1, ret
|
|
|
181
334
|
|
|
182
335
|
//#endregion
|
|
183
336
|
//#region src/lib/commerce/gql.ts
|
|
184
|
-
|
|
337
|
+
/** `status` is the upstream HTTP status; the route responds with `httpStatus` (502) unless mapped. */
|
|
338
|
+
var GraphQLErrorWithCodes = class extends UpstreamError {
|
|
185
339
|
constructor(errors, status) {
|
|
186
340
|
const errorMessage = errors.length === 1 ? `${errors[0]?.message || "Unknown error"}` : errors.map((error) => error.message).filter(Boolean).join("; ");
|
|
187
|
-
super(errorMessage
|
|
341
|
+
super(errorMessage, {
|
|
342
|
+
unauthorized: [status, ...errors.map((error) => error.status)].some((value) => value === 401 || value === 403),
|
|
343
|
+
details: {
|
|
344
|
+
upstreamStatus: status,
|
|
345
|
+
upstreamCodes: errors.map((error) => error.code).filter(Boolean),
|
|
346
|
+
upstreamStatuses: errors.map((error) => error.status).filter((value) => typeof value === "number")
|
|
347
|
+
}
|
|
348
|
+
});
|
|
188
349
|
this.errors = errors;
|
|
189
350
|
this.status = status;
|
|
190
351
|
this.name = "GraphQLErrorWithCodes";
|
|
@@ -209,7 +370,7 @@ async function gqlRequest({ endpoint, query, variables, headers: headersInit, fe
|
|
|
209
370
|
const requestHeaders = new Headers(headersInit);
|
|
210
371
|
requestHeaders.set("Accept", "application/json");
|
|
211
372
|
requestHeaders.set("Content-Type", "application/json");
|
|
212
|
-
const response = await (
|
|
373
|
+
const response = await fetchUpstream(endpoint, {
|
|
213
374
|
method: "POST",
|
|
214
375
|
headers: requestHeaders,
|
|
215
376
|
body: JSON.stringify({
|
|
@@ -217,7 +378,10 @@ async function gqlRequest({ endpoint, query, variables, headers: headersInit, fe
|
|
|
217
378
|
variables: variables ?? {}
|
|
218
379
|
}),
|
|
219
380
|
cache: "no-store"
|
|
220
|
-
}
|
|
381
|
+
}, {
|
|
382
|
+
operation: "GraphQL request",
|
|
383
|
+
details: { endpoint }
|
|
384
|
+
}, fetchImplementation);
|
|
221
385
|
let result;
|
|
222
386
|
try {
|
|
223
387
|
result = await response.json();
|
|
@@ -236,7 +400,7 @@ async function gqlRequest({ endpoint, query, variables, headers: headersInit, fe
|
|
|
236
400
|
message: `GraphQL request failed: ${response.status} ${response.statusText}`,
|
|
237
401
|
status: response.status
|
|
238
402
|
}], response.status);
|
|
239
|
-
if (result.data === void 0) throw new
|
|
403
|
+
if (result.data === void 0) throw new UpstreamError("GraphQL response did not include data", { details: { endpoint } });
|
|
240
404
|
return result.data;
|
|
241
405
|
}
|
|
242
406
|
|
|
@@ -331,15 +495,14 @@ function promotionCodesEnabled(configuration) {
|
|
|
331
495
|
}
|
|
332
496
|
async function createCheckoutSession(params, configuration = createRuntimeCommerceConfiguration()) {
|
|
333
497
|
const { draftOrderId, skuId, quantity, lineItemData, returnUrl, successUrl } = params;
|
|
334
|
-
if (!returnUrl || !successUrl) throw new
|
|
498
|
+
if (!returnUrl || !successUrl) throw new InvalidRequestError("createCheckoutSession: returnUrl and successUrl are required");
|
|
335
499
|
if ([
|
|
336
500
|
draftOrderId,
|
|
337
501
|
skuId,
|
|
338
502
|
lineItemData
|
|
339
|
-
].filter(Boolean).length !== 1) throw new
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
const checkoutConfiguration = configuration.readCheckout();
|
|
503
|
+
].filter(Boolean).length !== 1) throw new InvalidRequestError("createCheckoutSession: exactly one of draftOrderId, skuId, or lineItemData is required");
|
|
504
|
+
const { storeId, channelId, clientId, clientSecret, apiBaseUrl, sourceApp, owner, currencyCode: configCurrencyCode } = readReadyCommerceConfig(configuration);
|
|
505
|
+
const checkoutConfiguration = readReadyCheckoutConfig(configuration);
|
|
343
506
|
const enablePromotionCodes = promotionCodesEnabled(checkoutConfiguration);
|
|
344
507
|
const catalogShippingEnabled = lineItemData === void 0 && checkoutConfiguration.enableShipping;
|
|
345
508
|
const token = await getOAuthAccessToken({
|
|
@@ -402,16 +565,20 @@ async function createCheckoutSession(params, configuration = createRuntimeCommer
|
|
|
402
565
|
headers: authorizationHeaders({ accessToken: token.access_token })
|
|
403
566
|
});
|
|
404
567
|
} catch (error) {
|
|
405
|
-
throw new
|
|
568
|
+
throw new UpstreamError("Commerce checkout session could not be created", {
|
|
569
|
+
cause: error,
|
|
570
|
+
unauthorized: error instanceof UpstreamError && error.code === "upstream_unauthorized",
|
|
571
|
+
details: error instanceof UpstreamError ? error.details : void 0
|
|
572
|
+
});
|
|
406
573
|
}
|
|
407
574
|
const session = result.createCheckoutSession;
|
|
408
|
-
if (!session?.url || !session.id) throw new
|
|
409
|
-
if (session.storeId !== storeId || session.channelId !== channelId) throw new
|
|
575
|
+
if (!session?.url || !session.id) throw new UpstreamError("Checkout session was not created");
|
|
576
|
+
if (session.storeId !== storeId || session.channelId !== channelId) throw new UpstreamError(`Checkout session binding mismatch: expected store ${storeId} and channel ${channelId}, received store ${session.storeId ?? "missing"} and channel ${session.channelId ?? "missing"}`);
|
|
410
577
|
const expectedShipping = lineItemData === void 0 && checkoutConfiguration.enableShipping;
|
|
411
|
-
if (lineItemData === void 0 && enablePromotionCodes && session.enablePromotionCodes !== true) throw new
|
|
412
|
-
if (checkoutConfiguration.enableTaxCollection && session.enableTaxCollection !== true) throw new
|
|
413
|
-
if (expectedShipping && (session.enableShipping !== true || session.enableShippingAddressCollection !== true)) throw new
|
|
414
|
-
if (!session.paymentMethods?.card?.processor) throw new
|
|
578
|
+
if (lineItemData === void 0 && enablePromotionCodes && session.enablePromotionCodes !== true) throw new UpstreamError("Checkout session did not enable configured promotion codes");
|
|
579
|
+
if (checkoutConfiguration.enableTaxCollection && session.enableTaxCollection !== true) throw new UpstreamError("Checkout session did not enable configured tax collection");
|
|
580
|
+
if (expectedShipping && (session.enableShipping !== true || session.enableShippingAddressCollection !== true)) throw new UpstreamError("Checkout session did not enable configured shipping and address collection");
|
|
581
|
+
if (!session.paymentMethods?.card?.processor) throw new UpstreamError("Checkout session did not configure payment methods.");
|
|
415
582
|
return {
|
|
416
583
|
url: session.url,
|
|
417
584
|
id: session.id,
|
|
@@ -426,32 +593,73 @@ async function createCheckoutSession(params, configuration = createRuntimeCommer
|
|
|
426
593
|
|
|
427
594
|
//#endregion
|
|
428
595
|
//#region src/lib/commerce/get-order-status.ts
|
|
596
|
+
/** `CommerceOrderStatus.status` when the Orders API reports no payment status. */
|
|
597
|
+
const ORDER_STATUS_UNKNOWN = "unknown";
|
|
598
|
+
var InvalidOrderIdError = class extends InvalidRequestError {
|
|
599
|
+
constructor() {
|
|
600
|
+
super("missing or invalid orderId query parameter", { message: "getOrderStatus: a valid orderId is required" });
|
|
601
|
+
this.name = "InvalidOrderIdError";
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
var OrderNotFoundError = class extends NotFoundError {
|
|
605
|
+
constructor() {
|
|
606
|
+
super("Order not found");
|
|
607
|
+
this.name = "OrderNotFoundError";
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
function isBindingId(value) {
|
|
611
|
+
return typeof value === "string" && value !== "";
|
|
612
|
+
}
|
|
613
|
+
async function readErrorCode(response) {
|
|
614
|
+
try {
|
|
615
|
+
const body = await response.json();
|
|
616
|
+
return body && typeof body === "object" && "code" in body && typeof body.code === "string" ? body.code : null;
|
|
617
|
+
} catch {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
429
621
|
async function getOrderStatus(orderId, configuration = createRuntimeCommerceConfiguration()) {
|
|
430
|
-
if (typeof orderId !== "string" || !orderId.trim() || orderId === "." || orderId === "..") throw new
|
|
431
|
-
|
|
432
|
-
const { storeId, channelId, clientId, clientSecret, apiBaseUrl, currencyCode } = configuration.read();
|
|
622
|
+
if (typeof orderId !== "string" || !orderId || orderId !== orderId.trim() || orderId === "." || orderId === "..") throw new InvalidOrderIdError();
|
|
623
|
+
const { storeId, channelId, clientId, clientSecret, apiBaseUrl, currencyCode } = readReadyCommerceConfig(configuration);
|
|
433
624
|
const token = await getOAuthAccessToken({
|
|
434
625
|
clientId,
|
|
435
626
|
clientSecret,
|
|
436
627
|
apiBaseUrl,
|
|
437
628
|
scope: "commerce.order:read"
|
|
438
629
|
});
|
|
439
|
-
const
|
|
630
|
+
const endpoint = new URL(`/v1/commerce/stores/${encodeURIComponent(storeId)}/orders/${encodeURIComponent(orderId)}`, apiBaseUrl);
|
|
631
|
+
const request = {
|
|
632
|
+
operation: "Order lookup",
|
|
633
|
+
details: { endpoint: endpoint.href }
|
|
634
|
+
};
|
|
635
|
+
const response = await fetchUpstream(endpoint, {
|
|
440
636
|
method: "GET",
|
|
441
637
|
headers: {
|
|
442
638
|
...authorizationHeaders({ accessToken: token.access_token }),
|
|
443
639
|
Accept: "application/json"
|
|
444
640
|
},
|
|
445
641
|
cache: "no-store"
|
|
446
|
-
});
|
|
447
|
-
if (!response.ok)
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
642
|
+
}, request);
|
|
643
|
+
if (!response.ok) {
|
|
644
|
+
const code = response.status === 404 || response.status === 422 ? await readErrorCode(response) : void 0;
|
|
645
|
+
if (response.status === 404 && code === "NOT_FOUND") throw new OrderNotFoundError();
|
|
646
|
+
if (response.status === 422 && code === "VALIDATION_FAILED") throw new InvalidOrderIdError();
|
|
647
|
+
throw await upstreamStatusError(response, {
|
|
648
|
+
...request,
|
|
649
|
+
details: {
|
|
650
|
+
...request.details,
|
|
651
|
+
upstreamCode: code ?? void 0
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
const order = (await readUpstreamJson(response, request))?.order;
|
|
656
|
+
const context = order?.context;
|
|
657
|
+
if (!isBindingId(order?.id) || order.id !== orderId || !isBindingId(context?.storeId) || !isBindingId(context?.channelId)) throw new UpstreamError("Order lookup did not return the requested order", { details: { endpoint: endpoint.href } });
|
|
658
|
+
if (context.storeId !== storeId || context.channelId !== channelId) throw new OrderNotFoundError();
|
|
451
659
|
const total = order.totals?.total;
|
|
452
660
|
return {
|
|
453
661
|
id: order.id,
|
|
454
|
-
status: order.statuses?.paymentStatus ??
|
|
662
|
+
status: order.statuses?.paymentStatus ?? ORDER_STATUS_UNKNOWN,
|
|
455
663
|
amount: total?.value ?? 0,
|
|
456
664
|
currency: total?.currencyCode ?? currencyCode,
|
|
457
665
|
createdAt: order.createdAt,
|
|
@@ -505,6 +713,75 @@ function createCheckoutReturnUrlValidator(policy) {
|
|
|
505
713
|
};
|
|
506
714
|
}
|
|
507
715
|
|
|
716
|
+
//#endregion
|
|
717
|
+
//#region src/lib/commerce/commerce-route.ts
|
|
718
|
+
const consoleCommerceLogger = { error: (message, context) => console.error(message, context) };
|
|
719
|
+
const REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
720
|
+
function observabilityLocals(res) {
|
|
721
|
+
return res.locals;
|
|
722
|
+
}
|
|
723
|
+
function resolveRequestId(req, resolver) {
|
|
724
|
+
let supplied;
|
|
725
|
+
try {
|
|
726
|
+
supplied = resolver?.(req);
|
|
727
|
+
} catch {
|
|
728
|
+
supplied = void 0;
|
|
729
|
+
}
|
|
730
|
+
return typeof supplied === "string" && REQUEST_ID_PATTERN.test(supplied) ? supplied : randomUUID();
|
|
731
|
+
}
|
|
732
|
+
function requestIdFor(req, res) {
|
|
733
|
+
const locals = observabilityLocals(res);
|
|
734
|
+
locals.commerceRequestId ??= resolveRequestId(req, locals.commerceRequestIdResolver);
|
|
735
|
+
return locals.commerceRequestId;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* The single place Commerce failures become HTTP responses. Typed errors keep their status and
|
|
739
|
+
* public message; unmapped upstream failures are 502 and anything else is 500. 5xx detail goes
|
|
740
|
+
* to the configured logger, never into the body.
|
|
741
|
+
*/
|
|
742
|
+
function sendCommerceError(req, res, label, error, { failureFields = {}, classifyUpstreamError } = {}) {
|
|
743
|
+
const resolved = error instanceof UpstreamError ? classifyUpstreamError?.(error) ?? error : error instanceof CommerceError ? error : void 0;
|
|
744
|
+
const httpStatus = resolved?.httpStatus ?? 500;
|
|
745
|
+
const code = resolved?.code ?? "internal_error";
|
|
746
|
+
const requestId = requestIdFor(req, res);
|
|
747
|
+
if (httpStatus >= 500) {
|
|
748
|
+
const context = {
|
|
749
|
+
requestId,
|
|
750
|
+
method: req.method,
|
|
751
|
+
path: req.path,
|
|
752
|
+
httpStatus,
|
|
753
|
+
code,
|
|
754
|
+
...resolved?.details ? { details: resolved.details } : {},
|
|
755
|
+
error
|
|
756
|
+
};
|
|
757
|
+
try {
|
|
758
|
+
(observabilityLocals(res).commerceLogger ?? consoleCommerceLogger).error(`commerce-server: ${label}`, context);
|
|
759
|
+
} catch (loggerError) {
|
|
760
|
+
console.error(`commerce-server: ${label} (host logger failed)`, {
|
|
761
|
+
...context,
|
|
762
|
+
loggerError
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (res.headersSent) return;
|
|
767
|
+
res.status(httpStatus).json({
|
|
768
|
+
...failureFields,
|
|
769
|
+
error: resolved?.publicMessage ?? label,
|
|
770
|
+
code,
|
|
771
|
+
requestId
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
/** Wraps a route so thrown errors are answered by `sendCommerceError` with the route's label. */
|
|
775
|
+
function commerceRoute(label, handler, options = {}) {
|
|
776
|
+
return async (req, res) => {
|
|
777
|
+
try {
|
|
778
|
+
await handler(req, res);
|
|
779
|
+
} catch (error) {
|
|
780
|
+
sendCommerceError(req, res, label, error, options);
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
508
785
|
//#endregion
|
|
509
786
|
//#region src/lib/commerce/cart-scope.ts
|
|
510
787
|
/** Public cache/storage scope, not an authorization credential. */
|
|
@@ -517,11 +794,10 @@ function getCommerceCartScope(config) {
|
|
|
517
794
|
])).digest("hex").slice(0, 32);
|
|
518
795
|
}
|
|
519
796
|
/** Existing custom clients may omit the header; managed components always send it. */
|
|
520
|
-
function
|
|
797
|
+
function assertCommerceCartScope(req, config) {
|
|
521
798
|
const suppliedScope = req.headers?.["x-commerce-scope"];
|
|
522
|
-
if (suppliedScope === void 0 || suppliedScope === getCommerceCartScope(config)) return
|
|
523
|
-
|
|
524
|
-
return false;
|
|
799
|
+
if (suppliedScope === void 0 || suppliedScope === getCommerceCartScope(config)) return;
|
|
800
|
+
throw new ScopeMismatchError();
|
|
525
801
|
}
|
|
526
802
|
|
|
527
803
|
//#endregion
|
|
@@ -731,61 +1007,7 @@ const getCartOrderQuery = `
|
|
|
731
1007
|
`;
|
|
732
1008
|
|
|
733
1009
|
//#endregion
|
|
734
|
-
//#region src/
|
|
735
|
-
const applyDiscountCodesMutation = `
|
|
736
|
-
mutation ApplyDiscountCodes($input: ApplyDiscountCodesInput!) {
|
|
737
|
-
applyDiscountCodes(input: $input) {
|
|
738
|
-
id
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
`;
|
|
742
|
-
async function handler(req, res) {
|
|
743
|
-
try {
|
|
744
|
-
const cartId = req.params.id;
|
|
745
|
-
if (typeof cartId !== "string" || !cartId) {
|
|
746
|
-
res.status(400).json({ error: "Missing cart id" });
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
const body = req.body ?? {};
|
|
750
|
-
const codes = Array.isArray(body.discountCodes) ? body.discountCodes.filter((code) => typeof code === "string" && code.length > 0) : [];
|
|
751
|
-
if (codes.length === 0) {
|
|
752
|
-
res.status(400).json({ error: "discountCodes must be a non-empty string array" });
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
755
|
-
const config = readCommerceConfigForResponse(res);
|
|
756
|
-
if (!validateCommerceCartScope(req, res, config)) return;
|
|
757
|
-
const { storeId, clientId, apiBaseUrl } = config;
|
|
758
|
-
const endpoint = orderStorefrontEndpoint({ apiBaseUrl });
|
|
759
|
-
const headers = storefrontHeaders({
|
|
760
|
-
storeId,
|
|
761
|
-
clientId
|
|
762
|
-
});
|
|
763
|
-
await gqlRequest({
|
|
764
|
-
endpoint,
|
|
765
|
-
query: applyDiscountCodesMutation,
|
|
766
|
-
variables: { input: {
|
|
767
|
-
orderId: cartId,
|
|
768
|
-
discountCodes: codes
|
|
769
|
-
} },
|
|
770
|
-
headers
|
|
771
|
-
});
|
|
772
|
-
const hydrated = await gqlRequest({
|
|
773
|
-
endpoint,
|
|
774
|
-
query: getCartOrderQuery,
|
|
775
|
-
variables: { id: cartId },
|
|
776
|
-
headers
|
|
777
|
-
});
|
|
778
|
-
res.json({ cart: hydrated.orderById ?? null });
|
|
779
|
-
} catch (error) {
|
|
780
|
-
res.status(500).json({
|
|
781
|
-
error: "Failed to apply discount codes",
|
|
782
|
-
message: error instanceof Error ? error.message : String(error)
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
//#endregion
|
|
788
|
-
//#region src/server/api/commerce/cart/[id]/GET.ts
|
|
1010
|
+
//#region src/lib/commerce/upstream-errors.ts
|
|
789
1011
|
function isCartNotFoundError(error) {
|
|
790
1012
|
if (!(error instanceof GraphQLErrorWithCodes)) return false;
|
|
791
1013
|
const isFailure = (status) => status !== void 0 && status >= 400 && status !== 404 && status !== 410;
|
|
@@ -798,17 +1020,68 @@ function isCartNotFoundError(error) {
|
|
|
798
1020
|
return /\b(?:cart|(?:draft[ -])?order)\s+(?:(?:is|was|has)\s+)?(?:not found|expired)\b/i.test(message ?? "");
|
|
799
1021
|
});
|
|
800
1022
|
}
|
|
801
|
-
|
|
1023
|
+
const CART_UPSTREAM_ERROR_RULES = [{
|
|
1024
|
+
matches: isCartNotFoundError,
|
|
1025
|
+
toError: (error) => new NotFoundError("Cart not found", { cause: error })
|
|
1026
|
+
}];
|
|
1027
|
+
/** For routes acting on an existing saved cart (`/cart/:id`); creating a cart has none to miss. */
|
|
1028
|
+
function classifyCartUpstreamError(error) {
|
|
1029
|
+
return CART_UPSTREAM_ERROR_RULES.find((rule) => rule.matches(error))?.toError(error) ?? error;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
//#endregion
|
|
1033
|
+
//#region src/server/api/commerce/cart/[id]/discounts/POST.ts
|
|
1034
|
+
const applyDiscountCodesMutation = `
|
|
1035
|
+
mutation ApplyDiscountCodes($input: ApplyDiscountCodesInput!) {
|
|
1036
|
+
applyDiscountCodes(input: $input) {
|
|
1037
|
+
id
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
`;
|
|
1041
|
+
async function applyDiscountCodes(req, res) {
|
|
1042
|
+
const cartId = req.params.id;
|
|
1043
|
+
if (typeof cartId !== "string" || !cartId) throw new InvalidRequestError("Missing cart id");
|
|
1044
|
+
const body = req.body ?? {};
|
|
1045
|
+
const codes = Array.isArray(body.discountCodes) ? body.discountCodes.filter((code) => typeof code === "string" && code.length > 0) : [];
|
|
1046
|
+
if (codes.length === 0) throw new InvalidRequestError("discountCodes must be a non-empty string array");
|
|
1047
|
+
const config = readCommerceConfigForResponse(res);
|
|
1048
|
+
assertCommerceCartScope(req, config);
|
|
1049
|
+
const { storeId, clientId, apiBaseUrl } = config;
|
|
1050
|
+
const endpoint = orderStorefrontEndpoint({ apiBaseUrl });
|
|
1051
|
+
const headers = storefrontHeaders({
|
|
1052
|
+
storeId,
|
|
1053
|
+
clientId
|
|
1054
|
+
});
|
|
1055
|
+
await gqlRequest({
|
|
1056
|
+
endpoint,
|
|
1057
|
+
query: applyDiscountCodesMutation,
|
|
1058
|
+
variables: { input: {
|
|
1059
|
+
orderId: cartId,
|
|
1060
|
+
discountCodes: codes
|
|
1061
|
+
} },
|
|
1062
|
+
headers
|
|
1063
|
+
});
|
|
1064
|
+
const hydrated = await gqlRequest({
|
|
1065
|
+
endpoint,
|
|
1066
|
+
query: getCartOrderQuery,
|
|
1067
|
+
variables: { id: cartId },
|
|
1068
|
+
headers
|
|
1069
|
+
});
|
|
1070
|
+
res.json({ cart: hydrated.orderById ?? null });
|
|
1071
|
+
}
|
|
1072
|
+
var POST_default = commerceRoute("Failed to apply discount codes", applyDiscountCodes, { classifyUpstreamError: classifyCartUpstreamError });
|
|
1073
|
+
|
|
1074
|
+
//#endregion
|
|
1075
|
+
//#region src/server/api/commerce/cart/[id]/GET.ts
|
|
1076
|
+
async function readCart(req, res) {
|
|
1077
|
+
const cartId = req.params.id;
|
|
1078
|
+
if (typeof cartId !== "string" || !cartId) throw new InvalidRequestError("Missing cart id");
|
|
1079
|
+
const config = readCommerceConfigForResponse(res);
|
|
1080
|
+
assertCommerceCartScope(req, config);
|
|
1081
|
+
const { storeId, clientId, apiBaseUrl } = config;
|
|
1082
|
+
let data;
|
|
802
1083
|
try {
|
|
803
|
-
|
|
804
|
-
if (typeof cartId !== "string" || !cartId) {
|
|
805
|
-
res.status(400).json({ error: "Missing cart id" });
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
const config = readCommerceConfigForResponse(res);
|
|
809
|
-
if (!validateCommerceCartScope(req, res, config)) return;
|
|
810
|
-
const { storeId, clientId, apiBaseUrl } = config;
|
|
811
|
-
const data = await gqlRequest({
|
|
1084
|
+
data = await gqlRequest({
|
|
812
1085
|
endpoint: orderStorefrontEndpoint({ apiBaseUrl }),
|
|
813
1086
|
query: getCartOrderQuery,
|
|
814
1087
|
variables: { id: cartId },
|
|
@@ -817,18 +1090,16 @@ async function handler$1(req, res) {
|
|
|
817
1090
|
clientId
|
|
818
1091
|
})
|
|
819
1092
|
});
|
|
820
|
-
res.json({ cart: data.orderById ?? null });
|
|
821
1093
|
} catch (error) {
|
|
822
1094
|
if (isCartNotFoundError(error)) {
|
|
823
1095
|
res.status(200).json({ cart: null });
|
|
824
1096
|
return;
|
|
825
1097
|
}
|
|
826
|
-
|
|
827
|
-
error: "Failed to load cart",
|
|
828
|
-
message: error instanceof Error ? error.message : String(error)
|
|
829
|
-
});
|
|
1098
|
+
throw error;
|
|
830
1099
|
}
|
|
1100
|
+
res.json({ cart: data.orderById ?? null });
|
|
831
1101
|
}
|
|
1102
|
+
var GET_default = commerceRoute("Failed to load cart", readCart, { classifyUpstreamError: classifyCartUpstreamError });
|
|
832
1103
|
|
|
833
1104
|
//#endregion
|
|
834
1105
|
//#region src/server/api/commerce/cart/[id]/items/[itemId]/DELETE.ts
|
|
@@ -837,45 +1108,36 @@ const deleteLineItemByIdMutation = `
|
|
|
837
1108
|
deleteLineItemById(id: $id, orderId: $orderId)
|
|
838
1109
|
}
|
|
839
1110
|
`;
|
|
840
|
-
async function
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
variables: { id: cartId },
|
|
869
|
-
headers
|
|
870
|
-
});
|
|
871
|
-
res.json({ cart: hydrated.orderById ?? null });
|
|
872
|
-
} catch (error) {
|
|
873
|
-
res.status(500).json({
|
|
874
|
-
error: "Failed to delete line item",
|
|
875
|
-
message: error instanceof Error ? error.message : String(error)
|
|
876
|
-
});
|
|
877
|
-
}
|
|
1111
|
+
async function deleteLineItem(req, res) {
|
|
1112
|
+
const cartId = req.params.id;
|
|
1113
|
+
const itemId = req.params.itemId;
|
|
1114
|
+
if (typeof cartId !== "string" || !cartId || typeof itemId !== "string" || !itemId) throw new InvalidRequestError("Missing cart id or item id");
|
|
1115
|
+
const config = readCommerceConfigForResponse(res);
|
|
1116
|
+
assertCommerceCartScope(req, config);
|
|
1117
|
+
const { storeId, clientId, apiBaseUrl } = config;
|
|
1118
|
+
const endpoint = orderStorefrontEndpoint({ apiBaseUrl });
|
|
1119
|
+
const headers = storefrontHeaders({
|
|
1120
|
+
storeId,
|
|
1121
|
+
clientId
|
|
1122
|
+
});
|
|
1123
|
+
await gqlRequest({
|
|
1124
|
+
endpoint,
|
|
1125
|
+
query: deleteLineItemByIdMutation,
|
|
1126
|
+
variables: {
|
|
1127
|
+
id: itemId,
|
|
1128
|
+
orderId: cartId
|
|
1129
|
+
},
|
|
1130
|
+
headers
|
|
1131
|
+
});
|
|
1132
|
+
const hydrated = await gqlRequest({
|
|
1133
|
+
endpoint,
|
|
1134
|
+
query: getCartOrderQuery,
|
|
1135
|
+
variables: { id: cartId },
|
|
1136
|
+
headers
|
|
1137
|
+
});
|
|
1138
|
+
res.json({ cart: hydrated.orderById ?? null });
|
|
878
1139
|
}
|
|
1140
|
+
var DELETE_default = commerceRoute("Failed to delete line item", deleteLineItem, { classifyUpstreamError: classifyCartUpstreamError });
|
|
879
1141
|
|
|
880
1142
|
//#endregion
|
|
881
1143
|
//#region src/server/api/commerce/cart/[id]/items/[itemId]/PATCH.ts
|
|
@@ -886,52 +1148,43 @@ const updateLineItemByIdMutation = `
|
|
|
886
1148
|
}
|
|
887
1149
|
}
|
|
888
1150
|
`;
|
|
889
|
-
async function
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
variables: { id: cartId },
|
|
925
|
-
headers
|
|
926
|
-
});
|
|
927
|
-
res.json({ cart: hydrated.orderById ?? null });
|
|
928
|
-
} catch (error) {
|
|
929
|
-
res.status(500).json({
|
|
930
|
-
error: "Failed to update line item",
|
|
931
|
-
message: error instanceof Error ? error.message : String(error)
|
|
932
|
-
});
|
|
933
|
-
}
|
|
1151
|
+
async function updateLineItem(req, res) {
|
|
1152
|
+
const cartId = req.params.id;
|
|
1153
|
+
const itemId = req.params.itemId;
|
|
1154
|
+
if (typeof cartId !== "string" || !cartId || typeof itemId !== "string" || !itemId) throw new InvalidRequestError("Missing cart id or item id");
|
|
1155
|
+
const body = req.body ?? {};
|
|
1156
|
+
const config = readCommerceConfigForResponse(res);
|
|
1157
|
+
assertCommerceCartScope(req, config);
|
|
1158
|
+
const { storeId, clientId, apiBaseUrl } = config;
|
|
1159
|
+
const endpoint = orderStorefrontEndpoint({ apiBaseUrl });
|
|
1160
|
+
const headers = storefrontHeaders({
|
|
1161
|
+
storeId,
|
|
1162
|
+
clientId
|
|
1163
|
+
});
|
|
1164
|
+
await gqlRequest({
|
|
1165
|
+
endpoint,
|
|
1166
|
+
query: updateLineItemByIdMutation,
|
|
1167
|
+
variables: { input: {
|
|
1168
|
+
id: itemId,
|
|
1169
|
+
orderId: cartId,
|
|
1170
|
+
name: body.name,
|
|
1171
|
+
quantity: body.quantity,
|
|
1172
|
+
fulfillmentMode: body.fulfillmentMode,
|
|
1173
|
+
status: body.status,
|
|
1174
|
+
type: body.type,
|
|
1175
|
+
details: body.details
|
|
1176
|
+
} },
|
|
1177
|
+
headers
|
|
1178
|
+
});
|
|
1179
|
+
const hydrated = await gqlRequest({
|
|
1180
|
+
endpoint,
|
|
1181
|
+
query: getCartOrderQuery,
|
|
1182
|
+
variables: { id: cartId },
|
|
1183
|
+
headers
|
|
1184
|
+
});
|
|
1185
|
+
res.json({ cart: hydrated.orderById ?? null });
|
|
934
1186
|
}
|
|
1187
|
+
var PATCH_default = commerceRoute("Failed to update line item", updateLineItem, { classifyUpstreamError: classifyCartUpstreamError });
|
|
935
1188
|
|
|
936
1189
|
//#endregion
|
|
937
1190
|
//#region src/server/api/commerce/cart/[id]/items/POST.ts
|
|
@@ -942,50 +1195,38 @@ const addLineItemBySkuIdMutation$1 = `
|
|
|
942
1195
|
}
|
|
943
1196
|
}
|
|
944
1197
|
`;
|
|
945
|
-
async function
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
const hydrated = await gqlRequest({
|
|
976
|
-
endpoint,
|
|
977
|
-
query: getCartOrderQuery,
|
|
978
|
-
variables: { id: cartId },
|
|
979
|
-
headers
|
|
980
|
-
});
|
|
981
|
-
res.status(201).json({ cart: hydrated.orderById ?? null });
|
|
982
|
-
} catch (error) {
|
|
983
|
-
res.status(500).json({
|
|
984
|
-
error: "Failed to add line item",
|
|
985
|
-
message: error instanceof Error ? error.message : String(error)
|
|
986
|
-
});
|
|
987
|
-
}
|
|
1198
|
+
async function addLineItem(req, res) {
|
|
1199
|
+
const cartId = req.params.id;
|
|
1200
|
+
if (typeof cartId !== "string" || !cartId) throw new InvalidRequestError("Missing cart id");
|
|
1201
|
+
const body = req.body ?? {};
|
|
1202
|
+
if (!body.skuId || !body.name || typeof body.quantity !== "number") throw new InvalidRequestError("Missing required fields: skuId, name, quantity");
|
|
1203
|
+
const config = readCommerceConfigForResponse(res);
|
|
1204
|
+
assertCommerceCartScope(req, config);
|
|
1205
|
+
const { storeId, clientId, apiBaseUrl } = config;
|
|
1206
|
+
const endpoint = orderStorefrontEndpoint({ apiBaseUrl });
|
|
1207
|
+
const headers = storefrontHeaders({
|
|
1208
|
+
storeId,
|
|
1209
|
+
clientId
|
|
1210
|
+
});
|
|
1211
|
+
await gqlRequest({
|
|
1212
|
+
endpoint,
|
|
1213
|
+
query: addLineItemBySkuIdMutation$1,
|
|
1214
|
+
variables: { input: buildAddLineItemBySkuIdInput(cartId, {
|
|
1215
|
+
skuId: body.skuId,
|
|
1216
|
+
name: body.name,
|
|
1217
|
+
quantity: body.quantity
|
|
1218
|
+
}) },
|
|
1219
|
+
headers
|
|
1220
|
+
});
|
|
1221
|
+
const hydrated = await gqlRequest({
|
|
1222
|
+
endpoint,
|
|
1223
|
+
query: getCartOrderQuery,
|
|
1224
|
+
variables: { id: cartId },
|
|
1225
|
+
headers
|
|
1226
|
+
});
|
|
1227
|
+
res.status(201).json({ cart: hydrated.orderById ?? null });
|
|
988
1228
|
}
|
|
1229
|
+
var POST_default$1 = commerceRoute("Failed to add line item", addLineItem, { classifyUpstreamError: classifyCartUpstreamError });
|
|
989
1230
|
|
|
990
1231
|
//#endregion
|
|
991
1232
|
//#region src/server/api/commerce/cart/POST.ts
|
|
@@ -1003,156 +1244,100 @@ const addLineItemBySkuIdMutation = `
|
|
|
1003
1244
|
}
|
|
1004
1245
|
}
|
|
1005
1246
|
`;
|
|
1006
|
-
async function
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1247
|
+
async function createCart(req, res) {
|
|
1248
|
+
const body = req.body ?? {};
|
|
1249
|
+
const initialItems = Array.isArray(body.lineItems) ? body.lineItems : [];
|
|
1250
|
+
for (const item of initialItems) if (!item.skuId || !item.name || typeof item.quantity !== "number") throw new InvalidRequestError("Each lineItem must have skuId, a non-empty name, and a numeric quantity");
|
|
1251
|
+
const config = readCommerceConfigForResponse(res);
|
|
1252
|
+
assertCommerceCartScope(req, config);
|
|
1253
|
+
const { storeId, channelId, clientId, apiBaseUrl, currencyCode } = config;
|
|
1254
|
+
const endpoint = orderStorefrontEndpoint({ apiBaseUrl });
|
|
1255
|
+
const headers = storefrontHeaders({
|
|
1256
|
+
storeId,
|
|
1257
|
+
clientId
|
|
1258
|
+
});
|
|
1259
|
+
const newCartId = (await gqlRequest({
|
|
1260
|
+
endpoint,
|
|
1261
|
+
query: addCartOrderMutation,
|
|
1262
|
+
variables: { input: buildEmptyCartOrderInput({
|
|
1019
1263
|
storeId,
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
variables: { input: buildAddLineItemBySkuIdInput(newCartId, item) },
|
|
1041
|
-
headers
|
|
1042
|
-
});
|
|
1043
|
-
const hydrated = await gqlRequest({
|
|
1044
|
-
endpoint,
|
|
1045
|
-
query: getCartOrderQuery,
|
|
1046
|
-
variables: { id: newCartId },
|
|
1047
|
-
headers
|
|
1048
|
-
});
|
|
1049
|
-
res.status(201).json({ cart: hydrated.orderById ?? null });
|
|
1050
|
-
} catch (error) {
|
|
1051
|
-
res.status(500).json({
|
|
1052
|
-
error: "Failed to create cart",
|
|
1053
|
-
message: error instanceof Error ? error.message : String(error)
|
|
1054
|
-
});
|
|
1055
|
-
}
|
|
1264
|
+
channelId,
|
|
1265
|
+
owner: config.owner,
|
|
1266
|
+
currencyCode: body.currencyCode ?? currencyCode
|
|
1267
|
+
}) },
|
|
1268
|
+
headers
|
|
1269
|
+
})).addDraftOrder?.id;
|
|
1270
|
+
if (!newCartId) throw new UpstreamError("addDraftOrder response did not include a cart id");
|
|
1271
|
+
for (const item of initialItems) await gqlRequest({
|
|
1272
|
+
endpoint,
|
|
1273
|
+
query: addLineItemBySkuIdMutation,
|
|
1274
|
+
variables: { input: buildAddLineItemBySkuIdInput(newCartId, item) },
|
|
1275
|
+
headers
|
|
1276
|
+
});
|
|
1277
|
+
const hydrated = await gqlRequest({
|
|
1278
|
+
endpoint,
|
|
1279
|
+
query: getCartOrderQuery,
|
|
1280
|
+
variables: { id: newCartId },
|
|
1281
|
+
headers
|
|
1282
|
+
});
|
|
1283
|
+
res.status(201).json({ cart: hydrated.orderById ?? null });
|
|
1056
1284
|
}
|
|
1285
|
+
var POST_default$2 = commerceRoute("Failed to create cart", createCart);
|
|
1057
1286
|
|
|
1058
1287
|
//#endregion
|
|
1059
1288
|
//#region src/server/api/commerce/checkout/POST.ts
|
|
1060
|
-
async function
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
if (!validateCommerceCartScope(req, res, configuration.read())) return;
|
|
1079
|
-
}
|
|
1080
|
-
const validateReturnUrls = res.locals.commerceCheckoutReturnUrlValidator;
|
|
1081
|
-
if (!validateReturnUrls) {
|
|
1082
|
-
res.status(503).json({ error: "Checkout return destinations are not configured." });
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
const destinations = validateReturnUrls(returnUrl, successUrl);
|
|
1086
|
-
if (!destinations) {
|
|
1087
|
-
res.status(400).json({ error: "Checkout return destinations are not allowed." });
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
const session = await createCheckoutSession({
|
|
1091
|
-
draftOrderId,
|
|
1092
|
-
skuId,
|
|
1093
|
-
quantity,
|
|
1094
|
-
...destinations
|
|
1095
|
-
}, configuration);
|
|
1096
|
-
res.status(200).json(session);
|
|
1097
|
-
} catch (error) {
|
|
1098
|
-
res.status(500).json({
|
|
1099
|
-
error: "Failed to create checkout session",
|
|
1100
|
-
message: error instanceof Error ? error.message : String(error)
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1289
|
+
async function createCheckout(req, res) {
|
|
1290
|
+
const configuration = commerceConfigurationForResponse(res);
|
|
1291
|
+
const { draftOrderId, skuId, quantity, lineItemData, returnUrl, successUrl } = req.body ?? {};
|
|
1292
|
+
if (lineItemData !== void 0) throw new InvalidRequestError("Non-catalog checkout must be created by the server.");
|
|
1293
|
+
if ([draftOrderId, skuId].filter(Boolean).length !== 1 || [draftOrderId, skuId].some((value) => value !== void 0 && (typeof value !== "string" || !value.trim()))) throw new InvalidRequestError("exactly one non-empty draftOrderId or skuId is required");
|
|
1294
|
+
if (quantity !== void 0 && (!Number.isSafeInteger(quantity) || quantity < 1)) throw new InvalidRequestError("quantity must be a positive whole number");
|
|
1295
|
+
if (req.headers?.["x-commerce-scope"] !== void 0) assertCommerceCartScope(req, readReadyCommerceConfig(configuration));
|
|
1296
|
+
const validateReturnUrls = res.locals.commerceCheckoutReturnUrlValidator;
|
|
1297
|
+
if (!validateReturnUrls) throw new CommerceNotConfiguredError("Checkout return URLs are not configured on the router.", { publicMessage: "Checkout return destinations are not configured." });
|
|
1298
|
+
const destinations = validateReturnUrls(returnUrl, successUrl);
|
|
1299
|
+
if (!destinations) throw new InvalidRequestError("Checkout return destinations are not allowed.");
|
|
1300
|
+
const session = await createCheckoutSession({
|
|
1301
|
+
draftOrderId,
|
|
1302
|
+
skuId,
|
|
1303
|
+
quantity,
|
|
1304
|
+
...destinations
|
|
1305
|
+
}, configuration);
|
|
1306
|
+
res.status(200).json(session);
|
|
1103
1307
|
}
|
|
1308
|
+
var POST_default$3 = commerceRoute("Failed to create checkout session", createCheckout);
|
|
1104
1309
|
|
|
1105
1310
|
//#endregion
|
|
1106
1311
|
//#region src/server/api/commerce/config/GET.ts
|
|
1107
|
-
async function
|
|
1312
|
+
async function readPublicConfig(_req, res) {
|
|
1108
1313
|
res.setHeader("Cache-Control", "no-store");
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
if (state !== void 0 && state !== "ready") throw new Error("Invalid Commerce connection state.");
|
|
1117
|
-
const config = configuration.read();
|
|
1118
|
-
res.json({
|
|
1119
|
-
...state === "ready" ? { state } : {},
|
|
1120
|
-
cartScope: getCommerceCartScope(config),
|
|
1121
|
-
currencyCode: config.currencyCode
|
|
1122
|
-
});
|
|
1123
|
-
} catch (cause) {
|
|
1124
|
-
res.status(503).json({
|
|
1125
|
-
error: "Commerce configuration is unavailable. Complete the store connection before continuing.",
|
|
1126
|
-
message: cause instanceof Error ? cause.message : "Invalid Commerce configuration."
|
|
1127
|
-
});
|
|
1314
|
+
const configuration = commerceConfigurationForResponse(res);
|
|
1315
|
+
const state = readCommerceConnectionState(configuration);
|
|
1316
|
+
if (state === "unbound" || state === "connecting") {
|
|
1317
|
+
res.json({ state });
|
|
1318
|
+
return;
|
|
1128
1319
|
}
|
|
1320
|
+
const config = readReadyCommerceConfig(configuration);
|
|
1321
|
+
res.json({
|
|
1322
|
+
...state === "ready" ? { state } : {},
|
|
1323
|
+
cartScope: getCommerceCartScope(config),
|
|
1324
|
+
currencyCode: config.currencyCode
|
|
1325
|
+
});
|
|
1129
1326
|
}
|
|
1327
|
+
var GET_default$1 = commerceRoute("Failed to load Commerce configuration", readPublicConfig);
|
|
1130
1328
|
|
|
1131
1329
|
//#endregion
|
|
1132
1330
|
//#region src/server/api/commerce/order-status/GET.ts
|
|
1133
|
-
async function
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1143
|
-
const order = await getOrderStatus(orderId, commerceConfigurationForResponse(res));
|
|
1144
|
-
res.status(200).json({
|
|
1145
|
-
success: true,
|
|
1146
|
-
order
|
|
1147
|
-
});
|
|
1148
|
-
} catch (error) {
|
|
1149
|
-
res.status(500).json({
|
|
1150
|
-
success: false,
|
|
1151
|
-
error: "Failed to get order status",
|
|
1152
|
-
message: error instanceof Error ? error.message : String(error)
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1331
|
+
async function readOrderStatus(req, res) {
|
|
1332
|
+
const { orderId } = req.query;
|
|
1333
|
+
if (typeof orderId !== "string") throw new InvalidOrderIdError();
|
|
1334
|
+
const order = await getOrderStatus(orderId, commerceConfigurationForResponse(res));
|
|
1335
|
+
res.status(200).json({
|
|
1336
|
+
success: true,
|
|
1337
|
+
order
|
|
1338
|
+
});
|
|
1155
1339
|
}
|
|
1340
|
+
var GET_default$2 = commerceRoute("Failed to get order status", readOrderStatus, { failureFields: { success: false } });
|
|
1156
1341
|
|
|
1157
1342
|
//#endregion
|
|
1158
1343
|
//#region src/lib/commerce/catalog-subgraph.ts
|
|
@@ -1275,45 +1460,33 @@ function asNumber$1(value) {
|
|
|
1275
1460
|
const n = Number(value);
|
|
1276
1461
|
return Number.isFinite(n) ? n : void 0;
|
|
1277
1462
|
}
|
|
1278
|
-
async function
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
})
|
|
1304
|
-
});
|
|
1305
|
-
if (!data.skuGroup || !data.activeSkuGroups?.edges?.some((edge) => edge?.node?.id === productId)) {
|
|
1306
|
-
res.status(404).json({ error: "Product not found" });
|
|
1307
|
-
return;
|
|
1308
|
-
}
|
|
1309
|
-
res.json({ skuGroup: data.skuGroup });
|
|
1310
|
-
} catch (error) {
|
|
1311
|
-
res.status(500).json({
|
|
1312
|
-
error: "Failed to load product",
|
|
1313
|
-
message: error instanceof Error ? error.message : String(error)
|
|
1314
|
-
});
|
|
1315
|
-
}
|
|
1463
|
+
async function readProduct(req, res) {
|
|
1464
|
+
const productId = req.params.id;
|
|
1465
|
+
if (typeof productId !== "string" || !productId) throw new InvalidRequestError("Missing product id");
|
|
1466
|
+
const config = readCommerceConfigForResponse(res);
|
|
1467
|
+
assertCommerceCartScope(req, config);
|
|
1468
|
+
const { storeId, clientId, apiBaseUrl } = config;
|
|
1469
|
+
const variables = buildSkuGroupVariables({
|
|
1470
|
+
productId,
|
|
1471
|
+
selectedAttributeValues: asStringArray$1(req.query.attributeValues),
|
|
1472
|
+
skuGroupFirst: asNumber$1(req.query.skuGroupFirst)
|
|
1473
|
+
});
|
|
1474
|
+
const data = await gqlRequest({
|
|
1475
|
+
endpoint: catalogStorefrontEndpoint({
|
|
1476
|
+
storeId,
|
|
1477
|
+
apiBaseUrl
|
|
1478
|
+
}),
|
|
1479
|
+
query: skuGroupQuery,
|
|
1480
|
+
variables,
|
|
1481
|
+
headers: storefrontHeaders({
|
|
1482
|
+
storeId,
|
|
1483
|
+
clientId
|
|
1484
|
+
})
|
|
1485
|
+
});
|
|
1486
|
+
if (!data.skuGroup || !data.activeSkuGroups?.edges?.some((edge) => edge?.node?.id === productId)) throw new NotFoundError("Product not found");
|
|
1487
|
+
res.json({ skuGroup: data.skuGroup });
|
|
1316
1488
|
}
|
|
1489
|
+
var GET_default$3 = commerceRoute("Failed to load product", readProduct);
|
|
1317
1490
|
|
|
1318
1491
|
//#endregion
|
|
1319
1492
|
//#region src/server/api/commerce/products/GET.ts
|
|
@@ -1414,38 +1587,32 @@ function asNumber(value) {
|
|
|
1414
1587
|
const n = Number(value);
|
|
1415
1588
|
return Number.isFinite(n) ? n : void 0;
|
|
1416
1589
|
}
|
|
1417
|
-
async function
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
res.json(data);
|
|
1442
|
-
} catch (error) {
|
|
1443
|
-
res.status(500).json({
|
|
1444
|
-
error: "Failed to load products",
|
|
1445
|
-
message: error instanceof Error ? error.message : String(error)
|
|
1446
|
-
});
|
|
1447
|
-
}
|
|
1590
|
+
async function listProducts(req, res) {
|
|
1591
|
+
const config = readCommerceConfigForResponse(res);
|
|
1592
|
+
assertCommerceCartScope(req, config);
|
|
1593
|
+
const { storeId, clientId, apiBaseUrl } = config;
|
|
1594
|
+
const variables = buildSkuGroupsVariables({
|
|
1595
|
+
first: asNumber(req.query.first) ?? 24,
|
|
1596
|
+
after: typeof req.query.after === "string" ? req.query.after : void 0,
|
|
1597
|
+
searchQuery: typeof req.query.searchQuery === "string" ? req.query.searchQuery : void 0,
|
|
1598
|
+
productIds: asStringArray(req.query.productIds),
|
|
1599
|
+
categoryIds: asStringArray(req.query.categoryIds)
|
|
1600
|
+
});
|
|
1601
|
+
const data = await gqlRequest({
|
|
1602
|
+
endpoint: catalogStorefrontEndpoint({
|
|
1603
|
+
storeId,
|
|
1604
|
+
apiBaseUrl
|
|
1605
|
+
}),
|
|
1606
|
+
query: skuGroupsQuery,
|
|
1607
|
+
variables,
|
|
1608
|
+
headers: storefrontHeaders({
|
|
1609
|
+
storeId,
|
|
1610
|
+
clientId
|
|
1611
|
+
})
|
|
1612
|
+
});
|
|
1613
|
+
res.json(data);
|
|
1448
1614
|
}
|
|
1615
|
+
var GET_default$4 = commerceRoute("Failed to load products", listProducts);
|
|
1449
1616
|
|
|
1450
1617
|
//#endregion
|
|
1451
1618
|
//#region src/server/api/commerce/skus/[id]/GET.ts
|
|
@@ -1505,36 +1672,27 @@ const skuQuery = `
|
|
|
1505
1672
|
}
|
|
1506
1673
|
}
|
|
1507
1674
|
`;
|
|
1508
|
-
async function
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
clientId
|
|
1528
|
-
})
|
|
1529
|
-
});
|
|
1530
|
-
res.json(data);
|
|
1531
|
-
} catch (error) {
|
|
1532
|
-
res.status(500).json({
|
|
1533
|
-
error: "Failed to load sku",
|
|
1534
|
-
message: error instanceof Error ? error.message : String(error)
|
|
1535
|
-
});
|
|
1536
|
-
}
|
|
1675
|
+
async function readSku(req, res) {
|
|
1676
|
+
const skuId = req.params.id;
|
|
1677
|
+
if (typeof skuId !== "string" || !skuId) throw new InvalidRequestError("Missing sku id");
|
|
1678
|
+
const config = readCommerceConfigForResponse(res);
|
|
1679
|
+
assertCommerceCartScope(req, config);
|
|
1680
|
+
const { storeId, clientId, apiBaseUrl } = config;
|
|
1681
|
+
const data = await gqlRequest({
|
|
1682
|
+
endpoint: catalogStorefrontEndpoint({
|
|
1683
|
+
storeId,
|
|
1684
|
+
apiBaseUrl
|
|
1685
|
+
}),
|
|
1686
|
+
query: skuQuery,
|
|
1687
|
+
variables: { id: skuId },
|
|
1688
|
+
headers: storefrontHeaders({
|
|
1689
|
+
storeId,
|
|
1690
|
+
clientId
|
|
1691
|
+
})
|
|
1692
|
+
});
|
|
1693
|
+
res.json(data);
|
|
1537
1694
|
}
|
|
1695
|
+
var GET_default$5 = commerceRoute("Failed to load sku", readSku);
|
|
1538
1696
|
|
|
1539
1697
|
//#endregion
|
|
1540
1698
|
//#region src/router.ts
|
|
@@ -1543,32 +1701,39 @@ function createCommerceRouter(options = {}) {
|
|
|
1543
1701
|
const configuration = options.configuration ?? createRuntimeCommerceConfiguration();
|
|
1544
1702
|
const catalogEnabled = options.features?.catalog ?? true;
|
|
1545
1703
|
const paymentsEnabled = options.features?.payments ?? true;
|
|
1704
|
+
const logger = options.logger ?? consoleCommerceLogger;
|
|
1546
1705
|
const validateCheckoutReturnUrls = options.checkoutReturnUrls ? createCheckoutReturnUrlValidator(options.checkoutReturnUrls) : void 0;
|
|
1547
1706
|
router.use((_req, res, next) => {
|
|
1707
|
+
const observability = {
|
|
1708
|
+
commerceLogger: logger,
|
|
1709
|
+
commerceRequestIdResolver: options.getRequestId
|
|
1710
|
+
};
|
|
1711
|
+
Object.assign(res.locals, observability);
|
|
1548
1712
|
res.locals.commerceConfiguration = configuration;
|
|
1549
1713
|
res.locals.commerceCheckoutReturnUrlValidator = validateCheckoutReturnUrls;
|
|
1550
1714
|
next();
|
|
1551
1715
|
});
|
|
1552
1716
|
if (catalogEnabled) {
|
|
1553
|
-
router.get("/config",
|
|
1554
|
-
router.get("/products",
|
|
1555
|
-
router.get("/products/:id",
|
|
1556
|
-
router.get("/skus/:id",
|
|
1557
|
-
router.post("/cart",
|
|
1558
|
-
router.get("/cart/:id",
|
|
1559
|
-
router.post("/cart/:id/items",
|
|
1560
|
-
router.patch("/cart/:id/items/:itemId",
|
|
1561
|
-
router.delete("/cart/:id/items/:itemId",
|
|
1562
|
-
router.post("/cart/:id/discounts",
|
|
1717
|
+
router.get("/config", GET_default$1);
|
|
1718
|
+
router.get("/products", GET_default$4);
|
|
1719
|
+
router.get("/products/:id", GET_default$3);
|
|
1720
|
+
router.get("/skus/:id", GET_default$5);
|
|
1721
|
+
router.post("/cart", POST_default$2);
|
|
1722
|
+
router.get("/cart/:id", GET_default);
|
|
1723
|
+
router.post("/cart/:id/items", POST_default$1);
|
|
1724
|
+
router.patch("/cart/:id/items/:itemId", PATCH_default);
|
|
1725
|
+
router.delete("/cart/:id/items/:itemId", DELETE_default);
|
|
1726
|
+
router.post("/cart/:id/discounts", POST_default);
|
|
1563
1727
|
}
|
|
1564
1728
|
if (paymentsEnabled) {
|
|
1565
|
-
router.post("/checkout",
|
|
1566
|
-
router.get("/order-status",
|
|
1729
|
+
router.post("/checkout", POST_default$3);
|
|
1730
|
+
router.get("/order-status", GET_default$2);
|
|
1567
1731
|
}
|
|
1568
1732
|
return router;
|
|
1569
1733
|
}
|
|
1570
|
-
function createCommerceCatalogRouter(configuration) {
|
|
1734
|
+
function createCommerceCatalogRouter(configuration, observability = {}) {
|
|
1571
1735
|
return createCommerceRouter({
|
|
1736
|
+
...observability,
|
|
1572
1737
|
configuration,
|
|
1573
1738
|
features: {
|
|
1574
1739
|
catalog: true,
|
|
@@ -1576,8 +1741,9 @@ function createCommerceCatalogRouter(configuration) {
|
|
|
1576
1741
|
}
|
|
1577
1742
|
});
|
|
1578
1743
|
}
|
|
1579
|
-
function createGoDaddyPaymentsRouter(configuration, checkoutReturnUrls) {
|
|
1744
|
+
function createGoDaddyPaymentsRouter(configuration, checkoutReturnUrls, observability = {}) {
|
|
1580
1745
|
return createCommerceRouter({
|
|
1746
|
+
...observability,
|
|
1581
1747
|
configuration,
|
|
1582
1748
|
checkoutReturnUrls,
|
|
1583
1749
|
features: {
|
|
@@ -1588,4 +1754,4 @@ function createGoDaddyPaymentsRouter(configuration, checkoutReturnUrls) {
|
|
|
1588
1754
|
}
|
|
1589
1755
|
|
|
1590
1756
|
//#endregion
|
|
1591
|
-
export { createCheckoutSession, createCommerceCatalogRouter, createCommerceRouter, createGoDaddyPaymentsRouter, createRuntimeCommerceConfiguration, getOrderStatus, parseCommerceCheckoutConfiguration, readCommerceConfig };
|
|
1757
|
+
export { CommerceError, CommerceNotConfiguredError, InvalidOrderIdError, InvalidRequestError, NotFoundError, ORDER_STATUS_UNKNOWN, OrderNotFoundError, ScopeMismatchError, UpstreamError, createCheckoutSession, createCommerceCatalogRouter, createCommerceRouter, createGoDaddyPaymentsRouter, createRuntimeCommerceConfiguration, getOrderStatus, parseCommerceCheckoutConfiguration, readCommerceConfig };
|