@stardeck-customer-apps/testing 0.20.0 → 0.22.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/index.mjs CHANGED
@@ -309,6 +309,11 @@ var state = globalSingleton("state", () => ({
309
309
  boltIntentCounter: 0,
310
310
  boltCharges: [],
311
311
  boltChargeCounter: 0,
312
+ directCharges: /* @__PURE__ */ new Map(),
313
+ directChargeCounter: 0,
314
+ cardTokens: /* @__PURE__ */ new Map(),
315
+ cardTokenCounter: 0,
316
+ cardTokenizationEnabled: true,
312
317
  uploads: [],
313
318
  uploadCounter: 0,
314
319
  storageFiles: /* @__PURE__ */ new Map(),
@@ -1676,6 +1681,311 @@ function createDirectory() {
1676
1681
  };
1677
1682
  }
1678
1683
 
1684
+ // src/simulator/beam-direct-charges.ts
1685
+ var SIM_BEAM_HOST = "beam.stardeck.test";
1686
+ var SIM_BEAM_MERCHANT_ID = "sim_merchant";
1687
+ var SIM_BEAM_PUBLISHABLE_KEY = "pk_test_sim";
1688
+ var DIRECT_CHARGE_METHODS = /* @__PURE__ */ new Set([
1689
+ "CARD",
1690
+ "CARD_INSTALLMENTS",
1691
+ "CARD_TOKEN",
1692
+ "CARD_TOKEN_INSTALLMENTS",
1693
+ "QR_PROMPT_PAY",
1694
+ "ALIPAY",
1695
+ "ALIPAY_PLUS",
1696
+ "WECHAT_PAY",
1697
+ "TRUE_MONEY",
1698
+ "LINE_PAY",
1699
+ "SHOPEE_PAY",
1700
+ "KPLUS",
1701
+ "MAKE",
1702
+ "SCB_EASY",
1703
+ "KRUNGSRI_APP",
1704
+ "BANGKOK_BANK_APP",
1705
+ "SPAY_LATER"
1706
+ ]);
1707
+ var TEST_CARDS = {
1708
+ "4111111111111111": { outcome: "SUCCEEDED" },
1709
+ "5372074248113841": { outcome: "SUCCEEDED" },
1710
+ "378282246310005": { outcome: "SUCCEEDED" },
1711
+ "4953261730509988": { outcome: "REDIRECT" },
1712
+ "4389783280144506": { outcome: "REDIRECT" },
1713
+ "4943129900084541": { outcome: "FAILED", failureCode: "CH_INSUFFICIENT_FUNDS" },
1714
+ "4111111111000025": { outcome: "FAILED", failureCode: "CH_CARD_DECLINED" },
1715
+ "4111111111000033": { outcome: "FAILED", failureCode: "CH_CARD_RESTRICTED" }
1716
+ };
1717
+ var SIM_QR_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
1718
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1719
+ function invalid(issues) {
1720
+ return json({ error: "Invalid request body", details: issues }, 400);
1721
+ }
1722
+ var DEVICE_TYPES = /* @__PURE__ */ new Set(["ANDROID", "IOS", "WEB"]);
1723
+ function issue(path, message, code = "custom") {
1724
+ return { path, message, code };
1725
+ }
1726
+ function validateChargeFields(body) {
1727
+ const issues = [];
1728
+ const isInt = (v) => typeof v === "number" && Number.isInteger(v);
1729
+ if (!isInt(body.amount) || body.amount < 1) {
1730
+ issues.push(issue(["amount"], "Number must be greater than 0", "too_small"));
1731
+ }
1732
+ if (typeof body.deploymentId !== "string" || !UUID_RE.test(body.deploymentId)) {
1733
+ issues.push(issue(["deploymentId"], "Invalid deployment ID", "invalid_string"));
1734
+ }
1735
+ if (body.referenceId !== void 0 && (typeof body.referenceId !== "string" || body.referenceId.length > 100)) {
1736
+ issues.push(issue(["referenceId"], "Too long", "too_big"));
1737
+ }
1738
+ const expiry = body.expiryDurationInSec;
1739
+ if (expiry !== void 0 && (!isInt(expiry) || expiry < 60 || expiry > 3600)) {
1740
+ issues.push(issue(["expiryDurationInSec"], "Must be an integer from 60 to 3600", "too_big"));
1741
+ }
1742
+ if (body.returnUrl !== void 0 && (typeof body.returnUrl !== "string" || !URL.canParse(body.returnUrl))) {
1743
+ issues.push(issue(["returnUrl"], "Invalid URL", "invalid_format"));
1744
+ }
1745
+ if (body.deviceType !== void 0 && !DEVICE_TYPES.has(String(body.deviceType))) {
1746
+ issues.push(issue(["deviceType"], "Invalid enum value", "invalid_value"));
1747
+ }
1748
+ const key = body.idempotencyKey;
1749
+ if (key !== void 0 && (typeof key !== "string" || key.length < 1 || key.length > 255)) {
1750
+ issues.push(issue(["idempotencyKey"], "Must be 1 to 255 characters", "too_small"));
1751
+ }
1752
+ return issues;
1753
+ }
1754
+ function validateChargeMethod(body) {
1755
+ const method = body.paymentMethod;
1756
+ const type = method?.paymentMethodType;
1757
+ if (typeof type !== "string" || !DIRECT_CHARGE_METHODS.has(type)) {
1758
+ return [issue(["paymentMethod", "paymentMethodType"], "Invalid enum value", "invalid_value")];
1759
+ }
1760
+ const issues = [];
1761
+ const isToken = type === "CARD_TOKEN" || type === "CARD_TOKEN_INSTALLMENTS";
1762
+ const token = method?.cardToken;
1763
+ if (isToken && (typeof token?.cardTokenId !== "string" || token.cardTokenId === "")) {
1764
+ issues.push(
1765
+ issue(["paymentMethod", "cardToken"], `${type} requires paymentMethod.cardToken.cardTokenId`)
1766
+ );
1767
+ }
1768
+ if (token?.securityCode !== void 0 && !/^\d{3,4}$/.test(String(token.securityCode))) {
1769
+ issues.push(
1770
+ issue(
1771
+ ["paymentMethod", "cardToken", "securityCode"],
1772
+ "Invalid security code",
1773
+ "invalid_format"
1774
+ )
1775
+ );
1776
+ }
1777
+ if (type === "CARD_TOKEN_INSTALLMENTS" && ![3, 4, 6, 10].includes(method?.installmentPeriod)) {
1778
+ issues.push(
1779
+ issue(
1780
+ ["paymentMethod", "installmentPeriod"],
1781
+ "CARD_TOKEN_INSTALLMENTS requires paymentMethod.installmentPeriod (3, 4, 6 or 10)"
1782
+ )
1783
+ );
1784
+ }
1785
+ if (type !== "QR_PROMPT_PAY" && !body.returnUrl) {
1786
+ issues.push(
1787
+ issue(
1788
+ ["returnUrl"],
1789
+ `${type} sends the shopper away to pay; returnUrl is required to bring them back`
1790
+ )
1791
+ );
1792
+ }
1793
+ if (type === "CARD" || type === "CARD_INSTALLMENTS") {
1794
+ issues.push(
1795
+ issue(
1796
+ ["paymentMethod", "paymentMethodType"],
1797
+ `${type} takes raw card data; tokenize the card in the browser and charge CARD_TOKEN instead`
1798
+ )
1799
+ );
1800
+ }
1801
+ return issues;
1802
+ }
1803
+ function validateDirectCharge(body) {
1804
+ return [...validateChargeFields(body), ...validateChargeMethod(body)];
1805
+ }
1806
+ function chargeResponse(charge) {
1807
+ const response = {
1808
+ chargeId: charge.id,
1809
+ actionRequired: charge.actionRequired,
1810
+ paymentMethodType: charge.paymentMethodType
1811
+ };
1812
+ if (charge.actionRequired === "ENCODED_IMAGE") {
1813
+ response.encodedImage = {
1814
+ imageBase64Encoded: SIM_QR_PNG,
1815
+ rawData: `00020101021230SIM${charge.id}`,
1816
+ expiry: charge.expiresAt
1817
+ };
1818
+ }
1819
+ if (charge.actionRequired === "REDIRECT") {
1820
+ response.redirect = { redirectUrl: charge.redirectUrl };
1821
+ response.redirectUrl = charge.redirectUrl;
1822
+ }
1823
+ return response;
1824
+ }
1825
+ function nextChargeId() {
1826
+ state.directChargeCounter += 1;
1827
+ return `ch_sim_${state.directChargeCounter}`;
1828
+ }
1829
+ function createCharge(body) {
1830
+ const method = body.paymentMethod;
1831
+ const type = method.paymentMethodType;
1832
+ const id = nextChargeId();
1833
+ const now3 = /* @__PURE__ */ new Date();
1834
+ const expirySec = typeof body.expiryDurationInSec === "number" ? body.expiryDurationInSec : 30 * 60;
1835
+ const returnUrl = typeof body.returnUrl === "string" ? body.returnUrl : null;
1836
+ let actionRequired = "REDIRECT";
1837
+ let status = "PENDING";
1838
+ let failureCode = null;
1839
+ let cardLast4 = null;
1840
+ if (type === "QR_PROMPT_PAY") {
1841
+ actionRequired = "ENCODED_IMAGE";
1842
+ } else if (type === "CARD_TOKEN" || type === "CARD_TOKEN_INSTALLMENTS") {
1843
+ const token = state.cardTokens.get(method.cardToken.cardTokenId);
1844
+ if (!token) {
1845
+ return json({ error: "Card token not found", code: "CARD_TOKEN_NOT_FOUND" }, 404);
1846
+ }
1847
+ cardLast4 = token.last4;
1848
+ const card = TEST_CARDS[token.pan] ?? {
1849
+ outcome: "FAILED",
1850
+ failureCode: "CH_PROCESSING_FAILED"
1851
+ };
1852
+ if (card.outcome === "REDIRECT") {
1853
+ actionRequired = "REDIRECT";
1854
+ } else {
1855
+ actionRequired = "NONE";
1856
+ status = card.outcome;
1857
+ failureCode = card.failureCode ?? null;
1858
+ }
1859
+ }
1860
+ const charge = {
1861
+ id,
1862
+ status,
1863
+ amount: body.amount,
1864
+ currency: String(body.currency ?? "THB"),
1865
+ paymentMethodType: type,
1866
+ referenceId: typeof body.referenceId === "string" ? body.referenceId : null,
1867
+ deviceType: typeof body.deviceType === "string" ? body.deviceType : null,
1868
+ returnUrl,
1869
+ actionRequired,
1870
+ redirectUrl: actionRequired === "REDIRECT" ? `https://${SIM_BEAM_HOST}/pay/${id}${returnUrl ? `?returnUrl=${encodeURIComponent(returnUrl)}` : ""}` : null,
1871
+ expiresAt: new Date(now3.getTime() + expirySec * 1e3).toISOString(),
1872
+ failureCode,
1873
+ cardLast4,
1874
+ idempotencyKey: typeof body.idempotencyKey === "string" ? body.idempotencyKey : null,
1875
+ createdAt: now3.toISOString()
1876
+ };
1877
+ state.directCharges.set(id, charge);
1878
+ return charge;
1879
+ }
1880
+ async function handleDirectChargeRequest(request, pathname) {
1881
+ if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/charges$/.test(pathname)) {
1882
+ const body = await readJsonBody(request);
1883
+ const issues = validateDirectCharge(body);
1884
+ if (issues.length > 0) return invalid(issues);
1885
+ const key = typeof body.idempotencyKey === "string" ? body.idempotencyKey : null;
1886
+ if (key) {
1887
+ const existing = [...state.directCharges.values()].find((c) => c.idempotencyKey === key);
1888
+ if (existing) return json(chargeResponse(existing));
1889
+ }
1890
+ const created = createCharge(body);
1891
+ if (created instanceof Response) return created;
1892
+ return json(chargeResponse(created));
1893
+ }
1894
+ const getMatch = pathname.match(/^\/api\/store\/beam\/[^/]+\/charges\/([^/]+)$/);
1895
+ if (request.method === "GET" && getMatch) {
1896
+ const charge = state.directCharges.get(decodeURIComponent(getMatch[1]));
1897
+ if (!charge) return json({ error: "Charge not found", code: "NOT_FOUND" }, 404);
1898
+ return json({ charge: toChargeDetail(charge) });
1899
+ }
1900
+ if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/card-tokenization$/.test(pathname)) {
1901
+ if (!state.cardTokenizationEnabled) {
1902
+ return json(
1903
+ {
1904
+ error: "Add the Beam publishable key to this merchant account to take cards in your own checkout",
1905
+ code: "CARD_TOKENIZATION_NOT_CONFIGURED"
1906
+ },
1907
+ 409
1908
+ );
1909
+ }
1910
+ return json({
1911
+ merchantId: SIM_BEAM_MERCHANT_ID,
1912
+ publishableKey: SIM_BEAM_PUBLISHABLE_KEY,
1913
+ tokenizeUrl: `https://${SIM_BEAM_HOST}/client/v1/card-tokens`,
1914
+ isSandbox: true
1915
+ });
1916
+ }
1917
+ return null;
1918
+ }
1919
+ function toChargeDetail(charge) {
1920
+ return {
1921
+ id: charge.id,
1922
+ chargeId: charge.id,
1923
+ status: charge.status,
1924
+ amount: charge.amount,
1925
+ currency: charge.currency,
1926
+ paymentMethodType: charge.paymentMethodType,
1927
+ ...charge.referenceId !== null ? { referenceId: charge.referenceId } : {},
1928
+ ...charge.failureCode ? { failureCode: charge.failureCode } : {},
1929
+ source: "API",
1930
+ createdAt: charge.createdAt
1931
+ };
1932
+ }
1933
+ async function handleCardTokenRequest(request, url) {
1934
+ if (request.method !== "POST" || url.pathname !== "/client/v1/card-tokens") {
1935
+ return json({ error: `No Beam simulator for ${request.method} ${url.pathname}` }, 404);
1936
+ }
1937
+ const expected = `Basic ${btoa(`${SIM_BEAM_MERCHANT_ID}:${SIM_BEAM_PUBLISHABLE_KEY}`)}`;
1938
+ if (request.headers.get("Authorization") !== expected) {
1939
+ return json(
1940
+ {
1941
+ message: "invalid credentials",
1942
+ error: { errorCode: "INVALID_CREDENTIALS_ERROR", errorMessage: "invalid credentials" }
1943
+ },
1944
+ 401
1945
+ );
1946
+ }
1947
+ const body = await readJsonBody(request);
1948
+ const pan = typeof body.pan === "string" ? body.pan : "";
1949
+ const month = body.expiryMonth;
1950
+ const year = body.expiryYear;
1951
+ if (!/^\d{12,19}$/.test(pan) || typeof month !== "number" || month < 1 || month > 12 || typeof year !== "number" || year < 0 || year > 99) {
1952
+ return json(
1953
+ {
1954
+ message: "inputs are failing validation",
1955
+ error: {
1956
+ errorCode: "API_VALIDATION_ERROR",
1957
+ errorMessage: "inputs are failing validation"
1958
+ }
1959
+ },
1960
+ 400
1961
+ );
1962
+ }
1963
+ state.cardTokenCounter += 1;
1964
+ const token = {
1965
+ id: `ctok_sim_${state.cardTokenCounter}`,
1966
+ pan,
1967
+ last4: pan.slice(-4),
1968
+ expiryMonth: month,
1969
+ expiryYear: year
1970
+ };
1971
+ state.cardTokens.set(token.id, token);
1972
+ return json({ id: token.id });
1973
+ }
1974
+ function chargeWebhookPayload(charge) {
1975
+ return {
1976
+ chargeId: charge.id,
1977
+ merchantId: SIM_BEAM_MERCHANT_ID,
1978
+ ...charge.referenceId !== null ? { referenceId: charge.referenceId } : {},
1979
+ status: charge.status,
1980
+ currency: charge.currency,
1981
+ amount: charge.amount,
1982
+ source: "API",
1983
+ sourceId: charge.id,
1984
+ ...charge.failureCode ? { failureCode: charge.failureCode } : {},
1985
+ paymentMethod: { paymentMethodType: charge.paymentMethodType }
1986
+ };
1987
+ }
1988
+
1679
1989
  // src/simulator/payments.ts
