@waffo/pancake-ts 0.7.0 → 0.9.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/CHANGELOG.md CHANGED
@@ -4,6 +4,55 @@ All notable changes to `@waffo/pancake-ts` will be documented in this file.
4
4
 
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.9.0] - 2026-05-21
8
+
9
+ Adds flat dual-key external-id fields across write inputs, response entities, and webhook payload. The same field name now appears at every layer (REST request body / REST response / webhook payload / GraphQL).
10
+
11
+ ### Added
12
+
13
+ - `CreateCheckoutSessionParams.orderMerchantExternalId` — order business identifier (optional, max 128 chars). Inherited by orders, payments, and refunds; surfaces in webhook payload (`data.orderMerchantExternalId`) and GraphQL (`Order.orderMerchantExternalId` / `Payment.orderMerchantExternalId` / `Refund.orderMerchantExternalId`).
14
+ - `CreateRefundTicketParams.refundTicketMerchantExternalId` — refund-ticket business identifier (optional, max 128 chars). Inherited by the executed refund record on PSP success; surfaces in webhook payload (`data.refundTicketMerchantExternalId`) and GraphQL (`RefundTicket.refundTicketMerchantExternalId` / `Refund.refundTicketMerchantExternalId`).
15
+ - `RefundTicket.refundTicketMerchantExternalId` — response field (immutable across resubmits, max 128 chars).
16
+ - `WebhookEventData.orderMerchantExternalId` — present on order/payment events and on refund events (inherited from the related order).
17
+ - `WebhookEventData.refundTicketMerchantExternalId` — only present on `refund.*` events; coexists with `orderMerchantExternalId` on the same refund payload.
18
+
19
+ ### Notes
20
+
21
+ Non-breaking for SDK users — all five new fields are additive. The dual flat key naming is the canonical wire surface from this version onward.
22
+
23
+ ## [0.8.0] - 2026-05-17
24
+
25
+ ### Fixed
26
+
27
+ - **GraphQL queries actually return data.** Prior versions assumed a double-wrapped envelope (`{data:{data,errors,warnings}}`) and stripped one layer too many, so `result.data` was always `undefined` regardless of what the server returned. The wire is in fact the standard single-layer GraphQL envelope (`{data, errors?, warnings?}`); the SDK now returns it verbatim.
28
+ - **GraphQL queries no longer carry `X-Idempotency-Key`.** Queries are read-only; the gateway was caching them for 24h and serving stale snapshots on subsequent identical requests. Side-effect-free queries now hit the live DB on every call.
29
+ - **REST `warnings` are no longer dropped.** Every REST action endpoint can return `warnings: Notice[]` (handbook `command-layer.md`); prior `HttpClient.post()` returned only the unwrapped `data` field, throwing away migration `aiHint` notices like `update-store`'s `webhookSettings field ignored → Switch to client.webhooks.add/update/remove`.
30
+
31
+ ### Changed
32
+
33
+ - **Resource method return types widened** from `Promise<X>` to `Promise<X & { warnings?: Notice[] }>` for every REST action method (`stores`, `storeMerchants`, `onetimeProducts`, `subscriptionProducts`, `subscriptionProductGroups`, `orders`, `checkout.*`, `webhooks.add/update/remove`, `auth.issueSessionToken`, `buyer.cancelSubscription / cancelOnetimeOrder / reactivateSubscription / createRefundTicket / resubmitRefundTicket`). Existing destructuring (`const { store } = await client.stores.create(...)`) keeps working; add `warnings` to the destructure to read advisories.
34
+ - **Transport refactored**: `HttpClient.post<T>()` now returns the parsed envelope plus HTTP status (`PostResult<T> = { status, data, errors?, warnings? }`) without throwing on `errors[]`. Throw / unwrap / warnings handling moved to the resource layer via the internal `unwrapAction` helper. GraphQL resources return the envelope verbatim.
35
+ - **`GraphQLResource.query` and `BuyerGraphQL.query` pass `noIdempotency: true`** to the transport (suppresses `X-Idempotency-Key`).
36
+
37
+ ### Added
38
+
39
+ - **`Notice` type** (`{ message, layer, aiHint? }`) — unified shape used by both REST and GraphQL `errors[]` / `warnings[]`. Exported from `index.ts`.
40
+ - **`Envelope<T>` / `PostResult<T>` types** — transport-level envelope (and `PostResult` adds HTTP `status`). Exported for advanced callers.
41
+ - **`GraphQLResponse.errors[].layer?`** — optional field carrying which service stage produced the error (`"graphql"`, `"gateway"`).
42
+ - **`PostOptions.noIdempotency`** — boolean to suppress the `X-Idempotency-Key` header on a per-call basis.
43
+ - **README "Warnings (Migration Notices)" section** with REST + GraphQL examples and explicit guidance for LLM/agent consumers to act on `aiHint`.
44
+
45
+ ### Deprecated
46
+
47
+ - **`ApiError`, `ApiResponse`, `ApiSuccessResponse`, `ApiErrorResponse`** — kept as type aliases for backwards compatibility; prefer `Notice` and the new `Envelope<T>` / `PostResult<T>`.
48
+
49
+ ### Migration
50
+
51
+ - **Most callers need no changes.** Destructuring (`const { store } = ...`) still works; the new `warnings` field is optional and untouched code ignores it.
52
+ - **GraphQL callers**: if you have hacks that read `(result as any).stores` directly (bypassing the broken `result.data`), revert to `result.data.stores` — the bug that motivated the hack is gone.
53
+ - **LLM/agent consumers**: read `result.warnings?.[].aiHint` on every action — that's where the platform team puts canonical migration instructions when an API evolves (e.g. `update-store`'s deprecated `webhookSettings` field).
54
+ - **Direct `HttpClient.post` consumers** (rare; `HttpClient` is internal but reachable): return type changed from `T` to `PostResult<T>`; access `.data` to get the unwrapped payload, inspect `.errors` / `.warnings` directly. The transport no longer throws on `errors[]`.
55
+
7
56
  ## [0.7.0] - 2026-05-11
