@stardeck-customer-apps/testing 0.11.0 → 0.13.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/SKILL.md CHANGED
@@ -193,6 +193,21 @@ await app.payments.deliverBeamEvent(beamPost, {
193
193
  // charge-failure or expiry event for bolt payments.
194
194
  // EXPIRED is derived from expiresAt on read, never stored.
195
195
 
196
+ // Fail exactly one real SDK call, then let the next poll recover normally.
197
+ app.payments.failNext("beam.getPaymentLinkStatus", {
198
+ status: 503,
199
+ message: "Beam is temporarily unavailable",
200
+ });
201
+ await expect(payments.getPaymentLinkStatus("plink_test_1")).rejects.toMatchObject({
202
+ statusCode: 503,
203
+ });
204
+
205
+ // Network failures reject fetch instead of returning a PaymentError response.
206
+ app.payments.failNext("stripe.getCheckoutSessionStatus", {
207
+ kind: "network",
208
+ message: "connection reset",
209
+ });
210
+
196
211
  // File upload
197
212
  const storage = new StorageClient();
198
213
  await storage.upload(new File(["hi"], "note.txt", { type: "text/plain" }));
@@ -285,7 +300,14 @@ expect(app.edge.latestDisplay()?.action).toBe("show");
285
300
  - `app.issueSession(partial?)` — mint access/refresh tokens for cookie-based
286
301
  auth flows (`__stardeck:sess`).
287
302
  - `app.inbox` — emails sent through the email-sdk: `.latest(addr?)`,
288
- `.to(addr)`, `.all()`, `.count`, `.clear()`.
303
+ `.to(addr)`, `.all()`, `.count`, `.clear()`. Each captured email carries
304
+ `headers` (`{}` when none were set), so a reply that should thread onto an
305
+ inbound conversation can be asserted:
306
+
307
+ ```ts
308
+ expect(app.inbox.latest()?.headers["In-Reply-To"]).toBe("<original@mail.example>");
309
+ ```
310
+
289
311
  - `app.identities` — the platform-identity directory created through
290
312
  integrations-sdk `client.identities`: `.get(id)`, `.links(id)`, `.all()`,
291
313
  `.count`, `.clear()`. The test-only `.seedVerifiedLink(id, { kind,
@@ -295,6 +317,8 @@ externalId })` helper models a trusted platform/channel link, and
295
317
  identities through the integrations SDK.
296
318
  - `app.payments` — checkouts created through payments-sdk:
297
319
  `.checkouts`, `.latest()`, `.setProducts()`, `.markPaid(id)`,
320
+ `.setSessionStatus(id, status)`, `.setPaymentLinkStatus(id, status)`,
321
+ `.failNext(operation, failure)`,
298
322
  `.deliverStripeEvent(handler, event)`, `.deliverBeamEvent(handler, event)`,
299
323
  Bolt+ controls: `.approveBoltIntent(id)`, `.declineBoltIntent(id)`,
300
324
  `.expireBoltIntent(id)`, `.listBoltIntents()`, `.getBoltIntent(id)`,
@@ -378,7 +402,21 @@ cursor, limit })`; `list()` intentionally keeps its previous array shape.
378
402
  connections/intents/charges, product list) — captured in `app.payments`;
379
403
  fulfill checkout via `markPaid` (poll) or `deliverStripeEvent` /
380
404
  `deliverBeamEvent` (webhook push). Drive a terminal without hardware via
381
- `approveBoltIntent` / `declineBoltIntent` / `expireBoltIntent`.
405
+ `approveBoltIntent` / `declineBoltIntent` / `expireBoltIntent`. Beam payment-link
406
+ polling returns the app's original `order.referenceId`; playground project
407
+ scoping is an internal control-plane detail and must not be handled in app code.
408
+ - Payment outcomes are controllable without bypassing the SDK: Stripe sessions
409
+ support `open`, `complete`, and `expired` with `paid`, `unpaid`, or
410
+ `no_payment_required`; Beam links support `ACTIVE`, `DISABLED`, `EXPIRED`,
411
+ `PAID`, `VOIDED`, and `REFUNDED`; Bolt+ supports approve, decline, expiry, and
412
+ cancellation through the real SDK call. Use `app.payments.failNext()` for
413
+ one-shot HTTP or network failures during Stripe checkout creation/status polling
414
+ and Beam payment-link creation/status polling. The operation names are
415
+ `stripe.createCheckout`, `stripe.getCheckoutSessionStatus`,
416
+ `beam.createPaymentLink`, and `beam.getPaymentLinkStatus`. HTTP failures support
417
+ statuses 400, 402, 409, 429, 500, and 503 plus optional `code`, `details`, and
418
+ `retryAfterSeconds`; `{ kind: "network", message }` rejects fetch instead.
419
+ `app.reset()` clears pending failures.
382
420
  - `StorageClient` (upload/list/get/patch/delete, presigned upload) — captured
383
421
  in `app.storage` against the simulated `STORAGE_URL` host.
384
422
  - `client.slack` / `client.line` / `client.facebook` send endpoints — captured
package/dist/index.d.mts CHANGED
@@ -53,6 +53,11 @@ interface CapturedEmail {
53
53
  html?: string;
54
54
  text?: string;
55
55
  replyTo?: string;
56
+ /**
57
+ * Raw headers the sender attached, e.g. the `In-Reply-To` / `References` pair
58
+ * that threads a reply onto an inbound conversation. Empty when none were set.
59
+ */
60
+ headers: Record<string, string>;
56
61
  attachments: Array<{
57
62
  filename: string;
58
63
  contentType?: string;
@@ -133,6 +138,18 @@ interface CapturedCheckout {
133
138
  metadata?: Record<string, string>;
134
139
  createdAt: Date;
135
140
  }
141
+ type PaymentSimulatorOperation = "stripe.createCheckout" | "stripe.getCheckoutSessionStatus" | "beam.createPaymentLink" | "beam.getPaymentLinkStatus";
142
+ type PaymentSimulatorFailure = {
143
+ kind?: "http";
144
+ status: 400 | 402 | 409 | 429 | 500 | 503;
145
+ message: string;
146
+ code?: string;
147
+ details?: unknown;
148
+ retryAfterSeconds?: number;
149
+ } | {
150
+ kind: "network";
151
+ message: string;
152
+ };
136
153
  /** Simulated Bolt+ connection as returned by the control-plane store API. */
137
154
  interface SimBoltConnection {
138
155
  id: string;
@@ -184,6 +201,8 @@ interface TestPayments {
184
201
  get checkouts(): CapturedCheckout[];
185
202
  latest(): CapturedCheckout | undefined;
186
203
  setProducts(products: _stardeck_customer_apps_payments_sdk.Product[]): void;
204
+ /** Fail the next matching SDK request once, then resume normal simulator behavior. */
205
+ failNext(operation: PaymentSimulatorOperation, failure: PaymentSimulatorFailure): void;
187
206
  markPaid(id: string): void;
188
207
  setSessionStatus(id: string, status: Partial<_stardeck_customer_apps_payments_sdk.CheckoutSessionStatus>): void;
189
208
  setPaymentLinkStatus(id: string, status: _stardeck_customer_apps_payments_sdk.BeamPaymentLinkStatus): void;
@@ -545,4 +564,4 @@ declare const TEST_ENV_DEFAULTS: {
545
564
  /** Default location of the DDL snapshot written by `generate-types`. */
546
565
  declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
547
566
 
548
- export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
567
+ export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PaymentSimulatorFailure, type PaymentSimulatorOperation, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
package/dist/index.d.ts CHANGED
@@ -53,6 +53,11 @@ interface CapturedEmail {
53
53
  html?: string;
54
54
  text?: string;
55
55
  replyTo?: string;
56
+ /**
57
+ * Raw headers the sender attached, e.g. the `In-Reply-To` / `References` pair
58
+ * that threads a reply onto an inbound conversation. Empty when none were set.
59
+ */
60
+ headers: Record<string, string>;
56
61
  attachments: Array<{
57
62
  filename: string;
58
63
  contentType?: string;
@@ -133,6 +138,18 @@ interface CapturedCheckout {
133
138
  metadata?: Record<string, string>;
134
139
  createdAt: Date;
135
140
  }
141
+ type PaymentSimulatorOperation = "stripe.createCheckout" | "stripe.getCheckoutSessionStatus" | "beam.createPaymentLink" | "beam.getPaymentLinkStatus";
142
+ type PaymentSimulatorFailure = {
143
+ kind?: "http";
144
+ status: 400 | 402 | 409 | 429 | 500 | 503;
145
+ message: string;
146
+ code?: string;
147
+ details?: unknown;
148
+ retryAfterSeconds?: number;
149
+ } | {
150
+ kind: "network";
151
+ message: string;
152
+ };
136
153
  /** Simulated Bolt+ connection as returned by the control-plane store API. */
137
154
  interface SimBoltConnection {
138
155
  id: string;
@@ -184,6 +201,8 @@ interface TestPayments {
184
201
  get checkouts(): CapturedCheckout[];
185
202
  latest(): CapturedCheckout | undefined;
186
203
  setProducts(products: _stardeck_customer_apps_payments_sdk.Product[]): void;
204
+ /** Fail the next matching SDK request once, then resume normal simulator behavior. */
205
+ failNext(operation: PaymentSimulatorOperation, failure: PaymentSimulatorFailure): void;
187
206
  markPaid(id: string): void;
188
207
  setSessionStatus(id: string, status: Partial<_stardeck_customer_apps_payments_sdk.CheckoutSessionStatus>): void;
189
208
  setPaymentLinkStatus(id: string, status: _stardeck_customer_apps_payments_sdk.BeamPaymentLinkStatus): void;
@@ -545,4 +564,4 @@ declare const TEST_ENV_DEFAULTS: {
545
564
  /** Default location of the DDL snapshot written by `generate-types`. */
546
565
  declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
547
566
 
548
- export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
567
+ export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PaymentSimulatorFailure, type PaymentSimulatorOperation, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
package/dist/index.js CHANGED
@@ -108,6 +108,7 @@ var state = globalSingleton("state", () => ({
108
108
  checkoutCounter: 0,
109
109
  sessionStatuses: /* @__PURE__ */ new Map(),
110
110
  paymentLinks: /* @__PURE__ */ new Map(),
111
+ paymentFailures: [],
111
112
  products: [],
112
113
  boltConnections: /* @__PURE__ */ new Map(),
113
114
  boltUsedPairingCodes: /* @__PURE__ */ new Set(),
@@ -660,6 +661,7 @@ async function handleEmailSend(request) {
660
661
  html: body.html ? String(body.html) : void 0,
661
662
  text: body.text ? String(body.text) : void 0,
662
663
  replyTo: body.replyTo ? String(body.replyTo) : void 0,
664
+ headers: { ...body.headers ?? {} },
663
665
  attachments: (body.attachments ?? []).map((a) => ({
664
666
  filename: String(a.filename ?? ""),
665
667
  contentType: a.contentType ? String(a.contentType) : void 0
@@ -1273,6 +1275,43 @@ function payErr(error, status = 400, code, details) {
1273
1275
  if (details !== void 0) body.details = details;
1274
1276
  return json(body, status);
1275
1277
  }
1278
+ function paymentOperationForRequest(request, pathname) {
1279
+ if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/payment-links$/.test(pathname)) {
1280
+ return "beam.createPaymentLink";
1281
+ }
1282
+ if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/payment-links\/[^/]+$/.test(pathname)) {
1283
+ return "beam.getPaymentLinkStatus";
1284
+ }
1285
+ if (request.method === "POST" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout$/.test(pathname)) {
1286
+ return "stripe.createCheckout";
1287
+ }
1288
+ if (request.method === "GET" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout-sessions\/[^/]+$/.test(pathname)) {
1289
+ return "stripe.getCheckoutSessionStatus";
1290
+ }
1291
+ return null;
1292
+ }
1293
+ function consumePaymentFailure(request, pathname) {
1294
+ const operation = paymentOperationForRequest(request, pathname);
1295
+ if (!operation) return null;
1296
+ const index = state.paymentFailures.findIndex((entry) => entry.operation === operation);
1297
+ if (index === -1) return null;
1298
+ const [{ failure: failure2 }] = state.paymentFailures.splice(index, 1);
1299
+ if (failure2.kind === "network") {
1300
+ throw new TypeError(failure2.message);
1301
+ }
1302
+ const headers = new Headers({ "Content-Type": "application/json" });
1303
+ if (failure2.retryAfterSeconds !== void 0) {
1304
+ headers.set("Retry-After", String(failure2.retryAfterSeconds));
1305
+ }
1306
+ return new Response(
1307
+ JSON.stringify({
1308
+ error: failure2.message,
1309
+ code: failure2.code ?? "REQUEST_ERROR",
1310
+ ...failure2.details !== void 0 ? { details: failure2.details } : {}
1311
+ }),
1312
+ { status: failure2.status, headers }
1313
+ );
1314
+ }
1276
1315
  function isBoltPaymentMethod(value) {
1277
1316
  return typeof value === "string" && BEAM_BOLT_PAYMENT_METHODS.includes(value);
1278
1317
  }
@@ -1416,6 +1455,8 @@ async function deliverEvent(envelope, handler, options) {
1416
1455
  }
1417
1456
  async function handlePaymentsRequest(request, url) {
1418
1457
  const pathname = url.pathname;
1458
+ const injectedFailure = consumePaymentFailure(request, pathname);
1459
+ if (injectedFailure) return injectedFailure;
1419
1460
  if (/\/billing-portal$/.test(pathname)) {
1420
1461
  return payErr("Not found", 404, "NOT_FOUND");
1421
1462
  }
@@ -1710,6 +1751,9 @@ function createPayments() {
1710
1751
  setProducts(products) {
1711
1752
  state.products = products;
1712
1753
  },
1754
+ failNext(operation, failure2) {
1755
+ state.paymentFailures.push({ operation, failure: failure2 });
1756
+ },
1713
1757
  markPaid(id) {
1714
1758
  const session = state.sessionStatuses.get(id);
1715
1759
  if (session) {
@@ -1819,6 +1863,7 @@ function createPayments() {
1819
1863
  state.checkoutCounter = 0;
1820
1864
  state.sessionStatuses.clear();
1821
1865
  state.paymentLinks.clear();
1866
+ state.paymentFailures = [];
1822
1867
  state.products = [];
1823
1868
  state.boltConnections.clear();
1824
1869
  state.boltUsedPairingCodes.clear();
package/dist/index.mjs CHANGED
@@ -59,6 +59,7 @@ var state = globalSingleton("state", () => ({
59
59
  checkoutCounter: 0,
60
60
  sessionStatuses: /* @__PURE__ */ new Map(),
61
61
  paymentLinks: /* @__PURE__ */ new Map(),
62
+ paymentFailures: [],
62
63
  products: [],
63
64
  boltConnections: /* @__PURE__ */ new Map(),
64
65
  boltUsedPairingCodes: /* @__PURE__ */ new Set(),
@@ -611,6 +612,7 @@ async function handleEmailSend(request) {
611
612
  html: body.html ? String(body.html) : void 0,
612
613
  text: body.text ? String(body.text) : void 0,
613
614
  replyTo: body.replyTo ? String(body.replyTo) : void 0,
615
+ headers: { ...body.headers ?? {} },
614
616
  attachments: (body.attachments ?? []).map((a) => ({
615
617
  filename: String(a.filename ?? ""),
616
618
  contentType: a.contentType ? String(a.contentType) : void 0
@@ -1224,6 +1226,43 @@ function payErr(error, status = 400, code, details) {
1224
1226
  if (details !== void 0) body.details = details;
1225
1227
  return json(body, status);
1226
1228
  }
1229
+ function paymentOperationForRequest(request, pathname) {
1230
+ if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/payment-links$/.test(pathname)) {
1231
+ return "beam.createPaymentLink";
1232
+ }
1233
+ if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/payment-links\/[^/]+$/.test(pathname)) {
1234
+ return "beam.getPaymentLinkStatus";
1235
+ }
1236
+ if (request.method === "POST" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout$/.test(pathname)) {
1237
+ return "stripe.createCheckout";
1238
+ }
1239
+ if (request.method === "GET" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout-sessions\/[^/]+$/.test(pathname)) {
1240
+ return "stripe.getCheckoutSessionStatus";
1241
+ }
1242
+ return null;
1243
+ }
1244
+ function consumePaymentFailure(request, pathname) {
1245
+ const operation = paymentOperationForRequest(request, pathname);
1246
+ if (!operation) return null;
1247
+ const index = state.paymentFailures.findIndex((entry) => entry.operation === operation);
1248
+ if (index === -1) return null;
1249
+ const [{ failure: failure2 }] = state.paymentFailures.splice(index, 1);
1250
+ if (failure2.kind === "network") {
1251
+ throw new TypeError(failure2.message);
1252
+ }
1253
+ const headers = new Headers({ "Content-Type": "application/json" });
1254
+ if (failure2.retryAfterSeconds !== void 0) {
1255
+ headers.set("Retry-After", String(failure2.retryAfterSeconds));
1256
+ }
1257
+ return new Response(
1258
+ JSON.stringify({
1259
+ error: failure2.message,
1260
+ code: failure2.code ?? "REQUEST_ERROR",
1261
+ ...failure2.details !== void 0 ? { details: failure2.details } : {}
1262
+ }),
1263
+ { status: failure2.status, headers }
1264
+ );
1265
+ }
1227
1266
  function isBoltPaymentMethod(value) {
1228
1267
  return typeof value === "string" && BEAM_BOLT_PAYMENT_METHODS.includes(value);
1229
1268
  }
@@ -1367,6 +1406,8 @@ async function deliverEvent(envelope, handler, options) {
1367
1406
  }
1368
1407
  async function handlePaymentsRequest(request, url) {
1369
1408
  const pathname = url.pathname;
1409
+ const injectedFailure = consumePaymentFailure(request, pathname);
1410
+ if (injectedFailure) return injectedFailure;
1370
1411
  if (/\/billing-portal$/.test(pathname)) {
1371
1412
  return payErr("Not found", 404, "NOT_FOUND");
1372
1413
  }
@@ -1661,6 +1702,9 @@ function createPayments() {
1661
1702
  setProducts(products) {
1662
1703
  state.products = products;
1663
1704
  },
1705
+ failNext(operation, failure2) {
1706
+ state.paymentFailures.push({ operation, failure: failure2 });
1707
+ },
1664
1708
  markPaid(id) {
1665
1709
  const session = state.sessionStatuses.get(id);
1666
1710
  if (session) {
@@ -1770,6 +1814,7 @@ function createPayments() {
1770
1814
  state.checkoutCounter = 0;
1771
1815
  state.sessionStatuses.clear();
1772
1816
  state.paymentLinks.clear();
1817
+ state.paymentFailures = [];
1773
1818
  state.products = [];
1774
1819
  state.boltConnections.clear();
1775
1820
  state.boltUsedPairingCodes.clear();
@@ -51,6 +51,7 @@ var state = globalSingleton("state", () => ({
51
51
  checkoutCounter: 0,
52
52
  sessionStatuses: /* @__PURE__ */ new Map(),
53
53
  paymentLinks: /* @__PURE__ */ new Map(),
54
+ paymentFailures: [],
54
55
  products: [],
55
56
  boltConnections: /* @__PURE__ */ new Map(),
56
57
  boltUsedPairingCodes: /* @__PURE__ */ new Set(),
@@ -24,6 +24,7 @@ var state = globalSingleton("state", () => ({
24
24
  checkoutCounter: 0,
25
25
  sessionStatuses: /* @__PURE__ */ new Map(),
26
26
  paymentLinks: /* @__PURE__ */ new Map(),
27
+ paymentFailures: [],
27
28
  products: [],
28
29
  boltConnections: /* @__PURE__ */ new Map(),
29
30
  boltUsedPairingCodes: /* @__PURE__ */ new Set(),
package/dist/setup.js CHANGED
@@ -45,6 +45,7 @@ var state = globalSingleton("state", () => ({
45
45
  checkoutCounter: 0,
46
46
  sessionStatuses: /* @__PURE__ */ new Map(),
47
47
  paymentLinks: /* @__PURE__ */ new Map(),
48
+ paymentFailures: [],
48
49
  products: [],
49
50
  boltConnections: /* @__PURE__ */ new Map(),
50
51
  boltUsedPairingCodes: /* @__PURE__ */ new Set(),
@@ -607,6 +608,7 @@ async function handleEmailSend(request) {
607
608
  html: body.html ? String(body.html) : void 0,
608
609
  text: body.text ? String(body.text) : void 0,
609
610
  replyTo: body.replyTo ? String(body.replyTo) : void 0,
611
+ headers: { ...body.headers ?? {} },
610
612
  attachments: (body.attachments ?? []).map((a) => ({
611
613
  filename: String(a.filename ?? ""),
612
614
  contentType: a.contentType ? String(a.contentType) : void 0
@@ -1011,6 +1013,43 @@ function payErr(error, status = 400, code, details) {
1011
1013
  if (details !== void 0) body.details = details;
1012
1014
  return json(body, status);
1013
1015
  }
1016
+ function paymentOperationForRequest(request, pathname) {
1017
+ if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/payment-links$/.test(pathname)) {
1018
+ return "beam.createPaymentLink";
1019
+ }
1020
+ if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/payment-links\/[^/]+$/.test(pathname)) {
1021
+ return "beam.getPaymentLinkStatus";
1022
+ }
1023
+ if (request.method === "POST" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout$/.test(pathname)) {
1024
+ return "stripe.createCheckout";
1025
+ }
1026
+ if (request.method === "GET" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout-sessions\/[^/]+$/.test(pathname)) {
1027
+ return "stripe.getCheckoutSessionStatus";
1028
+ }
1029
+ return null;
1030
+ }
1031
+ function consumePaymentFailure(request, pathname) {
1032
+ const operation = paymentOperationForRequest(request, pathname);
1033
+ if (!operation) return null;
1034
+ const index = state.paymentFailures.findIndex((entry) => entry.operation === operation);
1035
+ if (index === -1) return null;
1036
+ const [{ failure: failure2 }] = state.paymentFailures.splice(index, 1);
1037
+ if (failure2.kind === "network") {
1038
+ throw new TypeError(failure2.message);
1039
+ }
1040
+ const headers = new Headers({ "Content-Type": "application/json" });
1041
+ if (failure2.retryAfterSeconds !== void 0) {
1042
+ headers.set("Retry-After", String(failure2.retryAfterSeconds));
1043
+ }
1044
+ return new Response(
1045
+ JSON.stringify({
1046
+ error: failure2.message,
1047
+ code: failure2.code ?? "REQUEST_ERROR",
1048
+ ...failure2.details !== void 0 ? { details: failure2.details } : {}
1049
+ }),
1050
+ { status: failure2.status, headers }
1051
+ );
1052
+ }
1014
1053
  function isBoltPaymentMethod(value) {
1015
1054
  return typeof value === "string" && BEAM_BOLT_PAYMENT_METHODS.includes(value);
1016
1055
  }
@@ -1119,6 +1158,8 @@ function storedStatusesForFilter(statuses) {
1119
1158
  }
1120
1159
  async function handlePaymentsRequest(request, url) {
1121
1160
  const pathname = url.pathname;
1161
+ const injectedFailure = consumePaymentFailure(request, pathname);
1162
+ if (injectedFailure) return injectedFailure;
1122
1163
  if (/\/billing-portal$/.test(pathname)) {
1123
1164
  return payErr("Not found", 404, "NOT_FOUND");
1124
1165
  }
package/dist/setup.mjs CHANGED
@@ -21,6 +21,7 @@ var state = globalSingleton("state", () => ({
21
21
  checkoutCounter: 0,
22
22
  sessionStatuses: /* @__PURE__ */ new Map(),
23
23
  paymentLinks: /* @__PURE__ */ new Map(),
24
+ paymentFailures: [],
24
25
  products: [],
25
26
  boltConnections: /* @__PURE__ */ new Map(),
26
27
  boltUsedPairingCodes: /* @__PURE__ */ new Set(),
@@ -583,6 +584,7 @@ async function handleEmailSend(request) {
583
584
  html: body.html ? String(body.html) : void 0,
584
585
  text: body.text ? String(body.text) : void 0,
585
586
  replyTo: body.replyTo ? String(body.replyTo) : void 0,
587
+ headers: { ...body.headers ?? {} },
586
588
  attachments: (body.attachments ?? []).map((a) => ({
587
589
  filename: String(a.filename ?? ""),
588
590
  contentType: a.contentType ? String(a.contentType) : void 0
@@ -987,6 +989,43 @@ function payErr(error, status = 400, code, details) {
987
989
  if (details !== void 0) body.details = details;
988
990
  return json(body, status);
989
991
  }
992
+ function paymentOperationForRequest(request, pathname) {
993
+ if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/payment-links$/.test(pathname)) {
994
+ return "beam.createPaymentLink";
995
+ }
996
+ if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/payment-links\/[^/]+$/.test(pathname)) {
997
+ return "beam.getPaymentLinkStatus";
998
+ }
999
+ if (request.method === "POST" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout$/.test(pathname)) {
1000
+ return "stripe.createCheckout";
1001
+ }
1002
+ if (request.method === "GET" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout-sessions\/[^/]+$/.test(pathname)) {
1003
+ return "stripe.getCheckoutSessionStatus";
1004
+ }
1005
+ return null;
1006
+ }
1007
+ function consumePaymentFailure(request, pathname) {
1008
+ const operation = paymentOperationForRequest(request, pathname);
1009
+ if (!operation) return null;
1010
+ const index = state.paymentFailures.findIndex((entry) => entry.operation === operation);
1011
+ if (index === -1) return null;
1012
+ const [{ failure: failure2 }] = state.paymentFailures.splice(index, 1);
1013
+ if (failure2.kind === "network") {
1014
+ throw new TypeError(failure2.message);
1015
+ }
1016
+ const headers = new Headers({ "Content-Type": "application/json" });
1017
+ if (failure2.retryAfterSeconds !== void 0) {
1018
+ headers.set("Retry-After", String(failure2.retryAfterSeconds));
1019
+ }
1020
+ return new Response(
1021
+ JSON.stringify({
1022
+ error: failure2.message,
1023
+ code: failure2.code ?? "REQUEST_ERROR",
1024
+ ...failure2.details !== void 0 ? { details: failure2.details } : {}
1025
+ }),
1026
+ { status: failure2.status, headers }
1027
+ );
1028
+ }
990
1029
  function isBoltPaymentMethod(value) {
991
1030
  return typeof value === "string" && BEAM_BOLT_PAYMENT_METHODS.includes(value);
992
1031
  }
@@ -1095,6 +1134,8 @@ function storedStatusesForFilter(statuses) {
1095
1134
  }
1096
1135
  async function handlePaymentsRequest(request, url) {
1097
1136
  const pathname = url.pathname;
1137
+ const injectedFailure = consumePaymentFailure(request, pathname);
1138
+ if (injectedFailure) return injectedFailure;
1098
1139
  if (/\/billing-portal$/.test(pathname)) {
1099
1140
  return payErr("Not found", 404, "NOT_FOUND");
1100
1141
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stardeck-customer-apps/testing",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Vitest test harness for Stardeck customer apps — in-process Postgres (PGlite) plus a control-plane simulator so the real Stardeck SDKs run unmodified in tests",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",