1680
1990
  import crypto5 from "crypto";
1681
1991
 
@@ -1790,7 +2100,7 @@ var BEAM_BOLT_PAYMENT_METHODS = [
1790
2100
  "SPAY_LATER"
1791
2101
  ];
1792
2102
  var BEAM_BOLT_INTENT_STATUSES = ["PENDING", "PAID", "FAILED", "CANCELED", "EXPIRED"];
1793
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
2103
+ 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;
1794
2104
  function payErr(error, status = 400, code, details) {
1795
2105
  const body = { error };
1796
2106
  if (code) body.code = code;
@@ -1804,6 +2114,12 @@ function paymentOperationForRequest(request, pathname) {
1804
2114
  if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/payment-links\/[^/]+$/.test(pathname)) {
1805
2115
  return "beam.getPaymentLinkStatus";
1806
2116
  }
2117
+ if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/charges$/.test(pathname)) {
2118
+ return "beam.createDirectCharge";
2119
+ }
2120
+ if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/charges\/[^/]+$/.test(pathname)) {
2121
+ return "beam.getCharge";
2122
+ }
1807
2123
  if (request.method === "POST" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout$/.test(pathname)) {
1808
2124
  return "stripe.createCheckout";
1809
2125
  }
@@ -1979,6 +2295,8 @@ async function handlePaymentsRequest(request, url) {
1979
2295
  const pathname = url.pathname;
1980
2296
  const injectedFailure = consumePaymentFailure(request, pathname);
1981
2297
  if (injectedFailure) return injectedFailure;
2298
+ const directChargeResponse = await handleDirectChargeRequest(request, pathname);
2299
+ if (directChargeResponse) return directChargeResponse;
1982
2300
  if (/\/billing-portal$/.test(pathname)) {
1983
2301
  return payErr("Not found", 404, "NOT_FOUND");
1984
2302
  }
@@ -2075,7 +2393,7 @@ async function handlePaymentsRequest(request, url) {
2075
2393
  code: "too_small"
2076
2394
  });