8
57
 
9
58
  ### Changed
package/README.md CHANGED
@@ -107,6 +107,7 @@ const result = await client.checkout.authenticated.create({
107
107
  buyerEmail: "customer@example.com",
108
108
  withTrial: true, // force enable trial (false = skip, omit = default rules)
109
109
  billingDetail: { country: "US", isBusiness: false },
110
+ orderMerchantExternalId: "ORDER-2026-00891", // optional, see Business-Side Identifiers below
110
111
  });
111
112
 
112
113
  // result.checkoutUrl = "https://pancake.waffo.ai/store/{slug}/checkout/{sessionId}#token={JWT}"
@@ -125,12 +126,13 @@ const result = await client.checkout.anonymous.create({
125
126
  currency: "USD",
126
127
  });
127
128
 
128
- // Also supports priceSnapshot and withTrial
129
+ // Also supports priceSnapshot, withTrial, and orderMerchantExternalId
129
130
  const result = await client.checkout.anonymous.create({
130
131
  productId: "PROD_xxx",
131
132
  currency: "USD",
132
133
  priceSnapshot: { amount: "4.99", taxCategory: "saas" },
133
134
  withTrial: false, // skip trial for this session
135
+ orderMerchantExternalId: "ORDER-2026-00891", // optional, API Key auth only
134
136
  });
135
137
 
136
138
  window.open(result.checkoutUrl, "_blank", "noopener,noreferrer");
@@ -186,6 +188,8 @@ app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
186
188
  break;
187
189
  case WebhookEventType.RefundSucceeded:
188
190
  console.log(`Refund succeeded: ${event.data.refundReason}`);
191
+ // refund.* events carry both business identifiers (see Business-Side Identifiers section)
192
+ await ledger.closeRefundTicket(event.data.refundTicketMerchantExternalId, event.data.orderMerchantExternalId);
189
193
  break;
190
194
  }
191
195
  } catch {
@@ -253,6 +257,7 @@ const { ticket } = await buyer.createRefundTicket({
253
257
  paymentId: "PAY_xxx",
254
258
  reason: "Product not as described",
255
259
  requestedAmount: { amount: "29.00", currency: "USD" },
260
+ refundTicketMerchantExternalId: "REF-2026-00012", // optional, see Business-Side Identifiers below
256
261
  });
257
262
 
258
263
  // Resubmit a rejected refund ticket
@@ -273,6 +278,17 @@ The token is scoped to the specified store and buyer identity — buyers can onl
273
278
 
274
279
  > **Note**: This uses the same `buyerIdentity` as `checkout.authenticated.create()`. Orders placed via authenticated checkout are automatically tied to this identity, so the buyer can manage them later with a token issued here.
275
280
 
281
+ ## Business-Side Identifiers
282
+
283
+ Attach your own internal references to a checkout or a refund ticket so cross-system reconciliation does not require Waffo IDs. Two flat keys, both optional (max 128 chars):
284
+
285
+ | Field | Attach at | Inherited by |
286
+ | -------------------------------- | ------------------------------------------- | ---------------------------------------------------------- |
287
+ | `orderMerchantExternalId` | `checkout.{authenticated,anonymous}.create` | `Order`, `Payment` (incl. subscription renewals), `Refund` |
288
+ | `refundTicketMerchantExternalId` | `buyer.createRefundTicket` | `RefundTicket`, `Refund` |
289
+
290
+ The same field name appears at every layer it surfaces: request body, response entity, webhook payload (`data.orderMerchantExternalId` / `data.refundTicketMerchantExternalId`), and every GraphQL type that carries the value. A `refund.*` webhook event carries **both** keys (order key inherited from the originating order). Query by either key via GraphQL filters — see [GraphQL Guide](docs/graphql-guide.md).
291
+
276
292
  ## GraphQL — Typed Queries
277
293
 
278
294
  ```typescript
@@ -301,10 +317,46 @@ const detail = await client.graphql.query({
301
317
  }`,
302
318
  variables: { id: "STO_xxx" },
303
319
  });
