@stardeck-customer-apps/testing 0.25.1 → 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/SKILL.md +39 -1
- package/dist/index.d.mts +98 -3
- package/dist/index.d.ts +98 -3
- package/dist/index.js +453 -17
- package/dist/index.mjs +453 -17
- package/dist/next/headers-shim.js +6 -0
- package/dist/next/headers-shim.mjs +6 -0
- package/dist/setup.js +337 -16
- package/dist/setup.mjs +337 -16
- package/package.json +6 -3
|
@@ -33,6 +33,7 @@ var state = globalSingleton("state", () => ({
|
|
|
33
33
|
identitySuppressClaimDelivery: false,
|
|
34
34
|
identityAllowedReturnOrigins: /* @__PURE__ */ new Set([APP_TEST_ORIGIN]),
|
|
35
35
|
checkouts: [],
|
|
36
|
+
scheduleWakes: [],
|
|
36
37
|
checkoutCounter: 0,
|
|
37
38
|
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
38
39
|
idempotentCheckouts: /* @__PURE__ */ new Map(),
|
|
@@ -51,6 +52,11 @@ var state = globalSingleton("state", () => ({
|
|
|
51
52
|
cardTokens: /* @__PURE__ */ new Map(),
|
|
52
53
|
cardTokenCounter: 0,
|
|
53
54
|
cardTokenizationEnabled: true,
|
|
55
|
+
stripePaymentIntents: /* @__PURE__ */ new Map(),
|
|
56
|
+
stripePaymentIntentCounter: 0,
|
|
57
|
+
stripeIdempotentIntents: /* @__PURE__ */ new Map(),
|
|
58
|
+
stripeAllowedMethods: ["card", "promptpay"],
|
|
59
|
+
stripeChargesEnabled: true,
|
|
54
60
|
uploads: [],
|
|
55
61
|
uploadCounter: 0,
|
|
56
62
|
storageFiles: /* @__PURE__ */ new Map(),
|
package/dist/setup.js
CHANGED
|
@@ -65,6 +65,7 @@ var state = globalSingleton("state", () => ({
|
|
|
65
65
|
identitySuppressClaimDelivery: false,
|
|
66
66
|
identityAllowedReturnOrigins: /* @__PURE__ */ new Set([APP_TEST_ORIGIN]),
|
|
67
67
|
checkouts: [],
|
|
68
|
+
scheduleWakes: [],
|
|
68
69
|
checkoutCounter: 0,
|
|
69
70
|
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
70
71
|
idempotentCheckouts: /* @__PURE__ */ new Map(),
|
|
@@ -83,6 +84,11 @@ var state = globalSingleton("state", () => ({
|
|
|
83
84
|
cardTokens: /* @__PURE__ */ new Map(),
|
|
84
85
|
cardTokenCounter: 0,
|
|
85
86
|
cardTokenizationEnabled: true,
|
|
87
|
+
stripePaymentIntents: /* @__PURE__ */ new Map(),
|
|
88
|
+
stripePaymentIntentCounter: 0,
|
|
89
|
+
stripeIdempotentIntents: /* @__PURE__ */ new Map(),
|
|
90
|
+
stripeAllowedMethods: ["card", "promptpay"],
|
|
91
|
+
stripeChargesEnabled: true,
|
|
86
92
|
uploads: [],
|
|
87
93
|
uploadCounter: 0,
|
|
88
94
|
storageFiles: /* @__PURE__ */ new Map(),
|
|
@@ -569,8 +575,8 @@ async function handleNeonSql(db, request) {
|
|
|
569
575
|
rowAsArray: arrayMode
|
|
570
576
|
});
|
|
571
577
|
} catch (error) {
|
|
572
|
-
const
|
|
573
|
-
return json({ message:
|
|
578
|
+
const err2 = error;
|
|
579
|
+
return json({ message: err2.message, code: err2.code }, 400);
|
|
574
580
|
}
|
|
575
581
|
}
|
|
576
582
|
|
|
@@ -1535,6 +1541,281 @@ var requestScopeStorage = globalSingleton(
|
|
|
1535
1541
|
() => new import_node_async_hooks.AsyncLocalStorage()
|
|
1536
1542
|
);
|
|
1537
1543
|
|
|
1544
|
+
// src/simulator/stripe-payment-intents.ts
|
|
1545
|
+
var SIM_STRIPE_PUBLISHABLE_KEY = "pk_test_sim_stripe";
|
|
1546
|
+
var SIM_STRIPE_DEFAULT_ACCOUNT = "acct_sim_default";
|
|
1547
|
+
var METHODS = ["card", "promptpay"];
|
|
1548
|
+
var RESERVED_METADATA_KEYS = ["projectId", "stardeckReferenceId", "stardeckStagingDeploymentId"];
|
|
1549
|
+
var PROMPTPAY_QR_TTL_SECONDS = 15 * 60;
|
|
1550
|
+
var PATH_RE = /^\/api\/store\/(?!beam\/)([^/]+)\/(payment-intents(?:\/([^/]+)(?:\/(cancel))?)?|elements-config|readiness)$/;
|
|
1551
|
+
function err(error, status, code) {
|
|
1552
|
+
return json({ error, code }, status);
|
|
1553
|
+
}
|
|
1554
|
+
function decode(segment) {
|
|
1555
|
+
try {
|
|
1556
|
+
return decodeURIComponent(segment);
|
|
1557
|
+
} catch {
|
|
1558
|
+
return segment;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
function isPlayground(account) {
|
|
1562
|
+
return account === "playground";
|
|
1563
|
+
}
|
|
1564
|
+
function resolveAccount(account) {
|
|
1565
|
+
if (isPlayground(account)) return null;
|
|
1566
|
+
if (account === "default") {
|
|
1567
|
+
return process.env.STRIPE_CONNECT_ACCOUNT_ID || SIM_STRIPE_DEFAULT_ACCOUNT;
|
|
1568
|
+
}
|
|
1569
|
+
return account;
|
|
1570
|
+
}
|
|
1571
|
+
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;
|
|
1572
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1573
|
+
var APP_METADATA_MAX_KEYS = 50 - RESERVED_METADATA_KEYS.length;
|
|
1574
|
+
function isStringRecord(value) {
|
|
1575
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((v) => typeof v === "string");
|
|
1576
|
+
}
|
|
1577
|
+
function isValidMetadata(value) {
|
|
1578
|
+
if (!isStringRecord(value)) return false;
|
|
1579
|
+
const entries = Object.entries(value);
|
|
1580
|
+
return entries.length <= APP_METADATA_MAX_KEYS && entries.every(([k, v]) => k.length >= 1 && k.length <= 40 && v.length <= 500);
|
|
1581
|
+
}
|
|
1582
|
+
function isOptional(value, test) {
|
|
1583
|
+
return value === void 0 || test(value);
|
|
1584
|
+
}
|
|
1585
|
+
var isEmail = (v) => typeof v === "string" && EMAIL_RE.test(v);
|
|
1586
|
+
function validateCreate(body) {
|
|
1587
|
+
const { amount, currency, method, referenceId, idempotencyKey } = body;
|
|
1588
|
+
const checks = [
|
|
1589
|
+
[
|
|
1590
|
+
typeof amount === "number" && Number.isInteger(amount) && amount > 0,
|
|
1591
|
+
"amount must be a positive integer in minor units"
|
|
1592
|
+
],
|
|
1593
|
+
[typeof currency === "string" && currency.length === 3, "currency must be a 3-letter ISO code"],
|
|
1594
|
+
[
|
|
1595
|
+
typeof method === "string" && METHODS.includes(method),
|
|
1596
|
+
`method must be one of: ${METHODS.join(", ")}`
|
|
1597
|
+
],
|
|
1598
|
+
[
|
|
1599
|
+
typeof referenceId === "string" && referenceId.length >= 1 && referenceId.length <= 200,
|
|
1600
|
+
"referenceId must be 1 to 200 characters"
|
|
1601
|
+
],
|
|
1602
|
+
[
|
|
1603
|
+
typeof idempotencyKey === "string" && idempotencyKey.length >= 1 && idempotencyKey.length <= 200,
|
|
1604
|
+
"idempotencyKey is required (1 to 200 characters)"
|
|
1605
|
+
],
|
|
1606
|
+
[
|
|
1607
|
+
isOptional(body.description, (v) => typeof v === "string" && v.length <= 1e3),
|
|
1608
|
+
"description must be at most 1000 characters"
|
|
1609
|
+
],
|
|
1610
|
+
[isOptional(body.receiptEmail, isEmail), "receiptEmail must be an email address"],
|
|
1611
|
+
[isOptional(body.billingEmail, isEmail), "billingEmail must be an email address"],
|
|
1612
|
+
[
|
|
1613
|
+
isOptional(body.returnUrl, (v) => typeof v === "string" && URL.canParse(v)),
|
|
1614
|
+
"returnUrl must be an absolute URL"
|
|
1615
|
+
],
|
|
1616
|
+
[
|
|
1617
|
+
isOptional(body.metadata, isValidMetadata),
|
|
1618
|
+
`metadata must map keys (1-40 chars) to strings (max 500), at most ${APP_METADATA_MAX_KEYS} keys`
|
|
1619
|
+
],
|
|
1620
|
+
[
|
|
1621
|
+
typeof body.deploymentId === "string" && UUID_RE2.test(body.deploymentId),
|
|
1622
|
+
"Invalid deployment ID"
|
|
1623
|
+
]
|
|
1624
|
+
];
|
|
1625
|
+
const failed = checks.find(([ok]) => !ok);
|
|
1626
|
+
return failed ? failed[1] : null;
|
|
1627
|
+
}
|
|
1628
|
+
function appMetadata(raw) {
|
|
1629
|
+
if (!isStringRecord(raw)) return {};
|
|
1630
|
+
const out = {};
|
|
1631
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
1632
|
+
if (!RESERVED_METADATA_KEYS.includes(key)) out[key] = value;
|
|
1633
|
+
}
|
|
1634
|
+
return out;
|
|
1635
|
+
}
|
|
1636
|
+
function stableStringify(value) {
|
|
1637
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
1638
|
+
if (value && typeof value === "object") {
|
|
1639
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
1640
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
1641
|
+
}
|
|
1642
|
+
return JSON.stringify(value);
|
|
1643
|
+
}
|
|
1644
|
+
function toStripePaymentIntentSummary(intent) {
|
|
1645
|
+
const open = intent.status !== "succeeded" && intent.status !== "canceled";
|
|
1646
|
+
let actionRequired = "NONE";
|
|
1647
|
+
if (open && intent.method === "card") actionRequired = "CLIENT_CONFIRM";
|
|
1648
|
+
if (open && intent.method === "promptpay" && intent.status === "requires_action") {
|
|
1649
|
+
actionRequired = "ENCODED_IMAGE";
|
|
1650
|
+
}
|
|
1651
|
+
return {
|
|
1652
|
+
id: intent.id,
|
|
1653
|
+
status: intent.status,
|
|
1654
|
+
amount: intent.amount,
|
|
1655
|
+
currency: intent.currency,
|
|
1656
|
+
method: intent.method,
|
|
1657
|
+
referenceId: intent.referenceId,
|
|
1658
|
+
metadata: { ...intent.appMetadata },
|
|
1659
|
+
actionRequired,
|
|
1660
|
+
clientSecret: intent.method === "card" ? intent.clientSecret : null,
|
|
1661
|
+
qr: actionRequired === "ENCODED_IMAGE" ? { ...intent.qr } : null,
|
|
1662
|
+
latestChargeId: intent.latestChargeId,
|
|
1663
|
+
amountRefunded: intent.amountRefunded,
|
|
1664
|
+
lastPaymentError: intent.lastPaymentError ? { ...intent.lastPaymentError } : null,
|
|
1665
|
+
created: intent.created,
|
|
1666
|
+
livemode: false,
|
|
1667
|
+
playground: intent.playground
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
function promptPayQr(id, created) {
|
|
1671
|
+
return {
|
|
1672
|
+
imageUrlPng: `https://qr.stripe.test/${id}.png`,
|
|
1673
|
+
imageUrlSvg: `https://qr.stripe.test/${id}.svg`,
|
|
1674
|
+
data: `00020101021230SIM${id}`,
|
|
1675
|
+
hostedInstructionsUrl: `https://payments.stripe.test/promptpay/${id}`,
|
|
1676
|
+
expiresAt: created + PROMPTPAY_QR_TTL_SECONDS
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
function createIntent(body, account, projectId) {
|
|
1680
|
+
const problem = validateCreate(body);
|
|
1681
|
+
if (problem) return err(problem, 400, "invalid_request");
|
|
1682
|
+
const method = body.method;
|
|
1683
|
+
const currency = body.currency.toLowerCase();
|
|
1684
|
+
if (!state.stripeAllowedMethods.includes(method)) {
|
|
1685
|
+
return err(
|
|
1686
|
+
`The ${method} payment method is turned off for this account`,
|
|
1687
|
+
422,
|
|
1688
|
+
"method_not_allowed"
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
if (method === "promptpay" && currency !== "thb") {
|
|
1692
|
+
return err("PromptPay only takes THB", 422, "currency_not_supported");
|
|
1693
|
+
}
|
|
1694
|
+
if (!state.stripeChargesEnabled) {
|
|
1695
|
+
return err("This Stripe account can't take payments yet", 503, "account_not_ready");
|
|
1696
|
+
}
|
|
1697
|
+
const scopedKey = JSON.stringify(["pi", projectId, account, body.idempotencyKey]);
|
|
1698
|
+
const comparable = { ...body };
|
|
1699
|
+
delete comparable.deploymentId;
|
|
1700
|
+
delete comparable.idempotencyKey;
|
|
1701
|
+
const fingerprint = stableStringify(comparable);
|
|
1702
|
+
const saved = state.stripeIdempotentIntents.get(scopedKey);
|
|
1703
|
+
if (saved) {
|
|
1704
|
+
if (saved.fingerprint !== fingerprint) {
|
|
1705
|
+
return err(
|
|
1706
|
+
"This idempotency key was already used with different parameters. Use a new key for a different payment.",
|
|
1707
|
+
409,
|
|
1708
|
+
"idempotency_conflict"
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
const existing = state.stripePaymentIntents.get(saved.id);
|
|
1712
|
+
return json({ paymentIntent: toStripePaymentIntentSummary(existing) }, 201);
|
|
1713
|
+
}
|
|
1714
|
+
state.stripePaymentIntentCounter += 1;
|
|
1715
|
+
const id = `pi_sim_${state.stripePaymentIntentCounter}`;
|
|
1716
|
+
const created = Math.floor(Date.now() / 1e3);
|
|
1717
|
+
const referenceId = body.referenceId;
|
|
1718
|
+
const metadata = appMetadata(body.metadata);
|
|
1719
|
+
const intent = {
|
|
1720
|
+
id,
|
|
1721
|
+
projectId,
|
|
1722
|
+
account,
|
|
1723
|
+
connectedAccountId: resolveAccount(account),
|
|
1724
|
+
playground: isPlayground(account),
|
|
1725
|
+
status: method === "card" ? "requires_payment_method" : "requires_action",
|
|
1726
|
+
amount: body.amount,
|
|
1727
|
+
currency,
|
|
1728
|
+
method,
|
|
1729
|
+
referenceId,
|
|
1730
|
+
appMetadata: metadata,
|
|
1731
|
+
metadata: { ...metadata, projectId, stardeckReferenceId: referenceId },
|
|
1732
|
+
description: typeof body.description === "string" ? body.description : null,
|
|
1733
|
+
receiptEmail: typeof body.receiptEmail === "string" ? body.receiptEmail : null,
|
|
1734
|
+
returnUrl: typeof body.returnUrl === "string" ? body.returnUrl : null,
|
|
1735
|
+
clientSecret: `${id}_secret_sim`,
|
|
1736
|
+
qr: method === "promptpay" ? promptPayQr(id, created) : null,
|
|
1737
|
+
latestChargeId: null,
|
|
1738
|
+
amountRefunded: 0,
|
|
1739
|
+
lastPaymentError: null,
|
|
1740
|
+
created
|
|
1741
|
+
};
|
|
1742
|
+
state.stripePaymentIntents.set(id, intent);
|
|
1743
|
+
state.stripeIdempotentIntents.set(scopedKey, { fingerprint, id });
|
|
1744
|
+
console.log(
|
|
1745
|
+
`[stardeck-testing] stripe payment intent ${id} created method=${method} amount=${intent.amount} ${currency}`
|
|
1746
|
+
);
|
|
1747
|
+
return json({ paymentIntent: toStripePaymentIntentSummary(intent) }, 201);
|
|
1748
|
+
}
|
|
1749
|
+
function ownedIntent(id, projectId) {
|
|
1750
|
+
const intent = state.stripePaymentIntents.get(id);
|
|
1751
|
+
return intent && intent.projectId === projectId ? intent : void 0;
|
|
1752
|
+
}
|
|
1753
|
+
function cancelIntent(intent) {
|
|
1754
|
+
if (intent.status === "succeeded") {
|
|
1755
|
+
return err("This payment already succeeded and can't be canceled", 409, "already_succeeded");
|
|
1756
|
+
}
|
|
1757
|
+
intent.status = "canceled";
|
|
1758
|
+
return json({ paymentIntent: toStripePaymentIntentSummary(intent) });
|
|
1759
|
+
}
|
|
1760
|
+
function handleIntents(request, url, account, intentId, action, projectId) {
|
|
1761
|
+
if (!intentId && request.method === "POST") {
|
|
1762
|
+
return readJsonBody(request).then((body) => createIntent(body, account, projectId));
|
|
1763
|
+
}
|
|
1764
|
+
if (!intentId && request.method === "GET") {
|
|
1765
|
+
const referenceId = url.searchParams.get("referenceId");
|
|
1766
|
+
if (!referenceId) return err("referenceId is required", 400, "invalid_request");
|
|
1767
|
+
const paymentIntents = [...state.stripePaymentIntents.values()].filter((i) => i.projectId === projectId && i.referenceId === referenceId).map(toStripePaymentIntentSummary);
|
|
1768
|
+
return json({ paymentIntents });
|
|
1769
|
+
}
|
|
1770
|
+
if (!intentId) return null;
|
|
1771
|
+
const intent = ownedIntent(decode(intentId), projectId);
|
|
1772
|
+
if (!action && request.method === "GET") {
|
|
1773
|
+
if (!intent) return err("Payment intent not found", 404, "not_found");
|
|
1774
|
+
return json({ paymentIntent: toStripePaymentIntentSummary(intent) });
|
|
1775
|
+
}
|
|
1776
|
+
if (action === "cancel" && request.method === "POST") {
|
|
1777
|
+
return readJsonBody(request).then((body) => {
|
|
1778
|
+
if (typeof body.deploymentId !== "string" || !UUID_RE2.test(body.deploymentId)) {
|
|
1779
|
+
return err("deploymentId is required", 400, "invalid_request");
|
|
1780
|
+
}
|
|
1781
|
+
if (!intent) return err("Payment intent not found", 404, "not_found");
|
|
1782
|
+
return cancelIntent(intent);
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
return null;
|
|
1786
|
+
}
|
|
1787
|
+
async function handleStripePaymentIntentRequest(request, url, projectId) {
|
|
1788
|
+
const match = url.pathname.match(PATH_RE);
|
|
1789
|
+
if (!match) return null;
|
|
1790
|
+
if (!projectId) return err("Missing authentication", 401, "unauthorized");
|
|
1791
|
+
const account = decode(match[1]);
|
|
1792
|
+
const resource = match[2];
|
|
1793
|
+
if (request.method === "GET" && !url.searchParams.get("deploymentId")) {
|
|
1794
|
+
return err("deploymentId is required", 400, "invalid_request");
|
|
1795
|
+
}
|
|
1796
|
+
if (resource === "elements-config" && request.method === "GET") {
|
|
1797
|
+
return json({
|
|
1798
|
+
publishableKey: SIM_STRIPE_PUBLISHABLE_KEY,
|
|
1799
|
+
stripeAccount: resolveAccount(account),
|
|
1800
|
+
playground: isPlayground(account)
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
if (resource === "readiness" && request.method === "GET") {
|
|
1804
|
+
const methods = [...state.stripeAllowedMethods];
|
|
1805
|
+
return json({
|
|
1806
|
+
provider: "stripe",
|
|
1807
|
+
ready: state.stripeChargesEnabled && methods.length > 0,
|
|
1808
|
+
chargesEnabled: state.stripeChargesEnabled,
|
|
1809
|
+
methods,
|
|
1810
|
+
playground: isPlayground(account)
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
if (resource.startsWith("payment-intents")) {
|
|
1814
|
+
return handleIntents(request, url, account, match[3], match[4], projectId);
|
|
1815
|
+
}
|
|
1816
|
+
return null;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1538
1819
|
// src/simulator/webhook-signing.ts
|
|
1539
1820
|
var import_node_crypto4 = __toESM(require("crypto"));
|
|
1540
1821
|
|
|
@@ -1554,7 +1835,7 @@ var BEAM_BOLT_PAYMENT_METHODS = [
|
|
|
1554
1835
|
"SPAY_LATER"
|
|
1555
1836
|
];
|
|
1556
1837
|
var BEAM_BOLT_INTENT_STATUSES = ["PENDING", "PAID", "FAILED", "CANCELED", "EXPIRED"];
|
|
1557
|
-
var
|
|
1838
|
+
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;
|
|
1558
1839
|
function payErr(error, status = 400, code, details) {
|
|
1559
1840
|
const body = { error };
|
|
1560
1841
|
if (code) body.code = code;
|
|
@@ -1580,7 +1861,23 @@ function paymentOperationForRequest(request, pathname) {
|
|
|
1580
1861
|
if (request.method === "GET" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout-sessions\/[^/]+$/.test(pathname)) {
|
|
1581
1862
|
return "stripe.getCheckoutSessionStatus";
|
|
1582
1863
|
}
|
|
1583
|
-
return
|
|
1864
|
+
return stripeInlineOperation(request.method, pathname);
|
|
1865
|
+
}
|
|
1866
|
+
var STRIPE_INLINE_OPERATIONS = [
|
|
1867
|
+
["POST", /^\/api\/store\/(?!beam\/)[^/]+\/payment-intents$/, "stripe.createPaymentIntent"],
|
|
1868
|
+
["GET", /^\/api\/store\/(?!beam\/)[^/]+\/payment-intents$/, "stripe.findPaymentIntents"],
|
|
1869
|
+
["GET", /^\/api\/store\/(?!beam\/)[^/]+\/payment-intents\/[^/]+$/, "stripe.getPaymentIntent"],
|
|
1870
|
+
[
|
|
1871
|
+
"POST",
|
|
1872
|
+
/^\/api\/store\/(?!beam\/)[^/]+\/payment-intents\/[^/]+\/cancel$/,
|
|
1873
|
+
"stripe.cancelPaymentIntent"
|
|
1874
|
+
],
|
|
1875
|
+
["GET", /^\/api\/store\/(?!beam\/)[^/]+\/elements-config$/, "stripe.getElementsConfig"],
|
|
1876
|
+
["GET", /^\/api\/store\/(?!beam\/)[^/]+\/readiness$/, "stripe.getReadiness"]
|
|
1877
|
+
];
|
|
1878
|
+
function stripeInlineOperation(method, pathname) {
|
|
1879
|
+
const hit = STRIPE_INLINE_OPERATIONS.find(([m, re]) => m === method && re.test(pathname));
|
|
1880
|
+
return hit ? hit[2] : null;
|
|
1584
1881
|
}
|
|
1585
1882
|
function consumePaymentFailure(request, pathname) {
|
|
1586
1883
|
const operation = paymentOperationForRequest(request, pathname);
|
|
@@ -1610,11 +1907,11 @@ function isBoltPaymentMethod(value) {
|
|
|
1610
1907
|
function isBoltIntentStatus(value) {
|
|
1611
1908
|
return BEAM_BOLT_INTENT_STATUSES.includes(value);
|
|
1612
1909
|
}
|
|
1613
|
-
function
|
|
1614
|
-
if (Array.isArray(value)) return `[${value.map(
|
|
1910
|
+
function stableStringify2(value) {
|
|
1911
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify2).join(",")}]`;
|
|
1615
1912
|
if (value && typeof value === "object") {
|
|
1616
1913
|
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
1617
|
-
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${
|
|
1914
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify2(v)}`).join(",")}}`;
|
|
1618
1915
|
}
|
|
1619
1916
|
return JSON.stringify(value);
|
|
1620
1917
|
}
|
|
@@ -1720,10 +2017,16 @@ function storedStatusesForFilter(statuses) {
|
|
|
1720
2017
|
}
|
|
1721
2018
|
return [...stored];
|
|
1722
2019
|
}
|
|
1723
|
-
async function handlePaymentsRequest(request, url) {
|
|
2020
|
+
async function handlePaymentsRequest(request, url, signedProjectId = null) {
|
|
1724
2021
|
const pathname = url.pathname;
|
|
1725
2022
|
const injectedFailure = consumePaymentFailure(request, pathname);
|
|
1726
2023
|
if (injectedFailure) return injectedFailure;
|
|
2024
|
+
const inlineStripeResponse = await handleStripePaymentIntentRequest(
|
|
2025
|
+
request,
|
|
2026
|
+
url,
|
|
2027
|
+
signedProjectId
|
|
2028
|
+
);
|
|
2029
|
+
if (inlineStripeResponse) return inlineStripeResponse;
|
|
1727
2030
|
const directChargeResponse = await handleDirectChargeRequest(request, pathname);
|
|
1728
2031
|
if (directChargeResponse) return directChargeResponse;
|
|
1729
2032
|
if (/\/billing-portal$/.test(pathname)) {
|
|
@@ -1822,7 +2125,7 @@ async function handlePaymentsRequest(request, url) {
|
|
|
1822
2125
|
code: "too_small"
|
|
1823
2126
|
});
|
|
1824
2127
|
}
|
|
1825
|
-
if (typeof body.deploymentId !== "string" || !
|
|
2128
|
+
if (typeof body.deploymentId !== "string" || !UUID_RE3.test(body.deploymentId)) {
|
|
1826
2129
|
issues.push({
|
|
1827
2130
|
path: ["deploymentId"],
|
|
1828
2131
|
message: "Invalid uuid",
|
|
@@ -2006,7 +2309,7 @@ async function handlePaymentsRequest(request, url) {
|
|
|
2006
2309
|
const comparable = { ...body };
|
|
2007
2310
|
delete comparable.deploymentId;
|
|
2008
2311
|
delete comparable.idempotencyKey;
|
|
2009
|
-
const fingerprint =
|
|
2312
|
+
const fingerprint = stableStringify2(comparable);
|
|
2010
2313
|
if (scopedKey) {
|
|
2011
2314
|
const saved = state.idempotentCheckouts.get(scopedKey);
|
|
2012
2315
|
if (saved) {
|
|
@@ -2707,6 +3010,16 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
2707
3010
|
return edgeFailure(`No edge simulator for ${method} .../edge${subPath}`, 404);
|
|
2708
3011
|
}
|
|
2709
3012
|
|
|
3013
|
+
// src/simulator/schedules.ts
|
|
3014
|
+
async function handleScheduleWake(request, scheduleKey) {
|
|
3015
|
+
if (request.method !== "POST") return failure(`Method ${request.method} not allowed`, 405);
|
|
3016
|
+
const body = await readJsonBody(request);
|
|
3017
|
+
const at = typeof body.at === "string" ? new Date(body.at) : null;
|
|
3018
|
+
if (!at || Number.isNaN(at.getTime())) return failure("`at` must be an ISO date-time", 400);
|
|
3019
|
+
state.scheduleWakes.push({ scheduleKey, at, requestedAt: /* @__PURE__ */ new Date() });
|
|
3020
|
+
return success({ woken: true });
|
|
3021
|
+
}
|
|
3022
|
+
|
|
2710
3023
|
// src/simulator/starlens.ts
|
|
2711
3024
|
var LATEST_VERSION = "v2";
|
|
2712
3025
|
function starlensFailure(error, status, code) {
|
|
@@ -2836,7 +3149,7 @@ function requiresDeploymentHmac(request, url) {
|
|
|
2836
3149
|
return !isPresignedPut;
|
|
2837
3150
|
}
|
|
2838
3151
|
const dataStoreMatch = url.pathname.match(/^\/api\/data-stores\/[^/]+(\/.*)?$/);
|
|
2839
|
-
const
|
|
3152
|
+
const isEmail2 = url.pathname === "/api/email/send";
|
|
2840
3153
|
const identitiesMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/identities(\/.*)?$/);
|
|
2841
3154
|
const storeMatch = url.pathname.match(/^\/api\/store\//);
|
|
2842
3155
|
const messagingMatch = url.pathname.match(
|
|
@@ -2844,8 +3157,10 @@ function requiresDeploymentHmac(request, url) {
|
|
|
2844
3157
|
);
|
|
2845
3158
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
2846
3159
|
const isStarlens = url.pathname === "/api/starlens/extract";
|
|
2847
|
-
|
|
3160
|
+
const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
|
|
3161
|
+
return !!(isStarlens || scheduleWakeMatch || dataStoreMatch || isEmail2 || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
|
|
2848
3162
|
}
|
|
3163
|
+
var SCHEDULE_WAKE_PATH = /^\/api\/deployments\/[^/]+\/schedules\/([^/]+)\/wake$/;
|
|
2849
3164
|
function deploymentIdFromPath(url) {
|
|
2850
3165
|
const match = url.pathname.match(/\/api\/deployments\/([^/]+)(?:\/|$)/);
|
|
2851
3166
|
if (!match) return null;
|
|
@@ -2866,7 +3181,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2866
3181
|
return authMatch[1].endsWith("verify") ? handleAuthVerify(request) : handleAuthRefresh(request);
|
|
2867
3182
|
}
|
|
2868
3183
|
const dataStoreMatch = url.pathname.match(/^\/api\/data-stores\/[^/]+(\/.*)?$/);
|
|
2869
|
-
const
|
|
3184
|
+
const isEmail2 = url.pathname === "/api/email/send";
|
|
2870
3185
|
const identitiesMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/identities(\/.*)?$/);
|
|
2871
3186
|
const storeMatch = url.pathname.match(/^\/api\/store\//);
|
|
2872
3187
|
const messagingMatch = url.pathname.match(
|
|
@@ -2875,6 +3190,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2875
3190
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
2876
3191
|
const isStorageHost = url.hostname === STORAGE_TEST_HOST;
|
|
2877
3192
|
let signedDeploymentId = null;
|
|
3193
|
+
let signedProjectId = null;
|
|
2878
3194
|
if (requiresDeploymentHmac(request, url)) {
|
|
2879
3195
|
const authHeader = request.headers.get("X-Stardeck-Auth");
|
|
2880
3196
|
if (!authHeader) {
|
|
@@ -2887,6 +3203,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2887
3203
|
return failure("Invalid authentication", 401);
|
|
2888
3204
|
}
|
|
2889
3205
|
signedDeploymentId = authPayload.deploymentId;
|
|
3206
|
+
signedProjectId = authPayload.projectId;
|
|
2890
3207
|
}
|
|
2891
3208
|
if (isStorageHost) {
|
|
2892
3209
|
return handleStorageRequest(request, url);
|
|
@@ -2894,14 +3211,14 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2894
3211
|
if (url.hostname === SIM_BEAM_HOST) {
|
|
2895
3212
|
return handleCardTokenRequest(request, url);
|
|
2896
3213
|
}
|
|
2897
|
-
if (
|
|
3214
|
+
if (isEmail2 && request.method === "POST") {
|
|
2898
3215
|
return handleEmailSend(request);
|
|
2899
3216
|
}
|
|
2900
3217
|
if (identitiesMatch) {
|
|
2901
3218
|
return handleIdentitiesRequest(request, identitiesMatch[1] ?? "");
|
|
2902
3219
|
}
|
|
2903
3220
|
if (storeMatch) {
|
|
2904
|
-
return handlePaymentsRequest(request, url);
|
|
3221
|
+
return handlePaymentsRequest(request, url, signedProjectId);
|
|
2905
3222
|
}
|
|
2906
3223
|
if (messagingMatch) {
|
|
2907
3224
|
const channel = messagingMatch[1];
|
|
@@ -2910,6 +3227,10 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2910
3227
|
if (edgeMatch) {
|
|
2911
3228
|
return handleEdgeRequest(request, edgeMatch[1] ?? "");
|
|
2912
3229
|
}
|
|
3230
|
+
const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
|
|
3231
|
+
if (scheduleWakeMatch) {
|
|
3232
|
+
return handleScheduleWake(request, decodeURIComponent(scheduleWakeMatch[1]));
|
|
3233
|
+
}
|
|
2913
3234
|
if (url.pathname === "/api/starlens/extract" && request.method === "POST") {
|
|
2914
3235
|
return handleStarlensExtract(request, signedDeploymentId);
|
|
2915
3236
|
}
|
|
@@ -2934,7 +3255,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2934
3255
|
}
|
|
2935
3256
|
}
|
|
2936
3257
|
return failure(
|
|
2937
|
-
`[stardeck-testing] No simulator for ${request.method} ${url.pathname}. Supported: data-store query/mutate/schema, email send, identities CRUD, payments store/checkout, storage upload/files, messaging send, edge print/display/bindings, starlens extract, auth verify/refresh, Neon /sql.`,
|
|
3258
|
+
`[stardeck-testing] No simulator for ${request.method} ${url.pathname}. Supported: data-store query/mutate/schema, email send, identities CRUD, payments store/checkout, storage upload/files, messaging send, edge print/display/bindings, starlens extract, schedule wakes, auth verify/refresh, Neon /sql.`,
|
|
2938
3259
|
404
|
|
2939
3260
|
);
|
|
2940
3261
|
}
|