@stardeck-customer-apps/testing 0.21.0 → 0.23.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 +55 -4
- package/dist/index.d.mts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +372 -2
- package/dist/index.mjs +372 -2
- package/dist/next/headers-shim.js +13 -5
- package/dist/next/headers-shim.mjs +13 -5
- package/dist/setup.js +310 -2
- package/dist/setup.mjs +310 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -310,6 +310,7 @@ var TEST_ENV_DEFAULTS = {
|
|
|
310
310
|
DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c",
|
|
311
311
|
STORAGE_URL: STORAGE_TEST_URL
|
|
312
312
|
};
|
|
313
|
+
var LIFF_ID_HEADER = "x-stardeck-liff-id";
|
|
313
314
|
var DEFAULT_TEST_USER = {
|
|
314
315
|
id: "test-user-1",
|
|
315
316
|
email: "test-user@example.com",
|
|
@@ -324,6 +325,7 @@ var state = globalSingleton("state", () => ({
|
|
|
324
325
|
db: null,
|
|
325
326
|
lastHarnessDataStoreManifest: null,
|
|
326
327
|
currentUser: null,
|
|
328
|
+
liffId: null,
|
|
327
329
|
sessions: /* @__PURE__ */ new Map(),
|
|
328
330
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
329
331
|
emails: [],
|
|
@@ -348,6 +350,11 @@ var state = globalSingleton("state", () => ({
|
|
|
348
350
|
boltIntentCounter: 0,
|
|
349
351
|
boltCharges: [],
|
|
350
352
|
boltChargeCounter: 0,
|
|
353
|
+
directCharges: /* @__PURE__ */ new Map(),
|
|
354
|
+
directChargeCounter: 0,
|
|
355
|
+
cardTokens: /* @__PURE__ */ new Map(),
|
|
356
|
+
cardTokenCounter: 0,
|
|
357
|
+
cardTokenizationEnabled: true,
|
|
351
358
|
uploads: [],
|
|
352
359
|
uploadCounter: 0,
|
|
353
360
|
storageFiles: /* @__PURE__ */ new Map(),
|
|
@@ -1715,6 +1722,311 @@ function createDirectory() {
|
|
|
1715
1722
|
};
|
|
1716
1723
|
}
|
|
1717
1724
|
|
|
1725
|
+
// src/simulator/beam-direct-charges.ts
|
|
1726
|
+
var SIM_BEAM_HOST = "beam.stardeck.test";
|
|
1727
|
+
var SIM_BEAM_MERCHANT_ID = "sim_merchant";
|
|
1728
|
+
var SIM_BEAM_PUBLISHABLE_KEY = "pk_test_sim";
|
|
1729
|
+
var DIRECT_CHARGE_METHODS = /* @__PURE__ */ new Set([
|
|
1730
|
+
"CARD",
|
|
1731
|
+
"CARD_INSTALLMENTS",
|
|
1732
|
+
"CARD_TOKEN",
|
|
1733
|
+
"CARD_TOKEN_INSTALLMENTS",
|
|
1734
|
+
"QR_PROMPT_PAY",
|
|
1735
|
+
"ALIPAY",
|
|
1736
|
+
"ALIPAY_PLUS",
|
|
1737
|
+
"WECHAT_PAY",
|
|
1738
|
+
"TRUE_MONEY",
|
|
1739
|
+
"LINE_PAY",
|
|
1740
|
+
"SHOPEE_PAY",
|
|
1741
|
+
"KPLUS",
|
|
1742
|
+
"MAKE",
|
|
1743
|
+
"SCB_EASY",
|
|
1744
|
+
"KRUNGSRI_APP",
|
|
1745
|
+
"BANGKOK_BANK_APP",
|
|
1746
|
+
"SPAY_LATER"
|
|
1747
|
+
]);
|
|
1748
|
+
var TEST_CARDS = {
|
|
1749
|
+
"4111111111111111": { outcome: "SUCCEEDED" },
|
|
1750
|
+
"5372074248113841": { outcome: "SUCCEEDED" },
|
|
1751
|
+
"378282246310005": { outcome: "SUCCEEDED" },
|
|
1752
|
+
"4953261730509988": { outcome: "REDIRECT" },
|
|
1753
|
+
"4389783280144506": { outcome: "REDIRECT" },
|
|
1754
|
+
"4943129900084541": { outcome: "FAILED", failureCode: "CH_INSUFFICIENT_FUNDS" },
|
|
1755
|
+
"4111111111000025": { outcome: "FAILED", failureCode: "CH_CARD_DECLINED" },
|
|
1756
|
+
"4111111111000033": { outcome: "FAILED", failureCode: "CH_CARD_RESTRICTED" }
|
|
1757
|
+
};
|
|
1758
|
+
var SIM_QR_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
|
|
1759
|
+
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;
|
|
1760
|
+
function invalid(issues) {
|
|
1761
|
+
return json({ error: "Invalid request body", details: issues }, 400);
|
|
1762
|
+
}
|
|
1763
|
+
var DEVICE_TYPES = /* @__PURE__ */ new Set(["ANDROID", "IOS", "WEB"]);
|
|
1764
|
+
function issue(path, message, code = "custom") {
|
|
1765
|
+
return { path, message, code };
|
|
1766
|
+
}
|
|
1767
|
+
function validateChargeFields(body) {
|
|
1768
|
+
const issues = [];
|
|
1769
|
+
const isInt = (v) => typeof v === "number" && Number.isInteger(v);
|
|
1770
|
+
if (!isInt(body.amount) || body.amount < 1) {
|
|
1771
|
+
issues.push(issue(["amount"], "Number must be greater than 0", "too_small"));
|
|
1772
|
+
}
|
|
1773
|
+
if (typeof body.deploymentId !== "string" || !UUID_RE.test(body.deploymentId)) {
|
|
1774
|
+
issues.push(issue(["deploymentId"], "Invalid deployment ID", "invalid_string"));
|
|
1775
|
+
}
|
|
1776
|
+
if (body.referenceId !== void 0 && (typeof body.referenceId !== "string" || body.referenceId.length > 100)) {
|
|
1777
|
+
issues.push(issue(["referenceId"], "Too long", "too_big"));
|
|
1778
|
+
}
|
|
1779
|
+
const expiry = body.expiryDurationInSec;
|
|
1780
|
+
if (expiry !== void 0 && (!isInt(expiry) || expiry < 60 || expiry > 3600)) {
|
|
1781
|
+
issues.push(issue(["expiryDurationInSec"], "Must be an integer from 60 to 3600", "too_big"));
|
|
1782
|
+
}
|
|
1783
|
+
if (body.returnUrl !== void 0 && (typeof body.returnUrl !== "string" || !URL.canParse(body.returnUrl))) {
|
|
1784
|
+
issues.push(issue(["returnUrl"], "Invalid URL", "invalid_format"));
|
|
1785
|
+
}
|
|
1786
|
+
if (body.deviceType !== void 0 && !DEVICE_TYPES.has(String(body.deviceType))) {
|
|
1787
|
+
issues.push(issue(["deviceType"], "Invalid enum value", "invalid_value"));
|
|
1788
|
+
}
|
|
1789
|
+
const key = body.idempotencyKey;
|
|
1790
|
+
if (key !== void 0 && (typeof key !== "string" || key.length < 1 || key.length > 255)) {
|
|
1791
|
+
issues.push(issue(["idempotencyKey"], "Must be 1 to 255 characters", "too_small"));
|
|
1792
|
+
}
|
|
1793
|
+
return issues;
|
|
1794
|
+
}
|
|
1795
|
+
function validateChargeMethod(body) {
|
|
1796
|
+
const method = body.paymentMethod;
|
|
1797
|
+
const type = method?.paymentMethodType;
|
|
1798
|
+
if (typeof type !== "string" || !DIRECT_CHARGE_METHODS.has(type)) {
|
|
1799
|
+
return [issue(["paymentMethod", "paymentMethodType"], "Invalid enum value", "invalid_value")];
|
|
1800
|
+
}
|
|
1801
|
+
const issues = [];
|
|
1802
|
+
const isToken = type === "CARD_TOKEN" || type === "CARD_TOKEN_INSTALLMENTS";
|
|
1803
|
+
const token = method?.cardToken;
|
|
1804
|
+
if (isToken && (typeof token?.cardTokenId !== "string" || token.cardTokenId === "")) {
|
|
1805
|
+
issues.push(
|
|
1806
|
+
issue(["paymentMethod", "cardToken"], `${type} requires paymentMethod.cardToken.cardTokenId`)
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
if (token?.securityCode !== void 0 && !/^\d{3,4}$/.test(String(token.securityCode))) {
|
|
1810
|
+
issues.push(
|
|
1811
|
+
issue(
|
|
1812
|
+
["paymentMethod", "cardToken", "securityCode"],
|
|
1813
|
+
"Invalid security code",
|
|
1814
|
+
"invalid_format"
|
|
1815
|
+
)
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
if (type === "CARD_TOKEN_INSTALLMENTS" && ![3, 4, 6, 10].includes(method?.installmentPeriod)) {
|
|
1819
|
+
issues.push(
|
|
1820
|
+
issue(
|
|
1821
|
+
["paymentMethod", "installmentPeriod"],
|
|
1822
|
+
"CARD_TOKEN_INSTALLMENTS requires paymentMethod.installmentPeriod (3, 4, 6 or 10)"
|
|
1823
|
+
)
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
if (type !== "QR_PROMPT_PAY" && !body.returnUrl) {
|
|
1827
|
+
issues.push(
|
|
1828
|
+
issue(
|
|
1829
|
+
["returnUrl"],
|
|
1830
|
+
`${type} sends the shopper away to pay; returnUrl is required to bring them back`
|
|
1831
|
+
)
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
if (type === "CARD" || type === "CARD_INSTALLMENTS") {
|
|
1835
|
+
issues.push(
|
|
1836
|
+
issue(
|
|
1837
|
+
["paymentMethod", "paymentMethodType"],
|
|
1838
|
+
`${type} takes raw card data; tokenize the card in the browser and charge CARD_TOKEN instead`
|
|
1839
|
+
)
|
|
1840
|
+
);
|
|
1841
|
+
}
|
|
1842
|
+
return issues;
|
|
1843
|
+
}
|
|
1844
|
+
function validateDirectCharge(body) {
|
|
1845
|
+
return [...validateChargeFields(body), ...validateChargeMethod(body)];
|
|
1846
|
+
}
|
|
1847
|
+
function chargeResponse(charge) {
|
|
1848
|
+
const response = {
|
|
1849
|
+
chargeId: charge.id,
|
|
1850
|
+
actionRequired: charge.actionRequired,
|
|
1851
|
+
paymentMethodType: charge.paymentMethodType
|
|
1852
|
+
};
|
|
1853
|
+
if (charge.actionRequired === "ENCODED_IMAGE") {
|
|
1854
|
+
response.encodedImage = {
|
|
1855
|
+
imageBase64Encoded: SIM_QR_PNG,
|
|
1856
|
+
rawData: `00020101021230SIM${charge.id}`,
|
|
1857
|
+
expiry: charge.expiresAt
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
if (charge.actionRequired === "REDIRECT") {
|
|
1861
|
+
response.redirect = { redirectUrl: charge.redirectUrl };
|
|
1862
|
+
response.redirectUrl = charge.redirectUrl;
|
|
1863
|
+
}
|
|
1864
|
+
return response;
|
|
1865
|
+
}
|
|
1866
|
+
function nextChargeId() {
|
|
1867
|
+
state.directChargeCounter += 1;
|
|
1868
|
+
return `ch_sim_${state.directChargeCounter}`;
|
|
1869
|
+
}
|
|
1870
|
+
function createCharge(body) {
|
|
1871
|
+
const method = body.paymentMethod;
|
|
1872
|
+
const type = method.paymentMethodType;
|
|
1873
|
+
const id = nextChargeId();
|
|
1874
|
+
const now3 = /* @__PURE__ */ new Date();
|
|
1875
|
+
const expirySec = typeof body.expiryDurationInSec === "number" ? body.expiryDurationInSec : 30 * 60;
|
|
1876
|
+
const returnUrl = typeof body.returnUrl === "string" ? body.returnUrl : null;
|
|
1877
|
+
let actionRequired = "REDIRECT";
|
|
1878
|
+
let status = "PENDING";
|
|
1879
|
+
let failureCode = null;
|
|
1880
|
+
let cardLast4 = null;
|
|
1881
|
+
if (type === "QR_PROMPT_PAY") {
|
|
1882
|
+
actionRequired = "ENCODED_IMAGE";
|
|
1883
|
+
} else if (type === "CARD_TOKEN" || type === "CARD_TOKEN_INSTALLMENTS") {
|
|
1884
|
+
const token = state.cardTokens.get(method.cardToken.cardTokenId);
|
|
1885
|
+
if (!token) {
|
|
1886
|
+
return json({ error: "Card token not found", code: "CARD_TOKEN_NOT_FOUND" }, 404);
|
|
1887
|
+
}
|
|
1888
|
+
cardLast4 = token.last4;
|
|
1889
|
+
const card = TEST_CARDS[token.pan] ?? {
|
|
1890
|
+
outcome: "FAILED",
|
|
1891
|
+
failureCode: "CH_PROCESSING_FAILED"
|
|
1892
|
+
};
|
|
1893
|
+
if (card.outcome === "REDIRECT") {
|
|
1894
|
+
actionRequired = "REDIRECT";
|
|
1895
|
+
} else {
|
|
1896
|
+
actionRequired = "NONE";
|
|
1897
|
+
status = card.outcome;
|
|
1898
|
+
failureCode = card.failureCode ?? null;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
const charge = {
|
|
1902
|
+
id,
|
|
1903
|
+
status,
|
|
1904
|
+
amount: body.amount,
|
|
1905
|
+
currency: String(body.currency ?? "THB"),
|
|
1906
|
+
paymentMethodType: type,
|
|
1907
|
+
referenceId: typeof body.referenceId === "string" ? body.referenceId : null,
|
|
1908
|
+
deviceType: typeof body.deviceType === "string" ? body.deviceType : null,
|
|
1909
|
+
returnUrl,
|
|
1910
|
+
actionRequired,
|
|
1911
|
+
redirectUrl: actionRequired === "REDIRECT" ? `https://${SIM_BEAM_HOST}/pay/${id}${returnUrl ? `?returnUrl=${encodeURIComponent(returnUrl)}` : ""}` : null,
|
|
1912
|
+
expiresAt: new Date(now3.getTime() + expirySec * 1e3).toISOString(),
|
|
1913
|
+
failureCode,
|
|
1914
|
+
cardLast4,
|
|
1915
|
+
idempotencyKey: typeof body.idempotencyKey === "string" ? body.idempotencyKey : null,
|
|
1916
|
+
createdAt: now3.toISOString()
|
|
1917
|
+
};
|
|
1918
|
+
state.directCharges.set(id, charge);
|
|
1919
|
+
return charge;
|
|
1920
|
+
}
|
|
1921
|
+
async function handleDirectChargeRequest(request, pathname) {
|
|
1922
|
+
if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/charges$/.test(pathname)) {
|
|
1923
|
+
const body = await readJsonBody(request);
|
|
1924
|
+
const issues = validateDirectCharge(body);
|
|
1925
|
+
if (issues.length > 0) return invalid(issues);
|
|
1926
|
+
const key = typeof body.idempotencyKey === "string" ? body.idempotencyKey : null;
|
|
1927
|
+
if (key) {
|
|
1928
|
+
const existing = [...state.directCharges.values()].find((c) => c.idempotencyKey === key);
|
|
1929
|
+
if (existing) return json(chargeResponse(existing));
|
|
1930
|
+
}
|
|
1931
|
+
const created = createCharge(body);
|
|
1932
|
+
if (created instanceof Response) return created;
|
|
1933
|
+
return json(chargeResponse(created));
|
|
1934
|
+
}
|
|
1935
|
+
const getMatch = pathname.match(/^\/api\/store\/beam\/[^/]+\/charges\/([^/]+)$/);
|
|
1936
|
+
if (request.method === "GET" && getMatch) {
|
|
1937
|
+
const charge = state.directCharges.get(decodeURIComponent(getMatch[1]));
|
|
1938
|
+
if (!charge) return json({ error: "Charge not found", code: "NOT_FOUND" }, 404);
|
|
1939
|
+
return json({ charge: toChargeDetail(charge) });
|
|
1940
|
+
}
|
|
1941
|
+
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/card-tokenization$/.test(pathname)) {
|
|
1942
|
+
if (!state.cardTokenizationEnabled) {
|
|
1943
|
+
return json(
|
|
1944
|
+
{
|
|
1945
|
+
error: "Add the Beam publishable key to this merchant account to take cards in your own checkout",
|
|
1946
|
+
code: "CARD_TOKENIZATION_NOT_CONFIGURED"
|
|
1947
|
+
},
|
|
1948
|
+
409
|
|
1949
|
+
);
|
|
1950
|
+
}
|
|
1951
|
+
return json({
|
|
1952
|
+
merchantId: SIM_BEAM_MERCHANT_ID,
|
|
1953
|
+
publishableKey: SIM_BEAM_PUBLISHABLE_KEY,
|
|
1954
|
+
tokenizeUrl: `https://${SIM_BEAM_HOST}/client/v1/card-tokens`,
|
|
1955
|
+
isSandbox: true
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
return null;
|
|
1959
|
+
}
|
|
1960
|
+
function toChargeDetail(charge) {
|
|
1961
|
+
return {
|
|
1962
|
+
id: charge.id,
|
|
1963
|
+
chargeId: charge.id,
|
|
1964
|
+
status: charge.status,
|
|
1965
|
+
amount: charge.amount,
|
|
1966
|
+
currency: charge.currency,
|
|
1967
|
+
paymentMethodType: charge.paymentMethodType,
|
|
1968
|
+
...charge.referenceId !== null ? { referenceId: charge.referenceId } : {},
|
|
1969
|
+
...charge.failureCode ? { failureCode: charge.failureCode } : {},
|
|
1970
|
+
source: "API",
|
|
1971
|
+
createdAt: charge.createdAt
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
async function handleCardTokenRequest(request, url) {
|
|
1975
|
+
if (request.method !== "POST" || url.pathname !== "/client/v1/card-tokens") {
|
|
1976
|
+
return json({ error: `No Beam simulator for ${request.method} ${url.pathname}` }, 404);
|
|
1977
|
+
}
|
|
1978
|
+
const expected = `Basic ${btoa(`${SIM_BEAM_MERCHANT_ID}:${SIM_BEAM_PUBLISHABLE_KEY}`)}`;
|
|
1979
|
+
if (request.headers.get("Authorization") !== expected) {
|
|
1980
|
+
return json(
|
|
1981
|
+
{
|
|
1982
|
+
message: "invalid credentials",
|
|
1983
|
+
error: { errorCode: "INVALID_CREDENTIALS_ERROR", errorMessage: "invalid credentials" }
|
|
1984
|
+
},
|
|
1985
|
+
401
|
|
1986
|
+
);
|
|
1987
|
+
}
|
|
1988
|
+
const body = await readJsonBody(request);
|
|
1989
|
+
const pan = typeof body.pan === "string" ? body.pan : "";
|
|
1990
|
+
const month = body.expiryMonth;
|
|
1991
|
+
const year = body.expiryYear;
|
|
1992
|
+
if (!/^\d{12,19}$/.test(pan) || typeof month !== "number" || month < 1 || month > 12 || typeof year !== "number" || year < 0 || year > 99) {
|
|
1993
|
+
return json(
|
|
1994
|
+
{
|
|
1995
|
+
message: "inputs are failing validation",
|
|
1996
|
+
error: {
|
|
1997
|
+
errorCode: "API_VALIDATION_ERROR",
|
|
1998
|
+
errorMessage: "inputs are failing validation"
|
|
1999
|
+
}
|
|
2000
|
+
},
|
|
2001
|
+
400
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
2004
|
+
state.cardTokenCounter += 1;
|
|
2005
|
+
const token = {
|
|
2006
|
+
id: `ctok_sim_${state.cardTokenCounter}`,
|
|
2007
|
+
pan,
|
|
2008
|
+
last4: pan.slice(-4),
|
|
2009
|
+
expiryMonth: month,
|
|
2010
|
+
expiryYear: year
|
|
2011
|
+
};
|
|
2012
|
+
state.cardTokens.set(token.id, token);
|
|
2013
|
+
return json({ id: token.id });
|
|
2014
|
+
}
|
|
2015
|
+
function chargeWebhookPayload(charge) {
|
|
2016
|
+
return {
|
|
2017
|
+
chargeId: charge.id,
|
|
2018
|
+
merchantId: SIM_BEAM_MERCHANT_ID,
|
|
2019
|
+
...charge.referenceId !== null ? { referenceId: charge.referenceId } : {},
|
|
2020
|
+
status: charge.status,
|
|
2021
|
+
currency: charge.currency,
|
|
2022
|
+
amount: charge.amount,
|
|
2023
|
+
source: "API",
|
|
2024
|
+
sourceId: charge.id,
|
|
2025
|
+
...charge.failureCode ? { failureCode: charge.failureCode } : {},
|
|
2026
|
+
paymentMethod: { paymentMethodType: charge.paymentMethodType }
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
|
|
1718
2030
|
// src/simulator/payments.ts
|
|
1719
2031
|
var import_node_crypto6 = __toESM(require("crypto"));
|
|
1720
2032
|
|
|
@@ -1756,6 +2068,9 @@ async function callRoute(handler, options = {}) {
|
|
|
1756
2068
|
if (user && !headers.has("x-stardeck-user")) {
|
|
1757
2069
|
headers.set("x-stardeck-user", JSON.stringify(user));
|
|
1758
2070
|
}
|
|
2071
|
+
if (state.liffId && !headers.has(LIFF_ID_HEADER)) {
|
|
2072
|
+
headers.set(LIFF_ID_HEADER, state.liffId);
|
|
2073
|
+
}
|
|
1759
2074
|
if (options.body !== void 0 && !headers.has("Content-Type")) {
|
|
1760
2075
|
headers.set("Content-Type", "application/json");
|
|
1761
2076
|
}
|
|
@@ -1829,7 +2144,7 @@ var BEAM_BOLT_PAYMENT_METHODS = [
|
|
|
1829
2144
|
"SPAY_LATER"
|
|
1830
2145
|
];
|
|
1831
2146
|
var BEAM_BOLT_INTENT_STATUSES = ["PENDING", "PAID", "FAILED", "CANCELED", "EXPIRED"];
|
|
1832
|
-
var
|
|
2147
|
+
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;
|
|
1833
2148
|
function payErr(error, status = 400, code, details) {
|
|
1834
2149
|
const body = { error };
|
|
1835
2150
|
if (code) body.code = code;
|
|
@@ -1843,6 +2158,12 @@ function paymentOperationForRequest(request, pathname) {
|
|
|
1843
2158
|
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/payment-links\/[^/]+$/.test(pathname)) {
|
|
1844
2159
|
return "beam.getPaymentLinkStatus";
|
|
1845
2160
|
}
|
|
2161
|
+
if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/charges$/.test(pathname)) {
|
|
2162
|
+
return "beam.createDirectCharge";
|
|
2163
|
+
}
|
|
2164
|
+
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/charges\/[^/]+$/.test(pathname)) {
|
|
2165
|
+
return "beam.getCharge";
|
|
2166
|
+
}
|
|
1846
2167
|
if (request.method === "POST" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout$/.test(pathname)) {
|
|
1847
2168
|
return "stripe.createCheckout";
|
|
1848
2169
|
}
|
|
@@ -2018,6 +2339,8 @@ async function handlePaymentsRequest(request, url) {
|
|
|
2018
2339
|
const pathname = url.pathname;
|
|
2019
2340
|
const injectedFailure = consumePaymentFailure(request, pathname);
|
|
2020
2341
|
if (injectedFailure) return injectedFailure;
|
|
2342
|
+
const directChargeResponse = await handleDirectChargeRequest(request, pathname);
|
|
2343
|
+
if (directChargeResponse) return directChargeResponse;
|
|
2021
2344
|
if (/\/billing-portal$/.test(pathname)) {
|
|
2022
2345
|
return payErr("Not found", 404, "NOT_FOUND");
|
|
2023
2346
|
}
|
|
@@ -2114,7 +2437,7 @@ async function handlePaymentsRequest(request, url) {
|
|
|
2114
2437
|
code: "too_small"
|
|
2115
2438
|
});
|
|
2116
2439
|
}
|
|
2117
|
-
if (typeof body.deploymentId !== "string" || !
|
|
2440
|
+
if (typeof body.deploymentId !== "string" || !UUID_RE2.test(body.deploymentId)) {
|
|
2118
2441
|
issues.push({
|
|
2119
2442
|
path: ["deploymentId"],
|
|
2120
2443
|
message: "Invalid uuid",
|
|
@@ -2301,6 +2624,18 @@ async function handlePaymentsRequest(request, url) {
|
|
|
2301
2624
|
}
|
|
2302
2625
|
return payErr(`No payments simulator for ${request.method} ${pathname}`, 404);
|
|
2303
2626
|
}
|
|
2627
|
+
function requirePendingDirectCharge(chargeId) {
|
|
2628
|
+
const charge = state.directCharges.get(chargeId);
|
|
2629
|
+
if (!charge) {
|
|
2630
|
+
throw new Error(`[stardeck-testing] Unknown direct charge id: ${chargeId}`);
|
|
2631
|
+
}
|
|
2632
|
+
if (charge.status !== "PENDING") {
|
|
2633
|
+
throw new Error(
|
|
2634
|
+
`[stardeck-testing] Direct charge ${chargeId} is ${charge.status}, not PENDING`
|
|
2635
|
+
);
|
|
2636
|
+
}
|
|
2637
|
+
return charge;
|
|
2638
|
+
}
|
|
2304
2639
|
function createPayments() {
|
|
2305
2640
|
return {
|
|
2306
2641
|
get checkouts() {
|
|
@@ -2419,6 +2754,26 @@ function createPayments() {
|
|
|
2419
2754
|
if (!intent) return void 0;
|
|
2420
2755
|
return toBoltIntentRecord(intent);
|
|
2421
2756
|
},
|
|
2757
|
+
get directCharges() {
|
|
2758
|
+
return [...state.directCharges.values()];
|
|
2759
|
+
},
|
|
2760
|
+
getDirectCharge(chargeId) {
|
|
2761
|
+
return state.directCharges.get(chargeId);
|
|
2762
|
+
},
|
|
2763
|
+
completeDirectCharge(chargeId) {
|
|
2764
|
+
const charge = requirePendingDirectCharge(chargeId);
|
|
2765
|
+
charge.status = "SUCCEEDED";
|
|
2766
|
+
return chargeWebhookPayload(charge);
|
|
2767
|
+
},
|
|
2768
|
+
failDirectCharge(chargeId, failureCode = "CH_PROCESSING_FAILED") {
|
|
2769
|
+
const charge = requirePendingDirectCharge(chargeId);
|
|
2770
|
+
charge.status = "FAILED";
|
|
2771
|
+
charge.failureCode = failureCode;
|
|
2772
|
+
return chargeWebhookPayload(charge);
|
|
2773
|
+
},
|
|
2774
|
+
setCardTokenization(enabled) {
|
|
2775
|
+
state.cardTokenizationEnabled = enabled;
|
|
2776
|
+
},
|
|
2422
2777
|
clear() {
|
|
2423
2778
|
state.checkouts = [];
|
|
2424
2779
|
state.checkoutCounter = 0;
|
|
@@ -2433,6 +2788,11 @@ function createPayments() {
|
|
|
2433
2788
|
state.boltIntentCounter = 0;
|
|
2434
2789
|
state.boltCharges = [];
|
|
2435
2790
|
state.boltChargeCounter = 0;
|
|
2791
|
+
state.directCharges.clear();
|
|
2792
|
+
state.directChargeCounter = 0;
|
|
2793
|
+
state.cardTokens.clear();
|
|
2794
|
+
state.cardTokenCounter = 0;
|
|
2795
|
+
state.cardTokenizationEnabled = true;
|
|
2436
2796
|
},
|
|
2437
2797
|
get count() {
|
|
2438
2798
|
return state.checkouts.length;
|
|
@@ -3312,6 +3672,9 @@ async function handleSimulatedRequest(request, url) {
|
|
|
3312
3672
|
if (isStorageHost) {
|
|
3313
3673
|
return handleStorageRequest(request, url);
|
|
3314
3674
|
}
|
|
3675
|
+
if (url.hostname === SIM_BEAM_HOST) {
|
|
3676
|
+
return handleCardTokenRequest(request, url);
|
|
3677
|
+
}
|
|
3315
3678
|
if (isEmail && request.method === "POST") {
|
|
3316
3679
|
return handleEmailSend(request);
|
|
3317
3680
|
}
|
|
@@ -3498,6 +3861,8 @@ async function createTestApp(options = {}) {
|
|
|
3498
3861
|
}
|
|
3499
3862
|
applyTestEnv(options);
|
|
3500
3863
|
state.allowNetwork = options.allowNetwork ?? false;
|
|
3864
|
+
const initialLiffId = options.liffId ?? null;
|
|
3865
|
+
state.liffId = initialLiffId;
|
|
3501
3866
|
installFetchRouter();
|
|
3502
3867
|
const schemaSql = loadSchemaSql(options) ?? "";
|
|
3503
3868
|
const db = await bootWithSchema(schemaSql);
|
|
@@ -3550,6 +3915,9 @@ async function createTestApp(options = {}) {
|
|
|
3550
3915
|
signOut() {
|
|
3551
3916
|
state.currentUser = null;
|
|
3552
3917
|
},
|
|
3918
|
+
setLiffId(liffId) {
|
|
3919
|
+
state.liffId = liffId;
|
|
3920
|
+
},
|
|
3553
3921
|
issueSession(user) {
|
|
3554
3922
|
const fullUser = buildUser(user);
|
|
3555
3923
|
const tokens = {
|
|
@@ -3572,6 +3940,7 @@ async function createTestApp(options = {}) {
|
|
|
3572
3940
|
}
|
|
3573
3941
|
}
|
|
3574
3942
|
state.currentUser = null;
|
|
3943
|
+
state.liffId = initialLiffId;
|
|
3575
3944
|
state.sessions.clear();
|
|
3576
3945
|
state.refreshSessions.clear();
|
|
3577
3946
|
state.emails = [];
|
|
@@ -3586,6 +3955,7 @@ async function createTestApp(options = {}) {
|
|
|
3586
3955
|
async close() {
|
|
3587
3956
|
state.db = null;
|
|
3588
3957
|
state.currentUser = null;
|
|
3958
|
+
state.liffId = null;
|
|
3589
3959
|
state.sessions.clear();
|
|
3590
3960
|
state.refreshSessions.clear();
|
|
3591
3961
|
state.emails = [];
|