320
+
321
+ // Look up by your business-side identifier (see Business-Side Identifiers above)
322
+ const byRef = await client.graphql.query({
323
+ query: `query ($ref: String!) {
324
+ payments(filter: { orderMerchantExternalId: { eq: $ref } }) {
325
+ id orderId status orderMerchantExternalId
326
+ }
327
+ }`,
328
+ variables: { ref: "ORDER-2026-00891" },
329
+ });
304
330
  ```
305
331
 
306
332
  See [GraphQL Guide](docs/graphql-guide.md) for filters, analytics queries, delivery logs, and more.
307
333
 
334
+ ## Warnings (Migration Notices)
335
+
336
+ Every successful REST action and GraphQL query may carry a `warnings` array alongside the data. Warnings describe non-fatal advisories the server wants you to act on — typically deprecated parameters, fields scheduled for removal, or new APIs you should switch to. Each `Notice` has `message` (human-readable), `layer` (which service produced it), and `aiHint` (a structured migration instruction aimed at LLM consumers).
337
+
338
+ ```typescript
339
+ // REST action — warnings spread onto the result alongside the typed payload
340
+ const { store, warnings } = await client.stores.update({
341
+ id: "STO_xxx",
342
+ webhookSettings: { ... }, // deprecated input
343
+ });
344
+ if (warnings) {
345
+ for (const w of warnings) {
346
+ console.warn(`[${w.layer}] ${w.message}`, w.aiHint);
347
+ // e.g. layer=store, aiHint="Switch to client.webhooks.add / update / remove"
348
+ }
349
+ }
350
+
351
+ // GraphQL — warnings sit on the envelope alongside data and errors
352
+ const result = await client.graphql.query<StoresQuery>({
353
+ query: `query { stores { id } }`,
354
+ });
355
+ result.warnings?.forEach(w => console.warn(w.message, w.aiHint));
356
+ ```
357
+
358
+ **LLM/agent consumers**: always check `aiHint` on every warning — it is the canonical migration instruction (npm package, version, method name, endpoint path) the platform team intends for you to follow when the underlying API evolves.
359
+
308
360
  ## Programmatic Store & Product Management
309
361
 
310
362
  > Most merchants manage stores and products in the [Dashboard](https://pancake.waffo.ai/dashboard). The following APIs are for merchants who need programmatic automation.
package/dist/index.cjs CHANGED
@@ -65,12 +65,10 @@ var BuyerHttpClient = class {
65
65
  this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
66
66
  }
67
67
  /**
68
- * Send a Bearer-authenticated POST request and return the parsed `data` field.
68
+ * Send a Bearer-authenticated POST and return the full envelope plus HTTP status.
69
69
  *
70
- * @param path - API path
71
- * @param body - Request body object
72
- * @returns Parsed `data` field from the response
73
- * @throws {WaffoPancakeError} When the API returns errors
70
+ * Does NOT throw on `errors[]` or non-2xx status — caller inspects the result.
71
+ * Throws {@link WaffoPancakeError} only when the response body is not valid JSON.
74
72
  */
75
73
  async post(path, body) {
76
74
  const response = await this._fetch(`${this.baseUrl}${path}`, {
@@ -81,11 +79,13 @@ var BuyerHttpClient = class {
81
79
  },
82
80
  body: JSON.stringify(body)
83
81
  });
84
- const result = await response.json();
85
- if ("errors" in result && result.errors) {
86
- throw new WaffoPancakeError(response.status, result.errors);
82
+ let envelope;
83
+ try {
84
+ envelope = await response.json();
85
+ } catch {
86
+ throw new WaffoPancakeError(response.status, [{ message: `Non-JSON response from ${path}`, layer: "sdk" }]);
87
87
  }
88
- return result.data;
88
+ return { status: response.status, ...envelope };
89
89
  }
90
90
  };
91
91
 
@@ -201,48 +201,64 @@ var HttpClient = class {
201
201
  this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
202
202
  }
203
203
  /**
204
- * Send a signed POST request and return the parsed `data` field.
204
+ * Send a signed POST and return the full envelope plus HTTP status.
205
205
  *
206
206
  * Behavior:
207
- * - Generates a deterministic `X-Idempotency-Key` from `merchantId + path + body` (same request produces same key)
208
- * - When `idempotencyWindow` is set, a floored timestamp is mixed into the key so identical params produce
209
- * a new key after the window elapses (useful for checkout where repeated creation is intentional)
210
- * - Auto-builds RSA-SHA256 signature (`X-Merchant-Id` / `X-Timestamp` / `X-Signature`)
211
- * - Unwraps the response envelope: returns `data` on success, throws `WaffoPancakeError` on failure
212
- *
213
- * @param path - API path (e.g. `/v1/actions/store/create-store`)
207
+ * - Builds RSA-SHA256 signature (`X-Merchant-Id` / `X-Timestamp` / `X-Signature`)
208
+ * - Attaches `X-Idempotency-Key` (deterministic `sha256(merchantId + path + body)`)
209
+ * unless `options.noIdempotency` is set
210
+ * - When `options.idempotencyWindow` is set, a floored timestamp is mixed into the
211
+ * key so identical params produce a new key after the window elapses
212
+ * - Does NOT throw on `errors[]` or non-2xx status — caller inspects the result
213
+ * - Throws {@link WaffoPancakeError} only on transport failures (non-JSON body)
214
+ *
215
+ * @param path - API path (e.g. `/v1/actions/store/create-store`, `/v1/graphql`)
214
216
  * @param body - Request body object
215
217
  * @param options - Optional settings
216
- * @param options.idempotencyWindow - Time window in seconds for idempotency key rotation (e.g. 60 = per-minute dedup)
217
- * @returns Parsed `data` field from the response
218
- * @throws {WaffoPancakeError} When the API returns errors
218
+ * @returns Parsed envelope with HTTP status
219
+ * @throws {WaffoPancakeError} When the response body is not valid JSON
219
220
  */
220
221
  async post(path, body, options) {
221
222
  const bodyStr = JSON.stringify(body);
222
- const now = Date.now();
223
- const timestampSec = Math.floor(now / 1e3);
223
+ const timestampSec = Math.floor(Date.now() / 1e3);
224
224
  const timestamp = timestampSec.toString();
225
225
  const signature = signRequest("POST", path, timestamp, bodyStr, this.privateKey);
226
- const idempotencyBase = `${this.merchantId}:${path}:${bodyStr}`;
227
- const idempotencyInput = options?.idempotencyWindow ? `${idempotencyBase}:${Math.floor(timestampSec / options.idempotencyWindow)}` : idempotencyBase;
226
+ const headers = {
227
+ "Content-Type": "application/json",
228
+ "X-Merchant-Id": this.merchantId,
229
+ "X-Timestamp": timestamp,
230
+ "X-Signature": signature
231
+ };
232
+ if (!options?.noIdempotency) {
233
+ headers["X-Idempotency-Key"] = computeIdempotencyKey(this.merchantId, path, bodyStr, timestampSec, options);
234
+ }
228
235
  const response = await this._fetch(`${this.baseUrl}${path}`, {
229
236
  method: "POST",
230
- headers: {
231
- "Content-Type": "application/json",
232
- "X-Merchant-Id": this.merchantId,
233
- "X-Timestamp": timestamp,
234
- "X-Signature": signature,
235
- "X-Idempotency-Key": (0, import_node_crypto2.createHash)("sha256").update(idempotencyInput).digest("hex")
236
- },
237
+ headers,
237
238
  body: bodyStr
238
239
  });
239
- const result = await response.json();
240
- if ("errors" in result && result.errors) {
241
- throw new WaffoPancakeError(response.status, result.errors);
240
+ let envelope;
241
+ try {
242
+ envelope = await response.json();
243
+ } catch {
244
+ throw new WaffoPancakeError(response.status, [{ message: `Non-JSON response from ${path}`, layer: "sdk" }]);
242
245
  }
243
- return result.data;
246
+ return { status: response.status, ...envelope };
244
247
  }
245
248
  };
249
+ function computeIdempotencyKey(merchantId, path, bodyStr, timestampSec, options) {
250
+ const base = `${merchantId}:${path}:${bodyStr}`;
251
+ const input = options?.idempotencyWindow ? `${base}:${Math.floor(timestampSec / options.idempotencyWindow)}` : base;
252
+ return (0, import_node_crypto2.createHash)("sha256").update(input).digest("hex");
253
+ }
254
+
255
+ // src/resources/internal.ts
256
+ function unwrapAction(r) {
257
+ if (r.errors?.length) {
258
+ throw new WaffoPancakeError(r.status, r.errors);
259
+ }
260
+ return { ...r.data, ...r.warnings ? { warnings: r.warnings } : {} };
261
+ }
246
262
 
247
263
  // src/validation.ts
248
264
  var SHORT_ID_REGEX = /^[A-Z]{2,5}_[0-9A-Za-z]{22}$/;
@@ -297,6 +313,11 @@ function validateEnum(field, value, allowed) {
297
313
  fail(`Invalid ${field}: expected one of [${allowed.join(", ")}], got "${value}"`);
298
314
  }
299
315
  }
316
+ function validateMaxLength(field, value, max) {
317
+ if (value !== void 0 && value.length > max) {
318
+ fail(`${field} must be at most ${max} characters, got ${value.length}`);
319
+ }
320
+ }
300
321
  function validatePositiveInteger(field, value) {
301
322
  if (!Number.isInteger(value) || value <= 0) {
302
323
  fail(`Invalid ${field}: expected positive integer, got ${value}`);
@@ -339,6 +360,7 @@ function validateCheckoutCommon(params) {
339
360
  if (params.expiresInSeconds !== void 0) {
340
361
  validatePositiveInteger("expiresInSeconds", params.expiresInSeconds);
341
362
  }
363
+ validateMaxLength("orderMerchantExternalId", params.orderMerchantExternalId, 128);
342
364
  }
343
365
 
344
366
  // src/resources/auth.ts
@@ -377,7 +399,7 @@ var AuthResource = class {
377
399
  validateShortId("productId", params.productId, "PROD");
378
400
  }
379
401
  validateRequired("buyerIdentity", params.buyerIdentity);
380
- return this.http.post("/v1/actions/auth/issue-session-token", params);
402
+ return unwrapAction(await this.http.post("/v1/actions/auth/issue-session-token", params));
381
403
  }
382
404
  };
383
405
 
@@ -401,7 +423,7 @@ var BuyerSession = class {
401
423
  */
402
424
  async cancelSubscription(params) {
403
425
  validateShortId("orderId", params.orderId, "ORD");
404
- return this.http.post("/v1/actions/subscription-order/cancel-order", params);
426
+ return unwrapAction(await this.http.post("/v1/actions/subscription-order/cancel-order", params));
405
427
  }
406
428
  /**
407
429
  * Cancel a one-time order (only while payment is still pending).
@@ -414,7 +436,7 @@ var BuyerSession = class {
414
436
  */
415
437
  async cancelOnetimeOrder(params) {
416
438
  validateShortId("orderId", params.orderId, "ORD");
417
- return this.http.post("/v1/actions/onetime-order/cancel-order", params);
439
+ return unwrapAction(await this.http.post("/v1/actions/onetime-order/cancel-order", params));
418
440
  }
419
441
  /**
420
442
  * Reactivate a subscription that is in `canceling` status.
@@ -428,7 +450,7 @@ var BuyerSession = class {
428
450
  */
429
451
  async reactivateSubscription(params) {
430
452
  validateShortId("orderId", params.orderId, "ORD");
431
- return this.http.post("/v1/actions/subscription-order/reactivate-order", params);
453
+ return unwrapAction(await this.http.post("/v1/actions/subscription-order/reactivate-order", params));
432
454
  }
433
455
  /**
434
456
  * Submit a refund request for a payment.
@@ -441,6 +463,7 @@ var BuyerSession = class {
441
463
  * paymentId: "PAY_xxx",
442
464
  * reason: "Product not as described",
443
465
  * requestedAmount: { amount: "29.00", currency: "USD" },
466
+ * refundTicketMerchantExternalId: "REF-2026-00891",
444
467
  * });
445
468
  */
446
469
  async createRefundTicket(params) {
@@ -448,7 +471,8 @@ var BuyerSession = class {
448
471
  validateRequired("reason", params.reason);
449
472
  validateAmountString("requestedAmount.amount", params.requestedAmount.amount);
450
473
  validateCurrencyCode("requestedAmount.currency", params.requestedAmount.currency);
451
- return this.http.post("/v1/actions/refund-ticket/create-ticket", params);
474
+ validateMaxLength("refundTicketMerchantExternalId", params.refundTicketMerchantExternalId, 128);
475
+ return unwrapAction(await this.http.post("/v1/actions/refund-ticket/create-ticket", params));
452
476
  }
453
477
  /**
454
478
  * Resubmit a previously rejected refund ticket with updated details.
@@ -470,7 +494,7 @@ var BuyerSession = class {
470
494
  validateRequired("reason", params.reason);
471
495
  validateAmountString("requestedAmount.amount", params.requestedAmount.amount);
472
496
  validateCurrencyCode("requestedAmount.currency", params.requestedAmount.currency);
473
- return this.http.post("/v1/actions/refund-ticket/resubmit-ticket", params);
497
+ return unwrapAction(await this.http.post("/v1/actions/refund-ticket/resubmit-ticket", params));
474
498
  }
475
499
  };
476
500
  var BuyerGraphQL = class {
@@ -490,7 +514,8 @@ var BuyerGraphQL = class {
490
514
  */
491
515
  async query(params) {
492
516
  validateRequired("query", params.query);
493
- return this.http.post("/v1/graphql", params);
517
+ const result = await this.http.post("/v1/graphql", params);
518
+ return { data: result.data, errors: result.errors, warnings: result.warnings };
494
519
  }
495
520
  };
496
521
 
@@ -513,17 +538,20 @@ var CheckoutAnonymousResource = class {
513
538
  * });
514
539
  *
515
540
  * @example
516
- * // Pre-fill email and billing without issuing a session token
541
+ * // Pre-fill email + billing + attach business-side order reference
517
542
  * const result = await client.checkout.anonymous.create({
518
543
  * productId: "PROD_xxx",
519
544
  * currency: "USD",
520
545
  * buyerEmail: "customer@example.com",
521
546
  * billingDetail: { country: "US", isBusiness: false, postcode: "10001" },
547
+ * orderMerchantExternalId: "ORDER-2026-00891",
522
548
  * });
523
549
  */
524
550
  async create(params) {
525
551
  validateCheckoutCommon(params);
526
- return this.http.post("/v1/actions/checkout/create-session", params, { idempotencyWindow: 60 });
552
+ return unwrapAction(
553
+ await this.http.post("/v1/actions/checkout/create-session", params, { idempotencyWindow: 60 })
554
+ );
527
555
  }
528
556
  };
529
557
 
@@ -552,6 +580,7 @@ var CheckoutAuthenticatedResource = class {
552
580
  * currency: "USD",
553
581
  * buyerIdentity: "user-123",
554
582
  * buyerEmail: "customer@example.com",
583
+ * orderMerchantExternalId: "ORDER-2026-00891",
555
584
  * });
556
585
  * // Redirect to result.checkoutUrl (includes #token=...)
557
586
  */
@@ -570,12 +599,16 @@ var CheckoutAuthenticatedResource = class {
570
599
  ),
571
600
  this.http.post("/v1/actions/checkout/create-session", sessionParams, { idempotencyWindow: 60 })
572
601
  ]);
602
+ const token = unwrapAction(tokenResult);
603
+ const session = unwrapAction(sessionResult);
604
+ const warnings = [...token.warnings ?? [], ...session.warnings ?? []];
573
605
  return {
574
- sessionId: sessionResult.sessionId,
575
- checkoutUrl: `${sessionResult.checkoutUrl}#token=${tokenResult.token}`,
576
- expiresAt: sessionResult.expiresAt,
577
- token: tokenResult.token,
578
- tokenExpiresAt: tokenResult.expiresAt
606
+ sessionId: session.sessionId,
607
+ checkoutUrl: `${session.checkoutUrl}#token=${token.token}`,
608
+ expiresAt: session.expiresAt,
609
+ token: token.token,
610
+ tokenExpiresAt: token.expiresAt,
611
+ ...warnings.length > 0 ? { warnings } : {}
579
612
  };