2077
2395
  }
2078
- if (typeof body.deploymentId !== "string" || !UUID_RE.test(body.deploymentId)) {
2396
+ if (typeof body.deploymentId !== "string" || !UUID_RE2.test(body.deploymentId)) {
2079
2397
  issues.push({
2080
2398
  path: ["deploymentId"],
2081
2399
  message: "Invalid uuid",
@@ -2262,6 +2580,18 @@ async function handlePaymentsRequest(request, url) {
2262
2580
  }
2263
2581
  return payErr(`No payments simulator for ${request.method} ${pathname}`, 404);
2264
2582
  }
2583
+ function requirePendingDirectCharge(chargeId) {
2584
+ const charge = state.directCharges.get(chargeId);
2585
+ if (!charge) {
2586
+ throw new Error(`[stardeck-testing] Unknown direct charge id: ${chargeId}`);
2587
+ }
2588
+ if (charge.status !== "PENDING") {
2589
+ throw new Error(
2590
+ `[stardeck-testing] Direct charge ${chargeId} is ${charge.status}, not PENDING`
2591
+ );
2592
+ }
2593
+ return charge;
2594
+ }
2265
2595
  function createPayments() {
2266
2596
  return {
2267
2597
  get checkouts() {
@@ -2380,6 +2710,26 @@ function createPayments() {
2380
2710
  if (!intent) return void 0;
2381
2711
  return toBoltIntentRecord(intent);
2382
2712
  },
2713
+ get directCharges() {
2714
+ return [...state.directCharges.values()];
2715
+ },
2716
+ getDirectCharge(chargeId) {
2717
+ return state.directCharges.get(chargeId);
2718
+ },
2719
+ completeDirectCharge(chargeId) {
2720
+ const charge = requirePendingDirectCharge(chargeId);
2721
+ charge.status = "SUCCEEDED";
2722
+ return chargeWebhookPayload(charge);
2723
+ },
2724
+ failDirectCharge(chargeId, failureCode = "CH_PROCESSING_FAILED") {
2725
+ const charge = requirePendingDirectCharge(chargeId);
2726
+ charge.status = "FAILED";
2727
+ charge.failureCode = failureCode;
2728
+ return chargeWebhookPayload(charge);
2729
+ },
2730
+ setCardTokenization(enabled) {
2731
+ state.cardTokenizationEnabled = enabled;
2732
+ },
2383
2733
  clear() {
2384
2734
  state.checkouts = [];
2385
2735
  state.checkoutCounter = 0;
@@ -2394,6 +2744,11 @@ function createPayments() {
2394
2744
  state.boltIntentCounter = 0;
2395
2745
  state.boltCharges = [];
2396
2746
  state.boltChargeCounter = 0;
2747
+ state.directCharges.clear();
2748
+ state.directChargeCounter = 0;
2749
+ state.cardTokens.clear();
2750
+ state.cardTokenCounter = 0;
2751
+ state.cardTokenizationEnabled = true;
2397
2752
  },
2398
2753
  get count() {
2399
2754
  return state.checkouts.length;
@@ -3063,6 +3418,7 @@ function createEdge() {
3063
3418
  }
3064
3419
 
3065
3420
  // src/simulator/starlens.ts
3421
+ var LATEST_VERSION = "v2";
3066
3422
  function starlensFailure(error, status, code) {
3067
3423
  return json({ success: false, error, code }, status);
3068
3424
  }
@@ -3081,11 +3437,18 @@ function nullFlagged(output, issues) {
3081
3437
  }
3082
3438
  return copy;
3083
3439
  }
3084
- async function handleStarlensExtract(request, signedDeploymentId) {
3085
- const body = await readJsonBody(request);
3086
- if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
3087
- return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
3088
- }
3440
+ function isInputShape(input) {
3441
+ if (!input || typeof input !== "object") return false;
3442
+ const i = input;
3443
+ return typeof i.url === "string" || typeof i.data === "string" && i.data.length > 0 && typeof i.mediaType === "string";
3444
+ }
3445
+ var NON_IMAGE_EXTENSION = /\.(pdf|mp3|m4a|wav|ogg|aac|flac|mp4|mov|webm|mkv)(\?|#|$)/i;
3446
+ function isCroppable(input) {
3447
+ if (typeof input.mediaType === "string") return input.mediaType.startsWith("image/");
3448
+ const name = typeof input.filename === "string" ? input.filename : input.url;
3449
+ return !(typeof name === "string" && NON_IMAGE_EXTENSION.test(name));
3450
+ }
3451
+ function validateStarlensBody(body) {
3089
3452
  const inputs = Array.isArray(body.inputs) ? body.inputs : [];
3090
3453
  if (inputs.length === 0) {
3091
3454
  return starlensFailure(
@@ -3094,6 +3457,13 @@ async function handleStarlensExtract(request, signedDeploymentId) {
3094
3457
  "VALIDATION_ERROR"
3095
3458
  );
3096
3459
  }
3460
+ if (!inputs.every(isInputShape)) {
3461
+ return starlensFailure(
3462
+ "Validation failed \u2014 inputs: each input needs a url, or data with a mediaType",
3463
+ 400,
3464
+ "VALIDATION_ERROR"
3465
+ );
3466
+ }
3097
3467
  const schema = body.schema;
3098
3468
  if (!schema || schema.type !== "object" || typeof schema.properties !== "object") {
3099
3469
  return starlensFailure(
@@ -3102,8 +3472,35 @@ async function handleStarlensExtract(request, signedDeploymentId) {
3102
3472
  "VALIDATION_ERROR"
3103
3473
  );
3104
3474
  }
3475
+ const version = body.version ?? "latest";
3476
+ if (version !== "v1" && version !== "v2" && version !== "latest") {
3477
+ return starlensFailure("Validation failed \u2014 version: unknown version", 400, "VALIDATION_ERROR");
3478
+ }
3479
+ const effort = body.effort ?? "low";
3480
+ if (effort !== "low" && effort !== "high") {
3481
+ return starlensFailure("Validation failed \u2014 effort: low or high", 400, "VALIDATION_ERROR");
3482
+ }
3483
+ const resolved = version === "latest" ? LATEST_VERSION : version;
3484
+ if (effort === "high" && resolved === "v1") {
3485
+ return starlensFailure(
3486
+ `Starlens v1 does not support effort "high"; use version "v2"`,
3487
+ 400,
3488
+ "unsupported_effort"
3489
+ );
3490
+ }
3491
+ return { version, resolved, effort, inputs, schema };
3492
+ }
3493
+ async function handleStarlensExtract(request, signedDeploymentId) {
3494
+ const body = await readJsonBody(request);
3495
+ const valid = validateStarlensBody(body);
3496
+ if (valid instanceof Response) return valid;
3497
+ const { version, resolved, effort, inputs, schema } = valid;
3498
+ if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
3499
+ return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
3500
+ }
3105
3501
  const call = {
3106
- version: typeof body.version === "string" ? body.version : "latest",
3502
+ version,
3503
+ effort,
3107
3504
  inputs: inputs.map((i) => ({
3108
3505
  name: typeof i.filename === "string" ? i.filename : typeof i.url === "string" ? i.url : "upload",
3109
3506
  mediaType: typeof i.mediaType === "string" ? i.mediaType : null,
@@ -3125,8 +3522,10 @@ async function handleStarlensExtract(request, signedDeploymentId) {
3125
3522
  return starlensFailure(reply.reject, 422, "unsupported_document");
3126
3523
  }
3127
3524
  const issues = reply.issues ?? [];
3525
+ const ran = reply.effort ?? (effort === "high" && !inputs.some(isCroppable) ? "low" : effort);
3128
3526
  return success({
3129
- version: call.version === "latest" ? "v1" : call.version,
3527
+ version: resolved,
3528
+ effort: ran,
3130
3529
  output: nullFlagged(reply.output, issues),
3131
3530
  issues,
3132
3531
  usage: { inputTokens: 0, outputTokens: 0, cost: 0 }
@@ -3229,6 +3628,9 @@ async function handleSimulatedRequest(request, url) {
3229
3628
  if (isStorageHost) {
3230
3629
  return handleStorageRequest(request, url);
3231
3630
  }
3631
+ if (url.hostname === SIM_BEAM_HOST) {
3632
+ return handleCardTokenRequest(request, url);
3633
+ }
3232
3634
  if (isEmail && request.method === "POST") {
3233
3635
  return handleEmailSend(request);
3234
3636
  }
@@ -70,6 +70,11 @@ var state = globalSingleton("state", () => ({
70
70
  boltIntentCounter: 0,
71
71
  boltCharges: [],
72
72
  boltChargeCounter: 0,
73
+ directCharges: /* @__PURE__ */ new Map(),
74
+ directChargeCounter: 0,
75
+ cardTokens: /* @__PURE__ */ new Map(),
76
+ cardTokenCounter: 0,
77
+ cardTokenizationEnabled: true,
73
78
  uploads: [],
74
79
  uploadCounter: 0,
75
80
  storageFiles: /* @__PURE__ */ new Map(),
@@ -43,6 +43,11 @@ var state = globalSingleton("state", () => ({
43
43
  boltIntentCounter: 0,
44
44
  boltCharges: [],
45
45
  boltChargeCounter: 0,
46
+ directCharges: /* @__PURE__ */ new Map(),
47
+ directChargeCounter: 0,
48
+ cardTokens: /* @__PURE__ */ new Map(),
49
+ cardTokenCounter: 0,
50
+ cardTokenizationEnabled: true,
46
51
  uploads: [],
47
52
  uploadCounter: 0,
48
53
  storageFiles: /* @__PURE__ */ new Map(),