@stardeck-customer-apps/testing 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/setup.mjs CHANGED
@@ -60,6 +60,11 @@ var state = globalSingleton("state", () => ({
60
60
  cardTokens: /* @__PURE__ */ new Map(),
61
61
  cardTokenCounter: 0,
62
62
  cardTokenizationEnabled: true,
63
+ stripePaymentIntents: /* @__PURE__ */ new Map(),
64
+ stripePaymentIntentCounter: 0,
65
+ stripeIdempotentIntents: /* @__PURE__ */ new Map(),
66
+ stripeAllowedMethods: ["card", "promptpay"],
67
+ stripeChargesEnabled: true,
63
68
  uploads: [],
64
69
  uploadCounter: 0,
65
70
  storageFiles: /* @__PURE__ */ new Map(),
@@ -546,8 +551,8 @@ async function handleNeonSql(db, request) {
546
551
  rowAsArray: arrayMode
547
552
  });
548
553
  } catch (error) {
549
- const err = error;
550
- return json({ message: err.message, code: err.code }, 400);
554
+ const err2 = error;
555
+ return json({ message: err2.message, code: err2.code }, 400);
551
556
  }
552
557
  }
553
558
 
@@ -1512,6 +1517,281 @@ var requestScopeStorage = globalSingleton(
1512
1517
  () => new AsyncLocalStorage()
1513
1518
  );
1514
1519
 