580
613
  }
581
614
  };
@@ -609,7 +642,9 @@ var CheckoutResource = class {
609
642
  * // Redirect to session.checkoutUrl
610
643
  */
611
644
  async createSession(params) {
612
- return this.http.post("/v1/actions/checkout/create-session", params, { idempotencyWindow: 60 });
645
+ return unwrapAction(
646
+ await this.http.post("/v1/actions/checkout/create-session", params, { idempotencyWindow: 60 })
647
+ );
613
648
  }
614
649
  };
615
650
 
@@ -638,7 +673,8 @@ var GraphQLResource = class {
638
673
  */
639
674
  async query(params) {
640
675
  validateRequired("query", params.query);
641
- return this.http.post("/v1/graphql", params);
676
+ const result = await this.http.post("/v1/graphql", params, { noIdempotency: true });
677
+ return { data: result.data, errors: result.errors, warnings: result.warnings };
642
678
  }
643
679
  };
644
680
 
@@ -664,7 +700,7 @@ var OnetimeProductsResource = class {
664
700
  validateShortId("storeId", params.storeId, "STO");
665
701
  validateRequired("name", params.name);
666
702
  validatePrices("prices", params.prices);
667
- return this.http.post("/v1/actions/onetime-product/create-product", params);
703
+ return unwrapAction(await this.http.post("/v1/actions/onetime-product/create-product", params));
668
704
  }
