@stardeck-customer-apps/testing 0.8.0 → 0.10.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/LICENSE +21 -0
- package/SKILL.md +123 -5
- package/dist/config.d.mts +6 -2
- package/dist/config.d.ts +6 -2
- package/dist/config.js +75 -1
- package/dist/config.mjs +73 -1
- package/dist/data-store-manifest-Zwgcv0DJ.d.mts +25 -0
- package/dist/data-store-manifest-Zwgcv0DJ.d.ts +25 -0
- package/dist/index.d.mts +82 -2
- package/dist/index.d.ts +82 -2
- package/dist/index.js +453 -6
- package/dist/index.mjs +450 -6
- package/dist/next/headers-shim.js +8 -0
- package/dist/next/headers-shim.mjs +8 -0
- package/dist/setup.js +294 -4
- package/dist/setup.mjs +294 -4
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -47,6 +47,7 @@ function globalSingleton(key, create) {
|
|
|
47
47
|
// src/state.ts
|
|
48
48
|
var state = globalSingleton("state", () => ({
|
|
49
49
|
db: null,
|
|
50
|
+
lastHarnessDataStoreManifest: null,
|
|
50
51
|
currentUser: null,
|
|
51
52
|
sessions: /* @__PURE__ */ new Map(),
|
|
52
53
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
@@ -59,6 +60,13 @@ var state = globalSingleton("state", () => ({
|
|
|
59
60
|
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
60
61
|
paymentLinks: /* @__PURE__ */ new Map(),
|
|
61
62
|
products: [],
|
|
63
|
+
boltConnections: /* @__PURE__ */ new Map(),
|
|
64
|
+
boltUsedPairingCodes: /* @__PURE__ */ new Set(),
|
|
65
|
+
boltConnectionCounter: 0,
|
|
66
|
+
boltIntents: /* @__PURE__ */ new Map(),
|
|
67
|
+
boltIntentCounter: 0,
|
|
68
|
+
boltCharges: [],
|
|
69
|
+
boltChargeCounter: 0,
|
|
62
70
|
uploads: [],
|
|
63
71
|
uploadCounter: 0,
|
|
64
72
|
storageFiles: /* @__PURE__ */ new Map(),
|
|
@@ -88,6 +96,7 @@ function requireDb() {
|
|
|
88
96
|
var TEST_DOMAIN_SUFFIX = ".stardeck.test";
|
|
89
97
|
var CONTROL_PLANE_TEST_URL = "https://control-plane.stardeck.test";
|
|
90
98
|
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
99
|
+
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
91
100
|
var STORAGE_TEST_URL = "https://storage.stardeck.test";
|
|
92
101
|
var STORAGE_TEST_HOST = "storage.stardeck.test";
|
|
93
102
|
var TEST_ENV_DEFAULTS = {
|
|
@@ -96,7 +105,6 @@ var TEST_ENV_DEFAULTS = {
|
|
|
96
105
|
ORGANIZATION_ID: "00000000-0000-4000-8000-00000000000a",
|
|
97
106
|
PROJECT_ID: "00000000-0000-4000-8000-00000000000b",
|
|
98
107
|
DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c",
|
|
99
|
-
DATA_STORE_URL: `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`,
|
|
100
108
|
STORAGE_URL: STORAGE_TEST_URL
|
|
101
109
|
};
|
|
102
110
|
var DEFAULT_TEST_USER = {
|
|
@@ -1196,8 +1204,31 @@ function signEventDelivery(secret, context, rawBody) {
|
|
|
1196
1204
|
}
|
|
1197
1205
|
|
|
1198
1206
|
// src/simulator/payments.ts
|
|
1199
|
-
|
|
1200
|
-
|
|
1207
|
+
var BEAM_BOLT_PAYMENT_METHODS = [
|
|
1208
|
+
"CARD",
|
|
1209
|
+
"CARD_INSTALLMENTS",
|
|
1210
|
+
"QR_PROMPT_PAY",
|
|
1211
|
+
"ALIPAY",
|
|
1212
|
+
"ALIPAY_PLUS",
|
|
1213
|
+
"LINE_PAY",
|
|
1214
|
+
"SHOPEE_PAY",
|
|
1215
|
+
"TRUE_MONEY",
|
|
1216
|
+
"WECHAT_PAY",
|
|
1217
|
+
"SPAY_LATER"
|
|
1218
|
+
];
|
|
1219
|
+
var BEAM_BOLT_INTENT_STATUSES = ["PENDING", "PAID", "FAILED", "CANCELED", "EXPIRED"];
|
|
1220
|
+
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;
|
|
1221
|
+
function payErr(error, status = 400, code, details) {
|
|
1222
|
+
const body = { error };
|
|
1223
|
+
if (code) body.code = code;
|
|
1224
|
+
if (details !== void 0) body.details = details;
|
|
1225
|
+
return json(body, status);
|
|
1226
|
+
}
|
|
1227
|
+
function isBoltPaymentMethod(value) {
|
|
1228
|
+
return typeof value === "string" && BEAM_BOLT_PAYMENT_METHODS.includes(value);
|
|
1229
|
+
}
|
|
1230
|
+
function isBoltIntentStatus(value) {
|
|
1231
|
+
return BEAM_BOLT_INTENT_STATUSES.includes(value);
|
|
1201
1232
|
}
|
|
1202
1233
|
function nextCheckoutId() {
|
|
1203
1234
|
state.checkoutCounter += 1;
|
|
@@ -1207,6 +1238,18 @@ function nextPaymentLinkId() {
|
|
|
1207
1238
|
state.checkoutCounter += 1;
|
|
1208
1239
|
return `plink_test_${state.checkoutCounter}`;
|
|
1209
1240
|
}
|
|
1241
|
+
function nextBoltConnectionId() {
|
|
1242
|
+
state.boltConnectionCounter += 1;
|
|
1243
|
+
return `boltc_${state.boltConnectionCounter}`;
|
|
1244
|
+
}
|
|
1245
|
+
function nextBoltIntentId() {
|
|
1246
|
+
state.boltIntentCounter += 1;
|
|
1247
|
+
return `bolti_${state.boltIntentCounter}`;
|
|
1248
|
+
}
|
|
1249
|
+
function nextBoltChargeId() {
|
|
1250
|
+
state.boltChargeCounter += 1;
|
|
1251
|
+
return `chrg_${state.boltChargeCounter}`;
|
|
1252
|
+
}
|
|
1210
1253
|
function seedStripeSession(id, body) {
|
|
1211
1254
|
const lineItems = body.lineItems ?? [];
|
|
1212
1255
|
let amountTotal = null;
|
|
@@ -1250,6 +1293,58 @@ function seedBeamLink(id, body, merchantId) {
|
|
|
1250
1293
|
collectDeliveryAddress: body.collectDeliveryAddress === true
|
|
1251
1294
|
});
|
|
1252
1295
|
}
|
|
1296
|
+
function deriveBoltIntentStatus(storedStatus, expiresAt, now3 = /* @__PURE__ */ new Date()) {
|
|
1297
|
+
if (storedStatus !== "PENDING") return storedStatus;
|
|
1298
|
+
return expiresAt.getTime() <= now3.getTime() ? "EXPIRED" : "PENDING";
|
|
1299
|
+
}
|
|
1300
|
+
function toBoltIntentRecord(intent, now3 = /* @__PURE__ */ new Date()) {
|
|
1301
|
+
return {
|
|
1302
|
+
id: intent.id,
|
|
1303
|
+
beamIntentId: intent.beamIntentId,
|
|
1304
|
+
boltConnectionId: intent.boltConnectionId,
|
|
1305
|
+
amount: intent.amount,
|
|
1306
|
+
currency: intent.currency,
|
|
1307
|
+
paymentMethodType: intent.paymentMethodType,
|
|
1308
|
+
referenceId: intent.referenceId,
|
|
1309
|
+
internalNote: intent.internalNote,
|
|
1310
|
+
status: deriveBoltIntentStatus(intent.status, intent.expiresAt, now3),
|
|
1311
|
+
isVirtual: intent.isVirtual,
|
|
1312
|
+
environment: intent.environment,
|
|
1313
|
+
expiresAt: intent.expiresAt.toISOString(),
|
|
1314
|
+
settledAt: intent.settledAt ? intent.settledAt.toISOString() : null,
|
|
1315
|
+
chargeId: intent.chargeId,
|
|
1316
|
+
failureReason: intent.failureReason,
|
|
1317
|
+
createdAt: intent.createdAt.toISOString()
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
function findBoltConnection(connectionId) {
|
|
1321
|
+
return state.boltConnections.get(connectionId) ?? [...state.boltConnections.values()].find((c) => c.beamConnectionId === connectionId);
|
|
1322
|
+
}
|
|
1323
|
+
function findBoltIntent(intentId) {
|
|
1324
|
+
return state.boltIntents.get(intentId) ?? [...state.boltIntents.values()].find((i) => i.beamIntentId === intentId);
|
|
1325
|
+
}
|
|
1326
|
+
function requirePendingBoltIntent(intentId) {
|
|
1327
|
+
const intent = findBoltIntent(intentId);
|
|
1328
|
+
if (!intent) {
|
|
1329
|
+
throw new Error(`[stardeck-testing] Unknown bolt intent id: ${intentId}`);
|
|
1330
|
+
}
|
|
1331
|
+
const derived = deriveBoltIntentStatus(intent.status, intent.expiresAt);
|
|
1332
|
+
if (derived !== "PENDING") {
|
|
1333
|
+
throw new Error(`[stardeck-testing] Bolt intent ${intentId} is ${derived}, expected PENDING`);
|
|
1334
|
+
}
|
|
1335
|
+
return intent;
|
|
1336
|
+
}
|
|
1337
|
+
function storedStatusesForFilter(statuses) {
|
|
1338
|
+
const stored = /* @__PURE__ */ new Set();
|
|
1339
|
+
for (const status of statuses) {
|
|
1340
|
+
if (status === "EXPIRED" || status === "PENDING") {
|
|
1341
|
+
stored.add("PENDING");
|
|
1342
|
+
} else {
|
|
1343
|
+
stored.add(status);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
return [...stored];
|
|
1347
|
+
}
|
|
1253
1348
|
async function deliverEvent(envelope, handler, options) {
|
|
1254
1349
|
const rawBody = JSON.stringify(envelope);
|
|
1255
1350
|
const secret = options?.deploymentSecret ?? TEST_ENV_DEFAULTS.DEPLOYMENT_SECRET;
|
|
@@ -1272,9 +1367,219 @@ async function deliverEvent(envelope, handler, options) {
|
|
|
1272
1367
|
}
|
|
1273
1368
|
async function handlePaymentsRequest(request, url) {
|
|
1274
1369
|
const pathname = url.pathname;
|
|
1275
|
-
if (/\/
|
|
1370
|
+
if (/\/billing-portal$/.test(pathname)) {
|
|
1276
1371
|
return payErr("Not found", 404, "NOT_FOUND");
|
|
1277
1372
|
}
|
|
1373
|
+
const boltConnectionsMatch = pathname.match(
|
|
1374
|
+
/^\/api\/store\/beam\/([^/]+)\/bolt-connections(?:\/([^/]+))?$/
|
|
1375
|
+
);
|
|
1376
|
+
if (boltConnectionsMatch) {
|
|
1377
|
+
const connectionId = boltConnectionsMatch[2];
|
|
1378
|
+
if (!connectionId && request.method === "POST") {
|
|
1379
|
+
const body = await readJsonBody(request);
|
|
1380
|
+
const pairingCode = body.pairingCode ? String(body.pairingCode) : "";
|
|
1381
|
+
if (!pairingCode) {
|
|
1382
|
+
return payErr("pairingCode is required", 400);
|
|
1383
|
+
}
|
|
1384
|
+
if (state.boltUsedPairingCodes.has(pairingCode)) {
|
|
1385
|
+
return payErr("Pairing code has already been used", 400, "PAIRING_CODE_USED");
|
|
1386
|
+
}
|
|
1387
|
+
const id = nextBoltConnectionId();
|
|
1388
|
+
const now3 = (/* @__PURE__ */ new Date()).toISOString();
|
|
1389
|
+
const environments = Array.isArray(body.environments) ? body.environments.map(String) : ["sandbox"];
|
|
1390
|
+
const isSandbox = environments.some((e) => e === "sandbox" || e === "preview");
|
|
1391
|
+
const connection = {
|
|
1392
|
+
id,
|
|
1393
|
+
projectId: TEST_ENV_DEFAULTS.PROJECT_ID,
|
|
1394
|
+
beamConnectionId: id,
|
|
1395
|
+
displayName: body.displayName != null ? String(body.displayName) : null,
|
|
1396
|
+
pairingCode,
|
|
1397
|
+
status: "ACTIVE",
|
|
1398
|
+
isSandbox,
|
|
1399
|
+
environments,
|
|
1400
|
+
createdAt: now3,
|
|
1401
|
+
updatedAt: now3
|
|
1402
|
+
};
|
|
1403
|
+
state.boltUsedPairingCodes.add(pairingCode);
|
|
1404
|
+
state.boltConnections.set(id, connection);
|
|
1405
|
+
return json({ connection });
|
|
1406
|
+
}
|
|
1407
|
+
if (!connectionId && request.method === "GET") {
|
|
1408
|
+
return json({ connections: [...state.boltConnections.values()] });
|
|
1409
|
+
}
|
|
1410
|
+
if (connectionId && request.method === "GET") {
|
|
1411
|
+
const connection = findBoltConnection(connectionId);
|
|
1412
|
+
if (!connection) return payErr("Bolt connection not found", 404, "NOT_FOUND");
|
|
1413
|
+
return json({ connection });
|
|
1414
|
+
}
|
|
1415
|
+
if (connectionId && request.method === "DELETE") {
|
|
1416
|
+
const connection = findBoltConnection(connectionId);
|
|
1417
|
+
if (!connection) return payErr("Bolt connection not found", 404, "NOT_FOUND");
|
|
1418
|
+
state.boltConnections.delete(connection.id);
|
|
1419
|
+
return json({ success: true });
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
const boltIntentsMatch = pathname.match(
|
|
1423
|
+
/^\/api\/store\/beam\/([^/]+)\/bolt-intents(?:\/([^/]+))?$/
|
|
1424
|
+
);
|
|
1425
|
+
if (boltIntentsMatch) {
|
|
1426
|
+
const intentId = boltIntentsMatch[2];
|
|
1427
|
+
if (!intentId && request.method === "POST") {
|
|
1428
|
+
const body = await readJsonBody(request);
|
|
1429
|
+
const issues = [];
|
|
1430
|
+
if (typeof body.amount !== "number" || !Number.isInteger(body.amount) || body.amount < 1) {
|
|
1431
|
+
issues.push({
|
|
1432
|
+
path: ["amount"],
|
|
1433
|
+
message: "Number must be greater than 0",
|
|
1434
|
+
code: "too_small"
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
const boltConnectionIdRaw = body.boltConnectionId;
|
|
1438
|
+
if (typeof boltConnectionIdRaw !== "string" || boltConnectionIdRaw.length < 1) {
|
|
1439
|
+
issues.push({
|
|
1440
|
+
path: ["boltConnectionId"],
|
|
1441
|
+
message: "String must contain at least 1 character(s)",
|
|
1442
|
+
code: "too_small"
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
const paymentMethod = body.paymentMethod;
|
|
1446
|
+
if (!paymentMethod || typeof paymentMethod !== "object") {
|
|
1447
|
+
issues.push({
|
|
1448
|
+
path: ["paymentMethod"],
|
|
1449
|
+
message: "Required",
|
|
1450
|
+
code: "invalid_type"
|
|
1451
|
+
});
|
|
1452
|
+
} else if (!isBoltPaymentMethod(paymentMethod.paymentMethodType)) {
|
|
1453
|
+
issues.push({
|
|
1454
|
+
path: ["paymentMethod", "paymentMethodType"],
|
|
1455
|
+
message: "Invalid enum value",
|
|
1456
|
+
code: "invalid_enum_value"
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
if (typeof body.expiryDurationInSec !== "number" || !Number.isInteger(body.expiryDurationInSec) || body.expiryDurationInSec < 90 || body.expiryDurationInSec > 600) {
|
|
1460
|
+
issues.push({
|
|
1461
|
+
path: ["expiryDurationInSec"],
|
|
1462
|
+
message: "Number must be between 90 and 600",
|
|
1463
|
+
code: "too_small"
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
if (typeof body.deploymentId !== "string" || !UUID_RE.test(body.deploymentId)) {
|
|
1467
|
+
issues.push({
|
|
1468
|
+
path: ["deploymentId"],
|
|
1469
|
+
message: "Invalid uuid",
|
|
1470
|
+
code: "invalid_string"
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
if (issues.length > 0) {
|
|
1474
|
+
return payErr("Invalid request body", 400, void 0, issues);
|
|
1475
|
+
}
|
|
1476
|
+
const boltConnectionId = String(body.boltConnectionId);
|
|
1477
|
+
if (!findBoltConnection(boltConnectionId)) {
|
|
1478
|
+
return payErr("Bolt connection not found", 404, "NOT_FOUND");
|
|
1479
|
+
}
|
|
1480
|
+
const paymentMethodType = body.paymentMethod.paymentMethodType;
|
|
1481
|
+
const amount = body.amount;
|
|
1482
|
+
const currency = String(body.currency ?? "THB");
|
|
1483
|
+
const expiryDurationInSec = body.expiryDurationInSec;
|
|
1484
|
+
const id = nextBoltIntentId();
|
|
1485
|
+
const now3 = /* @__PURE__ */ new Date();
|
|
1486
|
+
const intent = {
|
|
1487
|
+
id,
|
|
1488
|
+
beamIntentId: id,
|
|
1489
|
+
boltConnectionId,
|
|
1490
|
+
amount,
|
|
1491
|
+
currency,
|
|
1492
|
+
paymentMethodType,
|
|
1493
|
+
referenceId: body.referenceId != null ? String(body.referenceId) : null,
|
|
1494
|
+
internalNote: body.internalNote != null ? String(body.internalNote) : null,
|
|
1495
|
+
status: "PENDING",
|
|
1496
|
+
isVirtual: false,
|
|
1497
|
+
environment: "sandbox",
|
|
1498
|
+
expiresAt: new Date(now3.getTime() + expiryDurationInSec * 1e3),
|
|
1499
|
+
settledAt: null,
|
|
1500
|
+
chargeId: null,
|
|
1501
|
+
failureReason: null,
|
|
1502
|
+
createdAt: now3
|
|
1503
|
+
};
|
|
1504
|
+
state.boltIntents.set(id, intent);
|
|
1505
|
+
return json({
|
|
1506
|
+
id,
|
|
1507
|
+
status: "PENDING",
|
|
1508
|
+
amount,
|
|
1509
|
+
currency,
|
|
1510
|
+
createdAt: now3.toISOString()
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
if (!intentId && request.method === "GET") {
|
|
1514
|
+
const deploymentId = url.searchParams.get("deploymentId");
|
|
1515
|
+
if (!deploymentId) {
|
|
1516
|
+
return payErr("Missing deploymentId parameter", 400);
|
|
1517
|
+
}
|
|
1518
|
+
const now3 = /* @__PURE__ */ new Date();
|
|
1519
|
+
const statusParam = url.searchParams.get("status");
|
|
1520
|
+
let requestedStatuses;
|
|
1521
|
+
if (statusParam) {
|
|
1522
|
+
const parts = statusParam.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1523
|
+
const parsed = [];
|
|
1524
|
+
for (const part of parts) {
|
|
1525
|
+
if (!isBoltIntentStatus(part)) {
|
|
1526
|
+
return payErr(`Invalid status: ${part}`, 400);
|
|
1527
|
+
}
|
|
1528
|
+
parsed.push(part);
|
|
1529
|
+
}
|
|
1530
|
+
requestedStatuses = parsed;
|
|
1531
|
+
}
|
|
1532
|
+
const boltConnectionId = url.searchParams.get("boltConnectionId") ?? void 0;
|
|
1533
|
+
const limitParam = url.searchParams.get("limit");
|
|
1534
|
+
let limit = 50;
|
|
1535
|
+
if (limitParam !== null) {
|
|
1536
|
+
const trimmed = limitParam.trim();
|
|
1537
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
1538
|
+
return payErr("limit must be an integer between 1 and 100", 400);
|
|
1539
|
+
}
|
|
1540
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
1541
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100) {
|
|
1542
|
+
return payErr("limit must be an integer between 1 and 100", 400);
|
|
1543
|
+
}
|
|
1544
|
+
limit = parsed;
|
|
1545
|
+
}
|
|
1546
|
+
let intents = [...state.boltIntents.values()];
|
|
1547
|
+
if (boltConnectionId) {
|
|
1548
|
+
intents = intents.filter((i) => i.boltConnectionId === boltConnectionId);
|
|
1549
|
+
}
|
|
1550
|
+
if (requestedStatuses && requestedStatuses.length > 0) {
|
|
1551
|
+
const storedWanted = new Set(storedStatusesForFilter(requestedStatuses));
|
|
1552
|
+
intents = intents.filter((i) => storedWanted.has(i.status));
|
|
1553
|
+
}
|
|
1554
|
+
let records = intents.map((i) => toBoltIntentRecord(i, now3));
|
|
1555
|
+
if (requestedStatuses && requestedStatuses.length > 0) {
|
|
1556
|
+
const wanted = new Set(requestedStatuses);
|
|
1557
|
+
records = records.filter((r) => wanted.has(r.status));
|
|
1558
|
+
}
|
|
1559
|
+
records = records.slice(0, limit);
|
|
1560
|
+
return json({ intents: records });
|
|
1561
|
+
}
|
|
1562
|
+
if (intentId && request.method === "DELETE") {
|
|
1563
|
+
const intent = findBoltIntent(intentId);
|
|
1564
|
+
if (!intent) return payErr("Bolt intent not found", 404, "NOT_FOUND");
|
|
1565
|
+
const derived = deriveBoltIntentStatus(intent.status, intent.expiresAt);
|
|
1566
|
+
if (derived !== "PENDING") {
|
|
1567
|
+
return payErr(`Bolt intent is ${derived}, only PENDING intents can be canceled`, 409);
|
|
1568
|
+
}
|
|
1569
|
+
intent.status = "CANCELED";
|
|
1570
|
+
intent.settledAt = /* @__PURE__ */ new Date();
|
|
1571
|
+
return json({ success: true });
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
const chargesMatch = pathname.match(/^\/api\/store\/beam\/([^/]+)\/charges$/);
|
|
1575
|
+
if (chargesMatch && request.method === "GET") {
|
|
1576
|
+
const sourceId = url.searchParams.get("sourceId");
|
|
1577
|
+
if (!sourceId) {
|
|
1578
|
+
return payErr("Missing sourceId parameter", 400);
|
|
1579
|
+
}
|
|
1580
|
+
const charges = state.boltCharges.filter((c) => c.sourceId === sourceId);
|
|
1581
|
+
return json({ charges });
|
|
1582
|
+
}
|
|
1278
1583
|
const beamProductsMatch = pathname.match(
|
|
1279
1584
|
/^\/api\/store\/beam\/([^/]+)\/payment-links(?:\/([^/]+))?$/
|
|
1280
1585
|
);
|
|
@@ -1409,12 +1714,70 @@ function createPayments() {
|
|
|
1409
1714
|
};
|
|
1410
1715
|
return deliverEvent(envelope, handler, options);
|
|
1411
1716
|
},
|
|
1717
|
+
approveBoltIntent(intentId, options) {
|
|
1718
|
+
const intent = requirePendingBoltIntent(intentId);
|
|
1719
|
+
const chargeId = options?.chargeId ?? nextBoltChargeId();
|
|
1720
|
+
const charge = {
|
|
1721
|
+
id: chargeId,
|
|
1722
|
+
status: "SUCCEEDED",
|
|
1723
|
+
amount: intent.amount,
|
|
1724
|
+
currency: intent.currency,
|
|
1725
|
+
sourceId: intent.beamIntentId,
|
|
1726
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1727
|
+
};
|
|
1728
|
+
intent.status = "PAID";
|
|
1729
|
+
intent.chargeId = chargeId;
|
|
1730
|
+
intent.settledAt = /* @__PURE__ */ new Date();
|
|
1731
|
+
state.boltCharges.push(charge);
|
|
1732
|
+
return charge;
|
|
1733
|
+
},
|
|
1734
|
+
// No webhook on decline: Beam has no charge-failure event for bolt payments.
|
|
1735
|
+
declineBoltIntent(intentId, options) {
|
|
1736
|
+
const intent = requirePendingBoltIntent(intentId);
|
|
1737
|
+
const chargeId = nextBoltChargeId();
|
|
1738
|
+
const failureReason = options?.failureReason ?? "declined";
|
|
1739
|
+
const charge = {
|
|
1740
|
+
id: chargeId,
|
|
1741
|
+
status: "FAILED",
|
|
1742
|
+
amount: intent.amount,
|
|
1743
|
+
currency: intent.currency,
|
|
1744
|
+
sourceId: intent.beamIntentId,
|
|
1745
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1746
|
+
};
|
|
1747
|
+
intent.status = "FAILED";
|
|
1748
|
+
intent.chargeId = chargeId;
|
|
1749
|
+
intent.failureReason = failureReason;
|
|
1750
|
+
intent.settledAt = /* @__PURE__ */ new Date();
|
|
1751
|
+
state.boltCharges.push(charge);
|
|
1752
|
+
return charge;
|
|
1753
|
+
},
|
|
1754
|
+
// No webhook on expire: Beam does not notify apps when a bolt intent expires.
|
|
1755
|
+
expireBoltIntent(intentId) {
|
|
1756
|
+
const intent = requirePendingBoltIntent(intentId);
|
|
1757
|
+
intent.expiresAt = new Date(Date.now() - 1e3);
|
|
1758
|
+
},
|
|
1759
|
+
listBoltIntents() {
|
|
1760
|
+
const now3 = /* @__PURE__ */ new Date();
|
|
1761
|
+
return [...state.boltIntents.values()].map((i) => toBoltIntentRecord(i, now3));
|
|
1762
|
+
},
|
|
1763
|
+
getBoltIntent(intentId) {
|
|
1764
|
+
const intent = findBoltIntent(intentId);
|
|
1765
|
+
if (!intent) return void 0;
|
|
1766
|
+
return toBoltIntentRecord(intent);
|
|
1767
|
+
},
|
|
1412
1768
|
clear() {
|
|
1413
1769
|
state.checkouts = [];
|
|
1414
1770
|
state.checkoutCounter = 0;
|
|
1415
1771
|
state.sessionStatuses.clear();
|
|
1416
1772
|
state.paymentLinks.clear();
|
|
1417
1773
|
state.products = [];
|
|
1774
|
+
state.boltConnections.clear();
|
|
1775
|
+
state.boltUsedPairingCodes.clear();
|
|
1776
|
+
state.boltConnectionCounter = 0;
|
|
1777
|
+
state.boltIntents.clear();
|
|
1778
|
+
state.boltIntentCounter = 0;
|
|
1779
|
+
state.boltCharges = [];
|
|
1780
|
+
state.boltChargeCounter = 0;
|
|
1418
1781
|
},
|
|
1419
1782
|
get count() {
|
|
1420
1783
|
return state.checkouts.length;
|
|
@@ -2195,11 +2558,89 @@ function uninstallFetchRouter() {
|
|
|
2195
2558
|
}
|
|
2196
2559
|
}
|
|
2197
2560
|
|
|
2561
|
+
// src/data-store-manifest.ts
|
|
2562
|
+
var STARDECK_DATA_STORES_ENV = "STARDECK_DATA_STORES";
|
|
2563
|
+
function dataStoreSlug(name) {
|
|
2564
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
2565
|
+
return slug || "store";
|
|
2566
|
+
}
|
|
2567
|
+
function buildTestDataStoreManifest(inputs = [{}]) {
|
|
2568
|
+
const takenSlugs = /* @__PURE__ */ new Set();
|
|
2569
|
+
const takenBindingKeys = /* @__PURE__ */ new Set();
|
|
2570
|
+
const takenIds = /* @__PURE__ */ new Set();
|
|
2571
|
+
return inputs.map((input, index) => {
|
|
2572
|
+
for (const field of ["bindingKey", "slug", "name"]) {
|
|
2573
|
+
if (input[field] === "") {
|
|
2574
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].${field} cannot be empty.`);
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
let slug;
|
|
2578
|
+
if (input.slug !== void 0) {
|
|
2579
|
+
slug = input.slug;
|
|
2580
|
+
if (takenSlugs.has(slug)) {
|
|
2581
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].slug must be unique.`);
|
|
2582
|
+
}
|
|
2583
|
+
} else {
|
|
2584
|
+
const base = input.name !== void 0 ? dataStoreSlug(input.name) : index === 0 ? "main" : null;
|
|
2585
|
+
if (base === null) {
|
|
2586
|
+
throw new Error(
|
|
2587
|
+
`[stardeck-testing] dataStores[${index}] requires a slug or name. Only the first entry defaults to the "main" slug.`
|
|
2588
|
+
);
|
|
2589
|
+
}
|
|
2590
|
+
slug = base;
|
|
2591
|
+
let suffix = 2;
|
|
2592
|
+
while (takenSlugs.has(slug)) {
|
|
2593
|
+
slug = `${base}_${suffix}`;
|
|
2594
|
+
suffix++;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
const id = input.id ?? `00000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`;
|
|
2598
|
+
if (takenIds.has(id)) {
|
|
2599
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].id must be unique.`);
|
|
2600
|
+
}
|
|
2601
|
+
if (input.bindingKey !== void 0 && takenBindingKeys.has(input.bindingKey)) {
|
|
2602
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].bindingKey must be unique.`);
|
|
2603
|
+
}
|
|
2604
|
+
takenSlugs.add(slug);
|
|
2605
|
+
takenIds.add(id);
|
|
2606
|
+
if (input.bindingKey !== void 0) takenBindingKeys.add(input.bindingKey);
|
|
2607
|
+
const storeType = input.storeType ?? "database";
|
|
2608
|
+
return {
|
|
2609
|
+
id,
|
|
2610
|
+
name: input.name ?? slug,
|
|
2611
|
+
slug,
|
|
2612
|
+
storeType,
|
|
2613
|
+
accessLevel: input.accessLevel ?? "admin",
|
|
2614
|
+
...storeType === "database" ? { url: input.url ?? DATA_STORE_TEST_URL } : {},
|
|
2615
|
+
...input.bindingKey ? { bindingKey: input.bindingKey } : {}
|
|
2616
|
+
};
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2198
2620
|
// src/test-app.ts
|
|
2199
|
-
function applyTestEnv() {
|
|
2621
|
+
function applyTestEnv(options) {
|
|
2200
2622
|
for (const [key, value] of Object.entries(TEST_ENV_DEFAULTS)) {
|
|
2201
2623
|
process.env[key] = value;
|
|
2202
2624
|
}
|
|
2625
|
+
let manifestAdopted = true;
|
|
2626
|
+
if (options.dataStores !== void 0) {
|
|
2627
|
+
const manifest = JSON.stringify(buildTestDataStoreManifest(options.dataStores));
|
|
2628
|
+
process.env[STARDECK_DATA_STORES_ENV] = manifest;
|
|
2629
|
+
state.lastHarnessDataStoreManifest = manifest;
|
|
2630
|
+
} else {
|
|
2631
|
+
const currentManifest = process.env[STARDECK_DATA_STORES_ENV];
|
|
2632
|
+
if (!currentManifest || currentManifest === state.lastHarnessDataStoreManifest) {
|
|
2633
|
+
const manifest = JSON.stringify(buildTestDataStoreManifest());
|
|
2634
|
+
process.env[STARDECK_DATA_STORES_ENV] = manifest;
|
|
2635
|
+
state.lastHarnessDataStoreManifest = manifest;
|
|
2636
|
+
manifestAdopted = false;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
if (options.legacyDataStoreUrl === true || !manifestAdopted && options.legacyDataStoreUrl === void 0) {
|
|
2640
|
+
process.env.DATA_STORE_URL = DATA_STORE_TEST_URL;
|
|
2641
|
+
} else {
|
|
2642
|
+
delete process.env.DATA_STORE_URL;
|
|
2643
|
+
}
|
|
2203
2644
|
delete process.env.DISABLE_AUTH;
|
|
2204
2645
|
delete process.env.NEXT_PUBLIC_DISABLE_AUTH;
|
|
2205
2646
|
delete process.env.SANDBOX_MODE;
|
|
@@ -2232,7 +2673,7 @@ async function createTestApp(options = {}) {
|
|
|
2232
2673
|
"[stardeck-testing] A test app is already active in this test file. Call `app.close()` first, or share one app per file."
|
|
2233
2674
|
);
|
|
2234
2675
|
}
|
|
2235
|
-
applyTestEnv();
|
|
2676
|
+
applyTestEnv(options);
|
|
2236
2677
|
state.allowNetwork = options.allowNetwork ?? false;
|
|
2237
2678
|
installFetchRouter();
|
|
2238
2679
|
const db = await createPgliteDatabase();
|
|
@@ -2333,11 +2774,14 @@ function parseWorkflowName(describeTitle) {
|
|
|
2333
2774
|
export {
|
|
2334
2775
|
CONTROL_PLANE_TEST_URL,
|
|
2335
2776
|
DATA_STORE_TEST_HOST,
|
|
2777
|
+
DATA_STORE_TEST_URL,
|
|
2336
2778
|
DEFAULT_SCHEMA_PATH,
|
|
2779
|
+
STARDECK_DATA_STORES_ENV,
|
|
2337
2780
|
STORAGE_TEST_HOST,
|
|
2338
2781
|
STORAGE_TEST_URL,
|
|
2339
2782
|
TEST_ENV_DEFAULTS,
|
|
2340
2783
|
WORKFLOW_NAME_PREFIX,
|
|
2784
|
+
buildTestDataStoreManifest,
|
|
2341
2785
|
callRoute,
|
|
2342
2786
|
createTestApp,
|
|
2343
2787
|
describeWorkflow,
|
|
@@ -39,6 +39,7 @@ function globalSingleton(key, create) {
|
|
|
39
39
|
// src/state.ts
|
|
40
40
|
var state = globalSingleton("state", () => ({
|
|
41
41
|
db: null,
|
|
42
|
+
lastHarnessDataStoreManifest: null,
|
|
42
43
|
currentUser: null,
|
|
43
44
|
sessions: /* @__PURE__ */ new Map(),
|
|
44
45
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
@@ -51,6 +52,13 @@ var state = globalSingleton("state", () => ({
|
|
|
51
52
|
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
52
53
|
paymentLinks: /* @__PURE__ */ new Map(),
|
|
53
54
|
products: [],
|
|
55
|
+
boltConnections: /* @__PURE__ */ new Map(),
|
|
56
|
+
boltUsedPairingCodes: /* @__PURE__ */ new Set(),
|
|
57
|
+
boltConnectionCounter: 0,
|
|
58
|
+
boltIntents: /* @__PURE__ */ new Map(),
|
|
59
|
+
boltIntentCounter: 0,
|
|
60
|
+
boltCharges: [],
|
|
61
|
+
boltChargeCounter: 0,
|
|
54
62
|
uploads: [],
|
|
55
63
|
uploadCounter: 0,
|
|
56
64
|
storageFiles: /* @__PURE__ */ new Map(),
|
|
@@ -12,6 +12,7 @@ function globalSingleton(key, create) {
|
|
|
12
12
|
// src/state.ts
|
|
13
13
|
var state = globalSingleton("state", () => ({
|
|
14
14
|
db: null,
|
|
15
|
+
lastHarnessDataStoreManifest: null,
|
|
15
16
|
currentUser: null,
|
|
16
17
|
sessions: /* @__PURE__ */ new Map(),
|
|
17
18
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
@@ -24,6 +25,13 @@ var state = globalSingleton("state", () => ({
|
|
|
24
25
|
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
25
26
|
paymentLinks: /* @__PURE__ */ new Map(),
|
|
26
27
|
products: [],
|
|
28
|
+
boltConnections: /* @__PURE__ */ new Map(),
|
|
29
|
+
boltUsedPairingCodes: /* @__PURE__ */ new Set(),
|
|
30
|
+
boltConnectionCounter: 0,
|
|
31
|
+
boltIntents: /* @__PURE__ */ new Map(),
|
|
32
|
+
boltIntentCounter: 0,
|
|
33
|
+
boltCharges: [],
|
|
34
|
+
boltChargeCounter: 0,
|
|
27
35
|
uploads: [],
|
|
28
36
|
uploadCounter: 0,
|
|
29
37
|
storageFiles: /* @__PURE__ */ new Map(),
|