1520
+ // src/simulator/stripe-payment-intents.ts
1521
+ var SIM_STRIPE_PUBLISHABLE_KEY = "pk_test_sim_stripe";
1522
+ var SIM_STRIPE_DEFAULT_ACCOUNT = "acct_sim_default";
1523
+ var METHODS = ["card", "promptpay"];
1524
+ var RESERVED_METADATA_KEYS = ["projectId", "stardeckReferenceId", "stardeckStagingDeploymentId"];
1525
+ var PROMPTPAY_QR_TTL_SECONDS = 15 * 60;
1526
+ var PATH_RE = /^\/api\/store\/(?!beam\/)([^/]+)\/(payment-intents(?:\/([^/]+)(?:\/(cancel))?)?|elements-config|readiness)$/;
1527
+ function err(error, status, code) {
1528
+ return json({ error, code }, status);
1529
+ }
1530
+ function decode(segment) {
1531
+ try {
1532
+ return decodeURIComponent(segment);
1533
+ } catch {
1534
+ return segment;
1535
+ }
1536
+ }
1537
+ function isPlayground(account) {
1538
+ return account === "playground";
1539
+ }
1540
+ function resolveAccount(account) {
1541
+ if (isPlayground(account)) return null;
1542
+ if (account === "default") {
1543
+ return process.env.STRIPE_CONNECT_ACCOUNT_ID || SIM_STRIPE_DEFAULT_ACCOUNT;
1544
+ }
1545
+ return account;
1546
+ }
1547
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1548
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1549
+ var APP_METADATA_MAX_KEYS = 50 - RESERVED_METADATA_KEYS.length;
1550
+ function isStringRecord(value) {
1551
+ return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((v) => typeof v === "string");
1552
+ }
1553
+ function isValidMetadata(value) {
1554
+ if (!isStringRecord(value)) return false;
1555
+ const entries = Object.entries(value);
1556
+ return entries.length <= APP_METADATA_MAX_KEYS && entries.every(([k, v]) => k.length >= 1 && k.length <= 40 && v.length <= 500);
1557
+ }
1558
+ function isOptional(value, test) {
1559
+ return value === void 0 || test(value);
1560
+ }
1561
+ var isEmail = (v) => typeof v === "string" && EMAIL_RE.test(v);
1562
+ function validateCreate(body) {
1563
+ const { amount, currency, method, referenceId, idempotencyKey } = body;
1564
+ const checks = [
1565
+ [
1566
+ typeof amount === "number" && Number.isInteger(amount) && amount > 0,
1567
+ "amount must be a positive integer in minor units"
1568
+ ],
1569
+ [typeof currency === "string" && currency.length === 3, "currency must be a 3-letter ISO code"],
1570
+ [
1571
+ typeof method === "string" && METHODS.includes(method),
1572
+ `method must be one of: ${METHODS.join(", ")}`
1573
+ ],
1574
+ [
1575
+ typeof referenceId === "string" && referenceId.length >= 1 && referenceId.length <= 200,
1576
+ "referenceId must be 1 to 200 characters"
1577
+ ],
1578
+ [
1579
+ typeof idempotencyKey === "string" && idempotencyKey.length >= 1 && idempotencyKey.length <= 200,
1580
+ "idempotencyKey is required (1 to 200 characters)"
1581
+ ],
1582
+ [
1583
+ isOptional(body.description, (v) => typeof v === "string" && v.length <= 1e3),
1584
+ "description must be at most 1000 characters"
1585
+ ],
1586
+ [isOptional(body.receiptEmail, isEmail), "receiptEmail must be an email address"],
1587
+ [isOptional(body.billingEmail, isEmail), "billingEmail must be an email address"],
1588
+ [
1589
+ isOptional(body.returnUrl, (v) => typeof v === "string" && URL.canParse(v)),
1590
+ "returnUrl must be an absolute URL"
1591
+ ],
1592
+ [
1593
+ isOptional(body.metadata, isValidMetadata),
1594
+ `metadata must map keys (1-40 chars) to strings (max 500), at most ${APP_METADATA_MAX_KEYS} keys`
1595
+ ],
1596
+ [
1597
+ typeof body.deploymentId === "string" && UUID_RE2.test(body.deploymentId),
1598
+ "Invalid deployment ID"
1599
+ ]
1600
+ ];
1601
+ const failed = checks.find(([ok]) => !ok);
1602
+ return failed ? failed[1] : null;
1603
+ }
1604
+ function appMetadata(raw) {
1605
+ if (!isStringRecord(raw)) return {};
1606
+ const out = {};
1607
+ for (const [key, value] of Object.entries(raw)) {
1608
+ if (!RESERVED_METADATA_KEYS.includes(key)) out[key] = value;
1609
+ }
1610
+ return out;
1611
+ }
1612
+ function stableStringify(value) {
1613
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
1614
+ if (value && typeof value === "object") {
1615
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1616
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
1617
+ }
1618
+ return JSON.stringify(value);
1619
+ }
1620
+ function toStripePaymentIntentSummary(intent) {
1621
+ const open = intent.status !== "succeeded" && intent.status !== "canceled";
1622
+ let actionRequired = "NONE";
1623
+ if (open && intent.method === "card") actionRequired = "CLIENT_CONFIRM";
1624
+ if (open && intent.method === "promptpay" && intent.status === "requires_action") {
1625
+ actionRequired = "ENCODED_IMAGE";
1626
+ }
1627
+ return {
1628
+ id: intent.id,
1629
+ status: intent.status,
1630
+ amount: intent.amount,
1631
+ currency: intent.currency,
1632
+ method: intent.method,
1633
+ referenceId: intent.referenceId,
1634
+ metadata: { ...intent.appMetadata },
1635
+ actionRequired,
1636
+ clientSecret: intent.method === "card" ? intent.clientSecret : null,
1637
+ qr: actionRequired === "ENCODED_IMAGE" ? { ...intent.qr } : null,
1638
+ latestChargeId: intent.latestChargeId,
1639
+ amountRefunded: intent.amountRefunded,
1640
+ lastPaymentError: intent.lastPaymentError ? { ...intent.lastPaymentError } : null,
1641
+ created: intent.created,
1642
+ livemode: false,
1643
+ playground: intent.playground
1644
+ };
1645
+ }
1646
+ function promptPayQr(id, created) {
1647
+ return {
1648
+ imageUrlPng: `https://qr.stripe.test/${id}.png`,
1649
+ imageUrlSvg: `https://qr.stripe.test/${id}.svg`,
1650
+ data: `00020101021230SIM${id}`,
1651
+ hostedInstructionsUrl: `https://payments.stripe.test/promptpay/${id}`,
1652
+ expiresAt: created + PROMPTPAY_QR_TTL_SECONDS
1653
+ };
1654
+ }
1655
+ function createIntent(body, account, projectId) {
1656
+ const problem = validateCreate(body);
1657
+ if (problem) return err(problem, 400, "invalid_request");
1658
+ const method = body.method;
1659
+ const currency = body.currency.toLowerCase();
1660
+ if (!state.stripeAllowedMethods.includes(method)) {
1661
+ return err(
1662
+ `The ${method} payment method is turned off for this account`,
1663
+ 422,
1664
+ "method_not_allowed"
1665
+ );
1666
+ }
1667
+ if (method === "promptpay" && currency !== "thb") {
1668
+ return err("PromptPay only takes THB", 422, "currency_not_supported");
1669
+ }
1670
+ if (!state.stripeChargesEnabled) {
1671
+ return err("This Stripe account can't take payments yet", 503, "account_not_ready");
1672
+ }
1673
+ const scopedKey = JSON.stringify(["pi", projectId, account, body.idempotencyKey]);
1674
+ const comparable = { ...body };
1675
+ delete comparable.deploymentId;
1676
+ delete comparable.idempotencyKey;
1677
+ const fingerprint = stableStringify(comparable);
1678
+ const saved = state.stripeIdempotentIntents.get(scopedKey);
1679
+ if (saved) {
1680
+ if (saved.fingerprint !== fingerprint) {
1681
+ return err(
1682
+ "This idempotency key was already used with different parameters. Use a new key for a different payment.",
1683
+ 409,
1684
+ "idempotency_conflict"
1685
+ );
1686
+ }
1687
+ const existing = state.stripePaymentIntents.get(saved.id);
1688
+ return json({ paymentIntent: toStripePaymentIntentSummary(existing) }, 201);
1689
+ }
1690
+ state.stripePaymentIntentCounter += 1;
1691
+ const id = `pi_sim_${state.stripePaymentIntentCounter}`;
1692
+ const created = Math.floor(Date.now() / 1e3);
1693
+ const referenceId = body.referenceId;
1694
+ const metadata = appMetadata(body.metadata);
1695
+ const intent = {
1696
+ id,
1697
+ projectId,
1698
+ account,
1699
+ connectedAccountId: resolveAccount(account),
1700
+ playground: isPlayground(account),
1701
+ status: method === "card" ? "requires_payment_method" : "requires_action",
1702
+ amount: body.amount,
1703
+ currency,
1704
+ method,
1705
+ referenceId,
1706
+ appMetadata: metadata,
1707
+ metadata: { ...metadata, projectId, stardeckReferenceId: referenceId },
1708
+ description: typeof body.description === "string" ? body.description : null,
1709
+ receiptEmail: typeof body.receiptEmail === "string" ? body.receiptEmail : null,
1710
+ returnUrl: typeof body.returnUrl === "string" ? body.returnUrl : null,
1711
+ clientSecret: `${id}_secret_sim`,
1712
+ qr: method === "promptpay" ? promptPayQr(id, created) : null,
1713
+ latestChargeId: null,
1714
+ amountRefunded: 0,
1715
+ lastPaymentError: null,
1716
+ created
1717
+ };
1718
+ state.stripePaymentIntents.set(id, intent);
1719
+ state.stripeIdempotentIntents.set(scopedKey, { fingerprint, id });
1720
+ console.log(
1721
+ `[stardeck-testing] stripe payment intent ${id} created method=${method} amount=${intent.amount} ${currency}`
1722
+ );
1723
+ return json({ paymentIntent: toStripePaymentIntentSummary(intent) }, 201);
1724
+ }
1725
+ function ownedIntent(id, projectId) {
1726
+ const intent = state.stripePaymentIntents.get(id);
1727
+ return intent && intent.projectId === projectId ? intent : void 0;
1728
+ }
1729
+ function cancelIntent(intent) {
1730
+ if (intent.status === "succeeded") {
1731
+ return err("This payment already succeeded and can't be canceled", 409, "already_succeeded");
1732
+ }
1733
+ intent.status = "canceled";
1734
+ return json({ paymentIntent: toStripePaymentIntentSummary(intent) });
1735
+ }
1736
+ function handleIntents(request, url, account, intentId, action, projectId) {
1737
+ if (!intentId && request.method === "POST") {
1738
+ return readJsonBody(request).then((body) => createIntent(body, account, projectId));
1739
+ }
1740
+ if (!intentId && request.method === "GET") {
1741
+ const referenceId = url.searchParams.get("referenceId");
1742
+ if (!referenceId) return err("referenceId is required", 400, "invalid_request");
1743
+ const paymentIntents = [...state.stripePaymentIntents.values()].filter((i) => i.projectId === projectId && i.referenceId === referenceId).map(toStripePaymentIntentSummary);
1744
+ return json({ paymentIntents });
1745
+ }
1746
+ if (!intentId) return null;
1747
+ const intent = ownedIntent(decode(intentId), projectId);
1748
+ if (!action && request.method === "GET") {
1749
+ if (!intent) return err("Payment intent not found", 404, "not_found");
1750
+ return json({ paymentIntent: toStripePaymentIntentSummary(intent) });
1751
+ }
1752
+ if (action === "cancel" && request.method === "POST") {
1753
+ return readJsonBody(request).then((body) => {
1754
+ if (typeof body.deploymentId !== "string" || !UUID_RE2.test(body.deploymentId)) {
1755
+ return err("deploymentId is required", 400, "invalid_request");
1756
+ }
1757
+ if (!intent) return err("Payment intent not found", 404, "not_found");
1758
+ return cancelIntent(intent);
1759
+ });
1760
+ }
1761
+ return null;
1762
+ }
1763
+ async function handleStripePaymentIntentRequest(request, url, projectId) {
1764
+ const match = url.pathname.match(PATH_RE);
1765
+ if (!match) return null;
1766
+ if (!projectId) return err("Missing authentication", 401, "unauthorized");
1767
+ const account = decode(match[1]);
1768
+ const resource = match[2];
1769
+ if (request.method === "GET" && !url.searchParams.get("deploymentId")) {
1770
+ return err("deploymentId is required", 400, "invalid_request");
1771
+ }
1772
+ if (resource === "elements-config" && request.method === "GET") {
1773
+ return json({
1774
+ publishableKey: SIM_STRIPE_PUBLISHABLE_KEY,
1775
+ stripeAccount: resolveAccount(account),
1776
+ playground: isPlayground(account)
1777
+ });
1778
+ }
1779
+ if (resource === "readiness" && request.method === "GET") {
1780
+ const methods = [...state.stripeAllowedMethods];
1781
+ return json({
1782
+ provider: "stripe",
1783
+ ready: state.stripeChargesEnabled && methods.length > 0,
1784
+ chargesEnabled: state.stripeChargesEnabled,
1785
+ methods,
1786
+ playground: isPlayground(account)
1787
+ });
1788
+ }
1789
+ if (resource.startsWith("payment-intents")) {
1790
+ return handleIntents(request, url, account, match[3], match[4], projectId);
1791
+ }
1792
+ return null;
1793
+ }
1794
+
1515
1795
  // src/simulator/webhook-signing.ts