669
705
  /**
670
706
  * Update a one-time product. Creates a new version; skips if unchanged.
@@ -690,7 +726,7 @@ var OnetimeProductsResource = class {
690
726
  validateShortId("id", params.id, "PROD");
691
727
  if (params.name !== void 0) validateRequired("name", params.name);
692
728
  if (params.prices) validatePrices("prices", params.prices);
693
- return this.http.post("/v1/actions/onetime-product/update-product", params);
729
+ return unwrapAction(await this.http.post("/v1/actions/onetime-product/update-product", params));
694
730
  }
695
731
  /**
696
732
  * Publish a one-time product's test version to production.
@@ -703,7 +739,7 @@ var OnetimeProductsResource = class {
703
739
  */
704
740
  async publish(params) {
705
741
  validateShortId("id", params.id, "PROD");
706
- return this.http.post("/v1/actions/onetime-product/publish-product", params);
742
+ return unwrapAction(await this.http.post("/v1/actions/onetime-product/publish-product", params));
707
743
  }
708
744
  /**
709
745
  * Update a one-time product's status (active/inactive).
@@ -720,7 +756,7 @@ var OnetimeProductsResource = class {
720
756
  async updateStatus(params) {
721
757
  validateShortId("id", params.id, "PROD");
722
758
  validateEnum("status", params.status, ["active", "inactive"]);
723
- return this.http.post("/v1/actions/onetime-product/update-status", params);
759
+ return unwrapAction(await this.http.post("/v1/actions/onetime-product/update-status", params));
724
760
  }
725
761
  };
726
762
 
@@ -746,7 +782,7 @@ var OrdersResource = class {
746
782
  */
