@godaddy/gd-commerce-server 0.1.1 → 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 CHANGED
@@ -56,4 +56,52 @@ Configure `checkoutReturnUrls` on the router using trusted deployment settings.
56
56
 
57
57
  Without this policy, HTTP checkout returns 503 before creating a session. Invalid request destinations return 400. This applies to both router presets that expose checkout. Trusted in-process callers of `createCheckoutSession()` own their return URLs and must construct or validate them server-side.
58
58
 
59
- A return from hosted checkout is not proof of payment. The order-status route uses the authorized Orders REST API, which supports completed orders, and returns its payment status (for example `PAID` or `PENDING`; `unknown` if absent). The server OAuth client must be granted `commerce.order:read`. The helper verifies the returned order ID, store, and channel and returns a limited summary without customer contact data. Hosts must authenticate callers and authorize access to each requested order before exposing this route.
59
+ A return from hosted checkout is not proof of payment. The order-status route uses the authorized Orders REST API, which supports completed orders, and returns its payment status (for example `PAID` or `PENDING`; the exported `ORDER_STATUS_UNKNOWN`, `'unknown'`, if absent). Show it only as display enrichment and never present an unconfirmed payment as paid. The server OAuth client must be granted `commerce.order:read`. The helper verifies the returned order ID, store, and channel and returns a limited summary without customer contact data. The route returns 400 for a missing, blank, padded, `.`, or `..` order ID (padded IDs are rejected, not trimmed) or one the Orders API rejects as malformed (422 `VALIDATION_FAILED`), and 404 when the Orders API reports the order missing (404 `NOT_FOUND`) or the order belongs to another store or channel. A 404 without that code, such as from a misconfigured API base URL, is an upstream failure. Token and other Commerce failures, including a response for a different order ID, return 502 and unreadable configuration returns 503 (see [Errors](#errors)). Hosts must authenticate callers and authorize access to each requested order before exposing this route.
60
+
61
+ ### Connection state
62
+
63
+ Hosts can implement the optional synchronous `CommerceConfiguration.readConnectionState()`
64
+ method. Its return type is exported as `CommerceConnectionState` (`unbound | connecting | ready`).
65
+ Returning `undefined` keeps the existing behavior. Throw when the state is unknown or cannot be
66
+ read. The default environment configuration does not implement this method, so missing
67
+ credentials never count as `unbound`.
68
+
69
+ `GET /config` returns `{ state: 'unbound' }` or `{ state: 'connecting' }` without reading store
70
+ credentials. `ready` includes the usual opaque `cartScope` and `currencyCode` after `read()`
71
+ succeeds. Without this method, the successful response shape is unchanged. All responses are
72
+ uncached; a malformed state or unavailable configuration returns 503. Data and checkout routes,
73
+ and the in-process checkout and order helpers, reject any state other than `ready` before
74
+ upstream calls.
75
+
76
+ The host owns the connection lifecycle. Report `ready` only after the store connection is
77
+ complete. Lost configuration is an error, never evidence of `unbound`. After a store connects,
78
+ report `unbound` again only after it is deliberately disconnected. The storefront package owns
79
+ the generic sample products and local sample cart; this server never creates sample Commerce
80
+ records or simulates checkout.
81
+
82
+ ## Errors
83
+
84
+ Every route reports failures the same way. The body is `{ "error": "<customer-facing message>", "code": "<code>", "requestId": "<id>" }` (order-status also includes `success: false`). The same id is passed to the logger. Bodies never contain upstream messages, configuration details, or credentials.
85
+
86
+ | Status | `code` | Meaning |
87
+ | --- | --- | --- |
88
+ | 400 | `invalid_request` | Missing or invalid input. |
89
+ | 404 | `not_found` | Missing order or product, or a write to an existing cart (`/cart/:id/...`) that is missing, expired, or completed. Cart reads keep returning `200 { "cart": null }`. Checkout with a stale or completed `draftOrderId` is not yet distinguished and returns 502. |
90
+ | 409 | `scope_mismatch` | `X-Commerce-Scope` no longer matches the configured store binding. |
91
+ | 502 | `upstream_unauthorized` | Commerce rejected this server's OAuth client or requested scope (HTTP 401/403, or an OAuth 400 with `invalid_client`, `invalid_grant`, `unauthorized_client`, or `invalid_scope`). It concerns server credentials, not the caller, so it is never reported as 401/403. |
92
+ | 502 | `upstream_error` | Commerce failed, returned an unexpected response, or could not be reached. |
93
+ | 503 | `not_configured` | Configuration is missing or unreadable, the host reports a connection state other than `ready`, or checkout return URLs are not configured. |
94
+ | 500 | `internal_error` | An unexpected error in this package. |
95
+
96
+ Upstream errors without a specific mapping stay 502; business errors such as invalid discount codes are not yet distinguished. 5xx detail, including upstream status and GraphQL error codes, goes to the logger:
97
+
98
+ ```ts
99
+ createCommerceRouter({
100
+ configuration,
101
+ logger: { error: (message, context) => log.error(context, message) },
102
+ // Use the host's request id when its edge sets one; unsafe or missing ids fall back to a UUID.
103
+ getRequestId: (req) => req.get('x-request-id'),
104
+ });
105
+ ```
106
+
107
+ `createCommerceCatalogRouter` and `createGoDaddyPaymentsRouter` accept the same `{ logger, getRequestId }` as their last argument. The logger defaults to `console.error`. In-process helpers throw the exported `CommerceError` subclasses (`InvalidRequestError`, `NotFoundError`, `CommerceNotConfiguredError`, `UpstreamError`), including when a host configuration throws while being read. Check `error.code` (for example `'not_found'`) rather than `instanceof`, which fails when a host has more than one copy of this package.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Router } from "express";
1
+ import { Request, Router } from "express";
2
2
 
3
3
  //#region src/lib/commerce/checkout-config.d.ts
4
4
  /** Shipping options accepted by the hosted checkout API. Omit to use the store's configuration. */
@@ -21,6 +21,86 @@ interface CheckoutReturnUrls {
21
21
  successUrls: readonly string[];
22
22
  }
23
23
  //#endregion
24
+ //#region src/lib/commerce/errors.d.ts
25
+ /**
26
+ * Typed failures for the Commerce routes and helpers.
27
+ *
28
+ * Domain code throws these; `commerceRoute` is the only place that turns them into HTTP
29
+ * responses. `message` is internal and only logged. `publicMessage` is safe to show a shopper;
30
+ * when it is absent the route's failure label is used instead.
31
+ */
32
+ type CommerceErrorCode = 'invalid_request' | 'scope_mismatch' | 'not_found' | 'not_configured' | 'upstream_unauthorized' | 'upstream_error' | 'internal_error';
33
+ interface CommerceErrorOptions {
34
+ cause?: unknown;
35
+ publicMessage?: string;
36
+ /** Internal diagnostics for the server log. Never sent to the browser. */
37
+ details?: Record<string, unknown>;
38
+ }
39
+ declare class CommerceError extends Error {
40
+ readonly httpStatus: number;
41
+ readonly code: CommerceErrorCode;
42
+ readonly publicMessage?: string;
43
+ readonly details?: Record<string, unknown>;
44
+ constructor(httpStatus: number, code: CommerceErrorCode, message: string, options?: CommerceErrorOptions);
45
+ }
46
+ interface ClientErrorOptions extends Omit<CommerceErrorOptions, 'publicMessage'> {
47
+ /** Internal message for in-process callers; defaults to the public message. */
48
+ message?: string;
49
+ }
50
+ declare class InvalidRequestError extends CommerceError {
51
+ constructor(publicMessage: string, {
52
+ message,
53
+ ...options
54
+ }?: ClientErrorOptions);
55
+ }
56
+ declare class ScopeMismatchError extends CommerceError {
57
+ constructor();
58
+ }
59
+ declare class NotFoundError extends CommerceError {
60
+ constructor(publicMessage: string, {
61
+ message,
62
+ ...options
63
+ }?: ClientErrorOptions);
64
+ }
65
+ declare class CommerceNotConfiguredError extends CommerceError {
66
+ constructor(message: string, {
67
+ publicMessage,
68
+ ...options
69
+ }?: CommerceErrorOptions);
70
+ }
71
+ interface UpstreamErrorOptions extends Omit<CommerceErrorOptions, 'publicMessage'> {
72
+ /** The upstream rejected this server's own credentials (OAuth client or scope). */
73
+ unauthorized?: boolean;
74
+ }
75
+ /**
76
+ * Commerce returned a failure, an unexpected response, or could not be reached. Always 502:
77
+ * even an upstream 401/403 concerns this server's client credentials, not the caller, and
78
+ * answering 401 would collide with the host's own authentication handling.
79
+ */
80
+ declare class UpstreamError extends CommerceError {
81
+ constructor(message: string, {
82
+ unauthorized,
83
+ ...options
84
+ }?: UpstreamErrorOptions);
85
+ }
86
+ //#endregion
87
+ //#region src/lib/commerce/commerce-route.d.ts
88
+ interface CommerceErrorLogContext {
89
+ requestId: string;
90
+ method: string;
91
+ path: string;
92
+ httpStatus: number;
93
+ code: CommerceErrorCode;
94
+ details?: Record<string, unknown>;
95
+ error: unknown;
96
+ }
97
+ /** Receives server-side failure detail that is never sent to the browser. */
98
+ interface CommerceLogger {
99
+ error(message: string, context: CommerceErrorLogContext): void;
100
+ }
101
+ /** Returns the host's id for this request (for example one set by its load balancer), if any. */
102
+ type CommerceRequestIdResolver = (req: Request) => string | undefined;
103
+ //#endregion
24
104
  //#region src/lib/commerce/config.d.ts
25
105
  interface CommerceConfig {
26
106
  /** Public OAuth client id. */
@@ -39,7 +119,10 @@ interface CommerceConfig {
39
119
  /** Optional host-owned attribution shared by draft orders and checkout. */
40
120
  owner?: string;
41
121
  }
122
+ type CommerceConnectionState = 'unbound' | 'connecting' | 'ready';
42
123
  interface CommerceConfiguration {
124
+ /** Authoritative host state. Missing support preserves legacy live/error behavior. */
125
+ readConnectionState?(): CommerceConnectionState | undefined;
43
126
  read(): CommerceConfig;
44
127
  readCheckout(): CommerceCheckoutConfiguration;
45
128
  }
@@ -56,7 +139,6 @@ declare function readCommerceConfig(options?: RuntimeCommerceConfigurationOption
56
139
  declare function createRuntimeCommerceConfiguration(options?: RuntimeCommerceConfigurationOptions): CommerceConfiguration;
57
140
  //#endregion
58
141
  //#region src/lib/commerce/checkout-subgraph.d.ts
59
-
60
142
  interface CheckoutSessionLineItemPriceDataInput {
61
143
  /** Price per unit in the currency's smallest unit (e.g. cents for USD). */
62
144
  unitAmount: number;
@@ -110,10 +192,12 @@ interface CheckoutSession {
110
192
  declare function createCheckoutSession(params: CreateCheckoutSessionParams, configuration?: CommerceConfiguration): Promise<CheckoutSession>;
111
193
  //#endregion
112
194
  //#region src/lib/commerce/get-order-status.d.ts
195
+ /** `CommerceOrderStatus.status` when the Orders API reports no payment status. */
196
+ declare const ORDER_STATUS_UNKNOWN = "unknown";
113
197
  interface CommerceOrderStatus {
114
198
  /** GoDaddy order id. */
115
199
  id: string;
116
- /** Payment status reported by Commerce, or 'unknown' when it is unavailable. */
200
+ /** Payment status reported by Commerce (e.g. `PAID`, `PENDING`), or `ORDER_STATUS_UNKNOWN`. */
117
201
  status: string;
118
202
  /** Total amount in the currency's smallest unit (cents for USD). */
119
203
  amount: number;
@@ -126,6 +210,12 @@ interface CommerceOrderStatus {
126
210
  /** Line item summaries, excluding private order metadata. */
127
211
  lineItems?: unknown[];
128
212
  }
213
+ declare class InvalidOrderIdError extends InvalidRequestError {
214
+ constructor();
215
+ }
216
+ declare class OrderNotFoundError extends NotFoundError {
217
+ constructor();
218
+ }
129
219
  declare function getOrderStatus(orderId: string, configuration?: CommerceConfiguration): Promise<CommerceOrderStatus>;
130
220
  //#endregion
131
221
  //#region src/router.d.ts
@@ -133,14 +223,24 @@ interface CommerceRouterFeatures {
133
223
  catalog?: boolean;
134
224
  payments?: boolean;
135
225
  }
136
- interface CreateCommerceRouterOptions {
226
+ /** Where failure detail is logged and how each request's id is chosen. */
227
+ interface CommerceRouterObservabilityOptions {
228
+ /** Receives 5xx failure detail. Defaults to `console.error`. */
229
+ logger?: CommerceLogger;
230
+ /**
231
+ * Returns the host's id for the request, for example `(req) => req.get('x-request-id')` when
232
+ * the host's edge sets that header. Invalid or missing ids fall back to a random UUID.
233
+ */
234
+ getRequestId?: CommerceRequestIdResolver;
235
+ }
236
+ interface CreateCommerceRouterOptions extends CommerceRouterObservabilityOptions {
137
237
  configuration?: CommerceConfiguration;
138
238
  features?: CommerceRouterFeatures;
139
239
  /** Required to enable browser checkout; never derived from request headers. */
140
240
  checkoutReturnUrls?: CheckoutReturnUrls;
141
241
  }
142
242
  declare function createCommerceRouter(options?: CreateCommerceRouterOptions): Router;
143
- declare function createCommerceCatalogRouter(configuration?: CommerceConfiguration): Router;
144
- declare function createGoDaddyPaymentsRouter(configuration?: CommerceConfiguration, checkoutReturnUrls?: CheckoutReturnUrls): Router;
243
+ declare function createCommerceCatalogRouter(configuration?: CommerceConfiguration, observability?: CommerceRouterObservabilityOptions): Router;
244
+ declare function createGoDaddyPaymentsRouter(configuration?: CommerceConfiguration, checkoutReturnUrls?: CheckoutReturnUrls, observability?: CommerceRouterObservabilityOptions): Router;
145
245
  //#endregion
146
- export { type CheckoutReturnUrls, type CheckoutSession, type CommerceCheckoutConfiguration, type CommerceCheckoutShippingConfiguration, type CommerceConfig, type CommerceConfiguration, type CommerceOrderStatus, type CommerceRouterFeatures, type CreateCheckoutSessionParams, type CreateCommerceRouterOptions, type RuntimeCommerceConfigurationOptions, createCheckoutSession, createCommerceCatalogRouter, createCommerceRouter, createGoDaddyPaymentsRouter, createRuntimeCommerceConfiguration, getOrderStatus, parseCommerceCheckoutConfiguration, readCommerceConfig };
246
+ export { type CheckoutReturnUrls, type CheckoutSession, type CommerceCheckoutConfiguration, type CommerceCheckoutShippingConfiguration, type CommerceConfig, type CommerceConfiguration, type CommerceConnectionState, CommerceError, type CommerceErrorCode, type CommerceErrorLogContext, type CommerceLogger, CommerceNotConfiguredError, type CommerceOrderStatus, type CommerceRequestIdResolver, type CommerceRouterFeatures, type CommerceRouterObservabilityOptions, type CreateCheckoutSessionParams, type CreateCommerceRouterOptions, InvalidOrderIdError, InvalidRequestError, NotFoundError, ORDER_STATUS_UNKNOWN, OrderNotFoundError, type RuntimeCommerceConfigurationOptions, ScopeMismatchError, UpstreamError, createCheckoutSession, createCommerceCatalogRouter, createCommerceRouter, createGoDaddyPaymentsRouter, createRuntimeCommerceConfiguration, getOrderStatus, parseCommerceCheckoutConfiguration, readCommerceConfig };