1516
1796
  import crypto4 from "crypto";
1517
1797
 
@@ -1531,7 +1811,7 @@ var BEAM_BOLT_PAYMENT_METHODS = [
1531
1811
  "SPAY_LATER"
1532
1812
  ];
1533
1813
  var BEAM_BOLT_INTENT_STATUSES = ["PENDING", "PAID", "FAILED", "CANCELED", "EXPIRED"];
1534
- var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1814
+ var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1535
1815
  function payErr(error, status = 400, code, details) {
1536
1816
  const body = { error };
1537
1817
  if (code) body.code = code;
@@ -1557,7 +1837,23 @@ function paymentOperationForRequest(request, pathname) {
1557
1837
  if (request.method === "GET" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout-sessions\/[^/]+$/.test(pathname)) {
1558
1838
  return "stripe.getCheckoutSessionStatus";
1559
1839
  }
1560
- return null;
1840
+ return stripeInlineOperation(request.method, pathname);
1841
+ }
1842
+ var STRIPE_INLINE_OPERATIONS = [
1843
+ ["POST", /^\/api\/store\/(?!beam\/)[^/]+\/payment-intents$/, "stripe.createPaymentIntent"],
1844
+ ["GET", /^\/api\/store\/(?!beam\/)[^/]+\/payment-intents$/, "stripe.findPaymentIntents"],
1845
+ ["GET", /^\/api\/store\/(?!beam\/)[^/]+\/payment-intents\/[^/]+$/, "stripe.getPaymentIntent"],
1846
+ [
1847
+ "POST",
1848
+ /^\/api\/store\/(?!beam\/)[^/]+\/payment-intents\/[^/]+\/cancel$/,
1849
+ "stripe.cancelPaymentIntent"
1850
+ ],
1851
+ ["GET", /^\/api\/store\/(?!beam\/)[^/]+\/elements-config$/, "stripe.getElementsConfig"],
1852
+ ["GET", /^\/api\/store\/(?!beam\/)[^/]+\/readiness$/, "stripe.getReadiness"]
1853
+ ];
1854
+ function stripeInlineOperation(method, pathname) {
1855
+ const hit = STRIPE_INLINE_OPERATIONS.find(([m, re]) => m === method && re.test(pathname));
1856
+ return hit ? hit[2] : null;
1561
1857
  }
1562
1858
  function consumePaymentFailure(request, pathname) {
1563
1859
  const operation = paymentOperationForRequest(request, pathname);
@@ -1587,11 +1883,11 @@ function isBoltPaymentMethod(value) {
1587
1883
  function isBoltIntentStatus(value) {
1588
1884
  return BEAM_BOLT_INTENT_STATUSES.includes(value);
1589
1885
  }
1590
- function stableStringify(value) {
1591
- if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
1886
+ function stableStringify2(value) {
1887
+ if (Array.isArray(value)) return `[${value.map(stableStringify2).join(",")}]`;
1592
1888
  if (value && typeof value === "object") {
1593
1889
  const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1594
- return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
1890
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify2(v)}`).join(",")}}`;
1595
1891
  }
1596
1892
  return JSON.stringify(value);
1597
1893
  }
@@ -1697,10 +1993,16 @@ function storedStatusesForFilter(statuses) {
1697
1993
  }
1698
1994
  return [...stored];
1699
1995
  }
1700
- async function handlePaymentsRequest(request, url) {
1996
+ async function handlePaymentsRequest(request, url, signedProjectId = null) {
1701
1997
  const pathname = url.pathname;
1702
1998
  const injectedFailure = consumePaymentFailure(request, pathname);
1703
1999
  if (injectedFailure) return injectedFailure;
2000
+ const inlineStripeResponse = await handleStripePaymentIntentRequest(
2001
+ request,
2002
+ url,
2003
+ signedProjectId
2004
+ );
2005
+ if (inlineStripeResponse) return inlineStripeResponse;
1704
2006
  const directChargeResponse = await handleDirectChargeRequest(request, pathname);
1705
2007
  if (directChargeResponse) return directChargeResponse;
1706
2008
  if (/\/billing-portal$/.test(pathname)) {
@@ -1799,7 +2101,7 @@ async function handlePaymentsRequest(request, url) {
1799
2101
  code: "too_small"
1800
2102
  });
1801
2103
  }
1802
- if (typeof body.deploymentId !== "string" || !UUID_RE2.test(body.deploymentId)) {
2104
+ if (typeof body.deploymentId !== "string" || !UUID_RE3.test(body.deploymentId)) {
1803
2105
  issues.push({
1804
2106
  path: ["deploymentId"],
1805
2107
  message: "Invalid uuid",
@@ -1983,7 +2285,7 @@ async function handlePaymentsRequest(request, url) {
1983
2285
  const comparable = { ...body };
1984
2286
  delete comparable.deploymentId;
1985
2287
  delete comparable.idempotencyKey;
1986
- const fingerprint = stableStringify(comparable);
2288
+ const fingerprint = stableStringify2(comparable);
1987
2289
  if (scopedKey) {
1988
2290
  const saved = state.idempotentCheckouts.get(scopedKey);
1989
2291
  if (saved) {
@@ -2823,7 +3125,7 @@ function requiresDeploymentHmac(request, url) {
2823
3125
  return !isPresignedPut;
2824
3126
  }
2825
3127
  const dataStoreMatch = url.pathname.match(/^\/api\/data-stores\/[^/]+(\/.*)?$/);
2826
- const isEmail = url.pathname === "/api/email/send";
3128
+ const isEmail2 = url.pathname === "/api/email/send";
2827
3129
  const identitiesMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/identities(\/.*)?$/);
2828
3130
  const storeMatch = url.pathname.match(/^\/api\/store\//);
2829
3131
  const messagingMatch = url.pathname.match(
@@ -2832,7 +3134,7 @@ function requiresDeploymentHmac(request, url) {
2832
3134
  const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
2833
3135
  const isStarlens = url.pathname === "/api/starlens/extract";
2834
3136
  const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
2835
- return !!(isStarlens || scheduleWakeMatch || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
3137
+ return !!(isStarlens || scheduleWakeMatch || dataStoreMatch || isEmail2 || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
2836
3138
  }
2837
3139
  var SCHEDULE_WAKE_PATH = /^\/api\/deployments\/[^/]+\/schedules\/([^/]+)\/wake$/;
2838
3140
  function deploymentIdFromPath(url) {
@@ -2855,7 +3157,7 @@ async function handleSimulatedRequest(request, url) {
2855
3157
  return authMatch[1].endsWith("verify") ? handleAuthVerify(request) : handleAuthRefresh(request);
2856
3158
  }
2857
3159
  const dataStoreMatch = url.pathname.match(/^\/api\/data-stores\/[^/]+(\/.*)?$/);
2858
- const isEmail = url.pathname === "/api/email/send";
3160
+ const isEmail2 = url.pathname === "/api/email/send";
2859
3161
  const identitiesMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/identities(\/.*)?$/);
2860
3162
  const storeMatch = url.pathname.match(/^\/api\/store\//);
2861
3163
  const messagingMatch = url.pathname.match(
@@ -2864,6 +3166,7 @@ async function handleSimulatedRequest(request, url) {
2864
3166
  const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
2865
3167
  const isStorageHost = url.hostname === STORAGE_TEST_HOST;
2866
3168
  let signedDeploymentId = null;
3169
+ let signedProjectId = null;
2867
3170
  if (requiresDeploymentHmac(request, url)) {
2868
3171
  const authHeader = request.headers.get("X-Stardeck-Auth");
2869
3172
  if (!authHeader) {
@@ -2876,6 +3179,7 @@ async function handleSimulatedRequest(request, url) {
2876
3179
  return failure("Invalid authentication", 401);
2877
3180
  }
2878
3181
  signedDeploymentId = authPayload.deploymentId;
3182
+ signedProjectId = authPayload.projectId;
2879
3183
  }
2880
3184
  if (isStorageHost) {
2881
3185
  return handleStorageRequest(request, url);
@@ -2883,14 +3187,14 @@ async function handleSimulatedRequest(request, url) {
2883
3187
  if (url.hostname === SIM_BEAM_HOST) {
2884
3188
  return handleCardTokenRequest(request, url);
2885
3189
  }
2886
- if (isEmail && request.method === "POST") {
3190
+ if (isEmail2 && request.method === "POST") {
2887
3191
  return handleEmailSend(request);
2888
3192
  }
2889
3193
  if (identitiesMatch) {
2890
3194
  return handleIdentitiesRequest(request, identitiesMatch[1] ?? "");
2891
3195
  }
2892
3196
  if (storeMatch) {
2893
- return handlePaymentsRequest(request, url);
3197
+ return handlePaymentsRequest(request, url, signedProjectId);
2894
3198
  }
2895
3199
  if (messagingMatch) {
2896
3200
  const channel = messagingMatch[1];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stardeck-customer-apps/testing",
3
- "version": "0.26.0",
3
+ "version": "0.27.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",
@@ -48,7 +48,7 @@
48
48
  "build": "tsup src/index.ts src/config.ts src/setup.ts src/next/headers-shim.ts src/server-only-shim.ts --format cjs,esm --dts --no-splitting",
49
49
  "dev": "tsup src/index.ts src/config.ts src/setup.ts src/next/headers-shim.ts src/server-only-shim.ts --format cjs,esm --dts --no-splitting --watch",
50
50
  "format": "prettier --write . && oxlint . --fix",
51
- "typecheck": "tsgo --noEmit && tsgo -p tsconfig.test.json",
51
+ "typecheck": "node ../../scripts/typecheck.mjs --noEmit && node ../../scripts/typecheck.mjs -p tsconfig.test.json",
52
52
  "lint": "oxlint src/",
53
53
  "test": "vitest run",
54
54
  "test:watch": "vitest"
@@ -111,6 +111,8 @@
111
111
  "typescript": "^5.0.0",
112
112
  "vitest": "^3.2.4",
113
113
  "@stardeck-customer-apps/starlens": "*",
114
- "zod": "^4.3.5"
114
+ "zod": "^4.3.5",
115
+ "react": "19.2.8",
116
+ "react-dom": "19.2.8"
115
117
  }
116
118
  }