747
783
  async cancelSubscription(params) {
748
784
  validateShortId("orderId", params.orderId, "ORD");
749
- return this.http.post("/v1/actions/subscription-order/cancel-order", params);
785
+ return unwrapAction(await this.http.post("/v1/actions/subscription-order/cancel-order", params));
750
786
  }
751
787
  };
752
788
 
@@ -772,7 +808,7 @@ var StoreMerchantsResource = class {
772
808
  validateShortId("storeId", params.storeId, "STO");
773
809
  validateRequired("email", params.email);
774
810
  validateEnum("role", params.role, ["admin", "member"]);
775
- return this.http.post("/v1/actions/store-merchant/add-merchant", params);
811
+ return unwrapAction(await this.http.post("/v1/actions/store-merchant/add-merchant", params));
776
812
  }
777
813
  /**
778
814
  * Remove a merchant from a store.
@@ -789,7 +825,7 @@ var StoreMerchantsResource = class {
789
825
  async remove(params) {
790
826
  validateShortId("storeId", params.storeId, "STO");
791
827
  validateShortId("merchantId", params.merchantId, "MER");
792
- return this.http.post("/v1/actions/store-merchant/remove-merchant", params);
828
+ return unwrapAction(await this.http.post("/v1/actions/store-merchant/remove-merchant", params));
793
829
  }
794
830
  /**
795
831
  * Update a merchant's role in a store.
@@ -808,7 +844,7 @@ var StoreMerchantsResource = class {
808
844
  validateShortId("storeId", params.storeId, "STO");
809
845
  validateShortId("merchantId", params.merchantId, "MER");
810
846
  validateEnum("role", params.role, ["admin", "member"]);
811
- return this.http.post("/v1/actions/store-merchant/update-role", params);
847
+ return unwrapAction(await this.http.post("/v1/actions/store-merchant/update-role", params));
812
848
  }
813
849
  };
814
850
 
@@ -828,7 +864,7 @@ var StoresResource = class {
828
864
  */
829
865
  async create(params) {
830
866
  validateRequired("name", params.name);
831
- return this.http.post("/v1/actions/store/create-store", params);
867
+ return unwrapAction(await this.http.post("/v1/actions/store/create-store", params));
832
868
  }
833
869
  /**
834
870
  * Update an existing store's settings.
@@ -860,7 +896,7 @@ var StoresResource = class {
860
896
  */
861
897
  async update(params) {
862
898
  validateShortId("id", params.id, "STO");
863
- return this.http.post("/v1/actions/store/update-store", params);
899
+ return unwrapAction(await this.http.post("/v1/actions/store/update-store", params));
864
900
  }
865
901
  /**
866
902
  * Soft-delete a store. Only the owner can delete.
@@ -873,7 +909,7 @@ var StoresResource = class {
873
909
  */
874
910
  async delete(params) {
875
911
  validateShortId("id", params.id, "STO");
876
- return this.http.post("/v1/actions/store/delete-store", params);
912
+ return unwrapAction(await this.http.post("/v1/actions/store/delete-store", params));
877
913
  }
878
914
  };
879
915
 
@@ -899,7 +935,9 @@ var SubscriptionProductGroupsResource = class {
899
935
  async create(params) {
900
936
  validateShortId("storeId", params.storeId, "STO");
901
937
  validateRequired("name", params.name);
902
- return this.http.post("/v1/actions/subscription-product-group/create-group", params);
938
+ return unwrapAction(
939
+ await this.http.post("/v1/actions/subscription-product-group/create-group", params)
940
+ );
903
941
  }
904
942
  /**
905
943
  * Update a subscription product group. `productIds` is a full replacement.
@@ -915,7 +953,9 @@ var SubscriptionProductGroupsResource = class {
915
953
  */
916
954
  async update(params) {
917
955
  validateRequired("id", params.id);
918
- return this.http.post("/v1/actions/subscription-product-group/update-group", params);
956
+ return unwrapAction(
957
+ await this.http.post("/v1/actions/subscription-product-group/update-group", params)
958
+ );
919
959
  }
920
960
  /**
921
961
  * Hard-delete a subscription product group.
@@ -928,7 +968,9 @@ var SubscriptionProductGroupsResource = class {
928
968
  */
929
969
  async delete(params) {
930
970
  validateRequired("id", params.id);
931
- return this.http.post("/v1/actions/subscription-product-group/delete-group", params);
971
+ return unwrapAction(
972
+ await this.http.post("/v1/actions/subscription-product-group/delete-group", params)
973
+ );
932
974
  }
933
975
  /**
934
976
  * Publish a test-environment group to production (upsert).
@@ -941,7 +983,9 @@ var SubscriptionProductGroupsResource = class {
941
983
  */
942
984
  async publish(params) {
943
985
  validateRequired("id", params.id);
944
- return this.http.post("/v1/actions/subscription-product-group/publish-group", params);
986
+ return unwrapAction(
987
+ await this.http.post("/v1/actions/subscription-product-group/publish-group", params)
988
+ );
945
989
  }
946
990
  };
947
991
 
@@ -969,7 +1013,9 @@ var SubscriptionProductsResource = class {
969
1013
  validateRequired("name", params.name);
970
1014
  validateEnum("billingPeriod", params.billingPeriod, ["weekly", "monthly", "quarterly", "yearly"]);
971
1015
  validatePrices("prices", params.prices);
972
- return this.http.post("/v1/actions/subscription-product/create-product", params);
1016
+ return unwrapAction(
1017
+ await this.http.post("/v1/actions/subscription-product/create-product", params)
1018
+ );
973
1019
  }
974
1020
  /**
975
1021
  * Update a subscription product. Creates a new version; skips if unchanged.
@@ -998,7 +1044,9 @@ var SubscriptionProductsResource = class {
998
1044
  if (params.billingPeriod !== void 0)
999
1045
  validateEnum("billingPeriod", params.billingPeriod, ["weekly", "monthly", "quarterly", "yearly"]);
1000
1046
  if (params.prices) validatePrices("prices", params.prices);
1001
- return this.http.post("/v1/actions/subscription-product/update-product", params);
1047
+ return unwrapAction(
1048
+ await this.http.post("/v1/actions/subscription-product/update-product", params)
1049
+ );
1002
1050
  }
1003
1051
  /**
1004
1052
  * Publish a subscription product's test version to production.
@@ -1011,7 +1059,9 @@ var SubscriptionProductsResource = class {
1011
1059
  */
1012
1060
  async publish(params) {
1013
1061
  validateShortId("id", params.id, "PROD");
1014
- return this.http.post("/v1/actions/subscription-product/publish-product", params);
1062
+ return unwrapAction(
1063
+ await this.http.post("/v1/actions/subscription-product/publish-product", params)
1064
+ );
1015
1065
  }
1016
1066
  /**
1017
1067
  * Update a subscription product's status (active/inactive).
@@ -1028,7 +1078,9 @@ var SubscriptionProductsResource = class {
1028
1078
  async updateStatus(params) {
1029
1079
  validateShortId("id", params.id, "PROD");
1030
1080
  validateEnum("status", params.status, ["active", "inactive"]);
1031
- return this.http.post("/v1/actions/subscription-product/update-status", params);
1081
+ return unwrapAction(
1082
+ await this.http.post("/v1/actions/subscription-product/update-status", params)
1083
+ );
1032
1084
  }
1033
1085
  };
1034
1086
 
@@ -1185,7 +1237,7 @@ var WebhooksResource = class {
1185
1237
  validateShortId("storeId", params.storeId, "STO");
1186
1238
  validateRequired("channel", params.channel);
1187
1239
  validateRequired("url", params.url);
1188
- return this.http.post("/v1/actions/store/add-webhook", params);
1240
+ return unwrapAction(await this.http.post("/v1/actions/store/add-webhook", params));
1189
1241
  }
1190
1242
  /**
1191
1243
  * Update an existing webhook (only `url`, `events`, and `secret` are mutable).
@@ -1205,7 +1257,7 @@ var WebhooksResource = class {
1205
1257
  */
1206
1258
  async update(params) {
1207
1259
  validateRequired("id", params.id);
1208
- return this.http.post("/v1/actions/store/update-webhook", params);
1260
+ return unwrapAction(await this.http.post("/v1/actions/store/update-webhook", params));
1209
1261
  }
1210
1262
  /**
1211
1263
  * Hard-delete a webhook. Historical `webhook_deliveries` rows are retained
@@ -1219,7 +1271,7 @@ var WebhooksResource = class {
1219
1271
  */
1220
1272
  async remove(params) {
1221
1273
  validateRequired("id", params.id);
1222
- return this.http.post("/v1/actions/store/remove-webhook", params);
1274
+ return unwrapAction(await this.http.post("/v1/actions/store/remove-webhook", params));
1223
1275
  }
1224
1276
  /**
1225
1277
  * Verify and parse an incoming webhook event.