@stardeck-customer-apps/testing 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/setup.js CHANGED
@@ -45,6 +45,13 @@ var state = globalSingleton("state", () => ({
45
45
  sessionStatuses: /* @__PURE__ */ new Map(),
46
46
  paymentLinks: /* @__PURE__ */ new Map(),
47
47
  products: [],
48
+ boltConnections: /* @__PURE__ */ new Map(),
49
+ boltUsedPairingCodes: /* @__PURE__ */ new Set(),
50
+ boltConnectionCounter: 0,
51
+ boltIntents: /* @__PURE__ */ new Map(),
52
+ boltIntentCounter: 0,
53
+ boltCharges: [],
54
+ boltChargeCounter: 0,
48
55
  uploads: [],
49
56
  uploadCounter: 0,
50
57
  storageFiles: /* @__PURE__ */ new Map(),
@@ -983,8 +990,31 @@ var requestScopeStorage = globalSingleton(
983
990
  var import_node_crypto4 = __toESM(require("crypto"));
984
991
 
985
992
  // src/simulator/payments.ts
986
- function payErr(error, status = 400, code) {
987
- return json(code ? { error, code } : { error }, status);
993
+ var BEAM_BOLT_PAYMENT_METHODS = [
994
+ "CARD",
995
+ "CARD_INSTALLMENTS",
996
+ "QR_PROMPT_PAY",
997
+ "ALIPAY",
998
+ "ALIPAY_PLUS",
999
+ "LINE_PAY",
1000
+ "SHOPEE_PAY",
1001
+ "TRUE_MONEY",
1002
+ "WECHAT_PAY",
1003
+ "SPAY_LATER"
1004
+ ];
1005
+ var BEAM_BOLT_INTENT_STATUSES = ["PENDING", "PAID", "FAILED", "CANCELED", "EXPIRED"];
1006
+ 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;
1007
+ function payErr(error, status = 400, code, details) {
1008
+ const body = { error };
1009
+ if (code) body.code = code;
1010
+ if (details !== void 0) body.details = details;
1011
+ return json(body, status);
1012
+ }
1013
+ function isBoltPaymentMethod(value) {
1014
+ return typeof value === "string" && BEAM_BOLT_PAYMENT_METHODS.includes(value);
1015
+ }
1016
+ function isBoltIntentStatus(value) {
1017
+ return BEAM_BOLT_INTENT_STATUSES.includes(value);
988
1018
  }
989
1019
  function nextCheckoutId() {
990
1020
  state.checkoutCounter += 1;
@@ -994,6 +1024,14 @@ function nextPaymentLinkId() {
994
1024
  state.checkoutCounter += 1;
995
1025
  return `plink_test_${state.checkoutCounter}`;
996
1026
  }
1027
+ function nextBoltConnectionId() {
1028
+ state.boltConnectionCounter += 1;
1029
+ return `boltc_${state.boltConnectionCounter}`;
1030
+ }
1031
+ function nextBoltIntentId() {
1032
+ state.boltIntentCounter += 1;
1033
+ return `bolti_${state.boltIntentCounter}`;
1034
+ }
997
1035
  function seedStripeSession(id, body) {
998
1036
  const lineItems = body.lineItems ?? [];
999
1037
  let amountTotal = null;
@@ -1037,11 +1075,262 @@ function seedBeamLink(id, body, merchantId) {
1037
1075
  collectDeliveryAddress: body.collectDeliveryAddress === true
1038
1076
  });
1039
1077
  }
1078
+ function deriveBoltIntentStatus(storedStatus, expiresAt, now3 = /* @__PURE__ */ new Date()) {
1079
+ if (storedStatus !== "PENDING") return storedStatus;
1080
+ return expiresAt.getTime() <= now3.getTime() ? "EXPIRED" : "PENDING";
1081
+ }
1082
+ function toBoltIntentRecord(intent, now3 = /* @__PURE__ */ new Date()) {
1083
+ return {
1084
+ id: intent.id,
1085
+ beamIntentId: intent.beamIntentId,
1086
+ boltConnectionId: intent.boltConnectionId,
1087
+ amount: intent.amount,
1088
+ currency: intent.currency,
1089
+ paymentMethodType: intent.paymentMethodType,
1090
+ referenceId: intent.referenceId,
1091
+ internalNote: intent.internalNote,
1092
+ status: deriveBoltIntentStatus(intent.status, intent.expiresAt, now3),
1093
+ isVirtual: intent.isVirtual,
1094
+ environment: intent.environment,
1095
+ expiresAt: intent.expiresAt.toISOString(),
1096
+ settledAt: intent.settledAt ? intent.settledAt.toISOString() : null,
1097
+ chargeId: intent.chargeId,
1098
+ failureReason: intent.failureReason,
1099
+ createdAt: intent.createdAt.toISOString()
1100
+ };
1101
+ }
1102
+ function findBoltConnection(connectionId) {
1103
+ return state.boltConnections.get(connectionId) ?? [...state.boltConnections.values()].find((c) => c.beamConnectionId === connectionId);
1104
+ }
1105
+ function findBoltIntent(intentId) {
1106
+ return state.boltIntents.get(intentId) ?? [...state.boltIntents.values()].find((i) => i.beamIntentId === intentId);
1107
+ }
1108
+ function storedStatusesForFilter(statuses) {
1109
+ const stored = /* @__PURE__ */ new Set();
1110
+ for (const status of statuses) {
1111
+ if (status === "EXPIRED" || status === "PENDING") {
1112
+ stored.add("PENDING");
1113
+ } else {
1114
+ stored.add(status);
1115
+ }
1116
+ }
1117
+ return [...stored];
1118
+ }
1040
1119
  async function handlePaymentsRequest(request, url) {
1041
1120
  const pathname = url.pathname;
1042
- if (/\/bolt-connections/.test(pathname) || /\/bolt-intents/.test(pathname) || /\/charges(\/|$)/.test(pathname) || /\/billing-portal$/.test(pathname)) {
1121
+ if (/\/billing-portal$/.test(pathname)) {
1043
1122
  return payErr("Not found", 404, "NOT_FOUND");
1044
1123
  }
1124
+ const boltConnectionsMatch = pathname.match(
1125
+ /^\/api\/store\/beam\/([^/]+)\/bolt-connections(?:\/([^/]+))?$/
1126
+ );
1127
+ if (boltConnectionsMatch) {
1128
+ const connectionId = boltConnectionsMatch[2];
1129
+ if (!connectionId && request.method === "POST") {
1130
+ const body = await readJsonBody(request);
1131
+ const pairingCode = body.pairingCode ? String(body.pairingCode) : "";
1132
+ if (!pairingCode) {
1133
+ return payErr("pairingCode is required", 400);
1134
+ }
1135
+ if (state.boltUsedPairingCodes.has(pairingCode)) {
1136
+ return payErr("Pairing code has already been used", 400, "PAIRING_CODE_USED");
1137
+ }
1138
+ const id = nextBoltConnectionId();
1139
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
1140
+ const environments = Array.isArray(body.environments) ? body.environments.map(String) : ["sandbox"];
1141
+ const isSandbox = environments.some((e) => e === "sandbox" || e === "preview");
1142
+ const connection = {
1143
+ id,
1144
+ projectId: TEST_ENV_DEFAULTS.PROJECT_ID,
1145
+ beamConnectionId: id,
1146
+ displayName: body.displayName != null ? String(body.displayName) : null,
1147
+ pairingCode,
1148
+ status: "ACTIVE",
1149
+ isSandbox,
1150
+ environments,
1151
+ createdAt: now3,
1152
+ updatedAt: now3
1153
+ };
1154
+ state.boltUsedPairingCodes.add(pairingCode);
1155
+ state.boltConnections.set(id, connection);
1156
+ return json({ connection });
1157
+ }
1158
+ if (!connectionId && request.method === "GET") {
1159
+ return json({ connections: [...state.boltConnections.values()] });
1160
+ }
1161
+ if (connectionId && request.method === "GET") {
1162
+ const connection = findBoltConnection(connectionId);
1163
+ if (!connection) return payErr("Bolt connection not found", 404, "NOT_FOUND");
1164
+ return json({ connection });
1165
+ }
1166
+ if (connectionId && request.method === "DELETE") {
1167
+ const connection = findBoltConnection(connectionId);
1168
+ if (!connection) return payErr("Bolt connection not found", 404, "NOT_FOUND");
1169
+ state.boltConnections.delete(connection.id);
1170
+ return json({ success: true });
1171
+ }
1172
+ }
1173
+ const boltIntentsMatch = pathname.match(
1174
+ /^\/api\/store\/beam\/([^/]+)\/bolt-intents(?:\/([^/]+))?$/
1175
+ );
1176
+ if (boltIntentsMatch) {
1177
+ const intentId = boltIntentsMatch[2];
1178
+ if (!intentId && request.method === "POST") {
1179
+ const body = await readJsonBody(request);
1180
+ const issues = [];
1181
+ if (typeof body.amount !== "number" || !Number.isInteger(body.amount) || body.amount < 1) {
1182
+ issues.push({
1183
+ path: ["amount"],
1184
+ message: "Number must be greater than 0",
1185
+ code: "too_small"
1186
+ });
1187
+ }
1188
+ const boltConnectionIdRaw = body.boltConnectionId;
1189
+ if (typeof boltConnectionIdRaw !== "string" || boltConnectionIdRaw.length < 1) {
1190
+ issues.push({
1191
+ path: ["boltConnectionId"],
1192
+ message: "String must contain at least 1 character(s)",
1193
+ code: "too_small"
1194
+ });
1195
+ }
1196
+ const paymentMethod = body.paymentMethod;
1197
+ if (!paymentMethod || typeof paymentMethod !== "object") {
1198
+ issues.push({
1199
+ path: ["paymentMethod"],
1200
+ message: "Required",
1201
+ code: "invalid_type"
1202
+ });
1203
+ } else if (!isBoltPaymentMethod(paymentMethod.paymentMethodType)) {
1204
+ issues.push({
1205
+ path: ["paymentMethod", "paymentMethodType"],
1206
+ message: "Invalid enum value",
1207
+ code: "invalid_enum_value"
1208
+ });
1209
+ }
1210
+ if (typeof body.expiryDurationInSec !== "number" || !Number.isInteger(body.expiryDurationInSec) || body.expiryDurationInSec < 90 || body.expiryDurationInSec > 600) {
1211
+ issues.push({
1212
+ path: ["expiryDurationInSec"],
1213
+ message: "Number must be between 90 and 600",
1214
+ code: "too_small"
1215
+ });
1216
+ }
1217
+ if (typeof body.deploymentId !== "string" || !UUID_RE.test(body.deploymentId)) {
1218
+ issues.push({
1219
+ path: ["deploymentId"],
1220
+ message: "Invalid uuid",
1221
+ code: "invalid_string"
1222
+ });
1223
+ }
1224
+ if (issues.length > 0) {
1225
+ return payErr("Invalid request body", 400, void 0, issues);
1226
+ }
1227
+ const boltConnectionId = String(body.boltConnectionId);
1228
+ if (!findBoltConnection(boltConnectionId)) {
1229
+ return payErr("Bolt connection not found", 404, "NOT_FOUND");
1230
+ }
1231
+ const paymentMethodType = body.paymentMethod.paymentMethodType;
1232
+ const amount = body.amount;
1233
+ const currency = String(body.currency ?? "THB");
1234
+ const expiryDurationInSec = body.expiryDurationInSec;
1235
+ const id = nextBoltIntentId();
1236
+ const now3 = /* @__PURE__ */ new Date();
1237
+ const intent = {
1238
+ id,
1239
+ beamIntentId: id,
1240
+ boltConnectionId,
1241
+ amount,
1242
+ currency,
1243
+ paymentMethodType,
1244
+ referenceId: body.referenceId != null ? String(body.referenceId) : null,
1245
+ internalNote: body.internalNote != null ? String(body.internalNote) : null,
1246
+ status: "PENDING",
1247
+ isVirtual: false,
1248
+ environment: "sandbox",
1249
+ expiresAt: new Date(now3.getTime() + expiryDurationInSec * 1e3),
1250
+ settledAt: null,
1251
+ chargeId: null,
1252
+ failureReason: null,
1253
+ createdAt: now3
1254
+ };
1255
+ state.boltIntents.set(id, intent);
1256
+ return json({
1257
+ id,
1258
+ status: "PENDING",
1259
+ amount,
1260
+ currency,
1261
+ createdAt: now3.toISOString()
1262
+ });
1263
+ }
1264
+ if (!intentId && request.method === "GET") {
1265
+ const deploymentId = url.searchParams.get("deploymentId");
1266
+ if (!deploymentId) {
1267
+ return payErr("Missing deploymentId parameter", 400);
1268
+ }
1269
+ const now3 = /* @__PURE__ */ new Date();
1270
+ const statusParam = url.searchParams.get("status");
1271
+ let requestedStatuses;
1272
+ if (statusParam) {
1273
+ const parts = statusParam.split(",").map((s) => s.trim()).filter(Boolean);
1274
+ const parsed = [];
1275
+ for (const part of parts) {
1276
+ if (!isBoltIntentStatus(part)) {
1277
+ return payErr(`Invalid status: ${part}`, 400);
1278
+ }
1279
+ parsed.push(part);
1280
+ }
1281
+ requestedStatuses = parsed;
1282
+ }
1283
+ const boltConnectionId = url.searchParams.get("boltConnectionId") ?? void 0;
1284
+ const limitParam = url.searchParams.get("limit");
1285
+ let limit = 50;
1286
+ if (limitParam !== null) {
1287
+ const trimmed = limitParam.trim();
1288
+ if (!/^\d+$/.test(trimmed)) {
1289
+ return payErr("limit must be an integer between 1 and 100", 400);
1290
+ }
1291
+ const parsed = Number.parseInt(trimmed, 10);
1292
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100) {
1293
+ return payErr("limit must be an integer between 1 and 100", 400);
1294
+ }
1295
+ limit = parsed;
1296
+ }
1297
+ let intents = [...state.boltIntents.values()];
1298
+ if (boltConnectionId) {
1299
+ intents = intents.filter((i) => i.boltConnectionId === boltConnectionId);
1300
+ }
1301
+ if (requestedStatuses && requestedStatuses.length > 0) {
1302
+ const storedWanted = new Set(storedStatusesForFilter(requestedStatuses));
1303
+ intents = intents.filter((i) => storedWanted.has(i.status));
1304
+ }
1305
+ let records = intents.map((i) => toBoltIntentRecord(i, now3));
1306
+ if (requestedStatuses && requestedStatuses.length > 0) {
1307
+ const wanted = new Set(requestedStatuses);
1308
+ records = records.filter((r) => wanted.has(r.status));
1309
+ }
1310
+ records = records.slice(0, limit);
1311
+ return json({ intents: records });
1312
+ }
1313
+ if (intentId && request.method === "DELETE") {
1314
+ const intent = findBoltIntent(intentId);
1315
+ if (!intent) return payErr("Bolt intent not found", 404, "NOT_FOUND");
1316
+ const derived = deriveBoltIntentStatus(intent.status, intent.expiresAt);
1317
+ if (derived !== "PENDING") {
1318
+ return payErr(`Bolt intent is ${derived}, only PENDING intents can be canceled`, 409);
1319
+ }
1320
+ intent.status = "CANCELED";
1321
+ intent.settledAt = /* @__PURE__ */ new Date();
1322
+ return json({ success: true });
1323
+ }
1324
+ }
1325
+ const chargesMatch = pathname.match(/^\/api\/store\/beam\/([^/]+)\/charges$/);
1326
+ if (chargesMatch && request.method === "GET") {
1327
+ const sourceId = url.searchParams.get("sourceId");
1328
+ if (!sourceId) {
1329
+ return payErr("Missing sourceId parameter", 400);
1330
+ }
1331
+ const charges = state.boltCharges.filter((c) => c.sourceId === sourceId);
1332
+ return json({ charges });
1333
+ }
1045
1334
  const beamProductsMatch = pathname.match(
1046
1335
  /^\/api\/store\/beam\/([^/]+)\/payment-links(?:\/([^/]+))?$/
1047
1336
  );
package/dist/setup.mjs CHANGED
@@ -21,6 +21,13 @@ var state = globalSingleton("state", () => ({
21
21
  sessionStatuses: /* @__PURE__ */ new Map(),
22
22
  paymentLinks: /* @__PURE__ */ new Map(),
23
23
  products: [],
24
+ boltConnections: /* @__PURE__ */ new Map(),
25
+ boltUsedPairingCodes: /* @__PURE__ */ new Set(),
26
+ boltConnectionCounter: 0,
27
+ boltIntents: /* @__PURE__ */ new Map(),
28
+ boltIntentCounter: 0,
29
+ boltCharges: [],
30
+ boltChargeCounter: 0,
24
31
  uploads: [],
25
32
  uploadCounter: 0,
26
33
  storageFiles: /* @__PURE__ */ new Map(),
@@ -959,8 +966,31 @@ var requestScopeStorage = globalSingleton(
959
966
  import crypto4 from "crypto";
960
967
 
961
968
  // src/simulator/payments.ts
962
- function payErr(error, status = 400, code) {
963
- return json(code ? { error, code } : { error }, status);
969
+ var BEAM_BOLT_PAYMENT_METHODS = [
970
+ "CARD",
971
+ "CARD_INSTALLMENTS",
972
+ "QR_PROMPT_PAY",
973
+ "ALIPAY",
974
+ "ALIPAY_PLUS",
975
+ "LINE_PAY",
976
+ "SHOPEE_PAY",
977
+ "TRUE_MONEY",
978
+ "WECHAT_PAY",
979
+ "SPAY_LATER"
980
+ ];
981
+ var BEAM_BOLT_INTENT_STATUSES = ["PENDING", "PAID", "FAILED", "CANCELED", "EXPIRED"];
982
+ 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;
983
+ function payErr(error, status = 400, code, details) {
984
+ const body = { error };
985
+ if (code) body.code = code;
986
+ if (details !== void 0) body.details = details;
987
+ return json(body, status);
988
+ }
989
+ function isBoltPaymentMethod(value) {
990
+ return typeof value === "string" && BEAM_BOLT_PAYMENT_METHODS.includes(value);
991
+ }
992
+ function isBoltIntentStatus(value) {
993
+ return BEAM_BOLT_INTENT_STATUSES.includes(value);
964
994
  }
965
995
  function nextCheckoutId() {
966
996
  state.checkoutCounter += 1;
@@ -970,6 +1000,14 @@ function nextPaymentLinkId() {
970
1000
  state.checkoutCounter += 1;
971
1001
  return `plink_test_${state.checkoutCounter}`;
972
1002
  }
1003
+ function nextBoltConnectionId() {
1004
+ state.boltConnectionCounter += 1;
1005
+ return `boltc_${state.boltConnectionCounter}`;
1006
+ }
1007
+ function nextBoltIntentId() {
1008
+ state.boltIntentCounter += 1;
1009
+ return `bolti_${state.boltIntentCounter}`;
1010
+ }
973
1011
  function seedStripeSession(id, body) {
974
1012
  const lineItems = body.lineItems ?? [];
975
1013
  let amountTotal = null;
@@ -1013,11 +1051,262 @@ function seedBeamLink(id, body, merchantId) {
1013
1051
  collectDeliveryAddress: body.collectDeliveryAddress === true
1014
1052
  });
1015
1053
  }
1054
+ function deriveBoltIntentStatus(storedStatus, expiresAt, now3 = /* @__PURE__ */ new Date()) {
1055
+ if (storedStatus !== "PENDING") return storedStatus;
1056
+ return expiresAt.getTime() <= now3.getTime() ? "EXPIRED" : "PENDING";
1057
+ }
1058
+ function toBoltIntentRecord(intent, now3 = /* @__PURE__ */ new Date()) {
1059
+ return {
1060
+ id: intent.id,
1061
+ beamIntentId: intent.beamIntentId,
1062
+ boltConnectionId: intent.boltConnectionId,
1063
+ amount: intent.amount,
1064
+ currency: intent.currency,
1065
+ paymentMethodType: intent.paymentMethodType,
1066
+ referenceId: intent.referenceId,
1067
+ internalNote: intent.internalNote,
1068
+ status: deriveBoltIntentStatus(intent.status, intent.expiresAt, now3),
1069
+ isVirtual: intent.isVirtual,
1070
+ environment: intent.environment,
1071
+ expiresAt: intent.expiresAt.toISOString(),
1072
+ settledAt: intent.settledAt ? intent.settledAt.toISOString() : null,
1073
+ chargeId: intent.chargeId,
1074
+ failureReason: intent.failureReason,
1075
+ createdAt: intent.createdAt.toISOString()
1076
+ };
1077
+ }
1078
+ function findBoltConnection(connectionId) {
1079
+ return state.boltConnections.get(connectionId) ?? [...state.boltConnections.values()].find((c) => c.beamConnectionId === connectionId);
1080
+ }
1081
+ function findBoltIntent(intentId) {
1082
+ return state.boltIntents.get(intentId) ?? [...state.boltIntents.values()].find((i) => i.beamIntentId === intentId);
1083
+ }
1084
+ function storedStatusesForFilter(statuses) {
1085
+ const stored = /* @__PURE__ */ new Set();
1086
+ for (const status of statuses) {
1087
+ if (status === "EXPIRED" || status === "PENDING") {
1088
+ stored.add("PENDING");
1089
+ } else {
1090
+ stored.add(status);
1091
+ }
1092
+ }
1093
+ return [...stored];
1094
+ }
1016
1095
  async function handlePaymentsRequest(request, url) {
1017
1096
  const pathname = url.pathname;
1018
- if (/\/bolt-connections/.test(pathname) || /\/bolt-intents/.test(pathname) || /\/charges(\/|$)/.test(pathname) || /\/billing-portal$/.test(pathname)) {
1097
+ if (/\/billing-portal$/.test(pathname)) {
1019
1098
  return payErr("Not found", 404, "NOT_FOUND");
1020
1099
  }
1100
+ const boltConnectionsMatch = pathname.match(
1101
+ /^\/api\/store\/beam\/([^/]+)\/bolt-connections(?:\/([^/]+))?$/
1102
+ );
1103
+ if (boltConnectionsMatch) {
1104
+ const connectionId = boltConnectionsMatch[2];
1105
+ if (!connectionId && request.method === "POST") {
1106
+ const body = await readJsonBody(request);
1107
+ const pairingCode = body.pairingCode ? String(body.pairingCode) : "";
1108
+ if (!pairingCode) {
1109
+ return payErr("pairingCode is required", 400);
1110
+ }
1111
+ if (state.boltUsedPairingCodes.has(pairingCode)) {
1112
+ return payErr("Pairing code has already been used", 400, "PAIRING_CODE_USED");
1113
+ }
1114
+ const id = nextBoltConnectionId();
1115
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
1116
+ const environments = Array.isArray(body.environments) ? body.environments.map(String) : ["sandbox"];
1117
+ const isSandbox = environments.some((e) => e === "sandbox" || e === "preview");
1118
+ const connection = {
1119
+ id,
1120
+ projectId: TEST_ENV_DEFAULTS.PROJECT_ID,
1121
+ beamConnectionId: id,
1122
+ displayName: body.displayName != null ? String(body.displayName) : null,
1123
+ pairingCode,
1124
+ status: "ACTIVE",
1125
+ isSandbox,
1126
+ environments,
1127
+ createdAt: now3,
1128
+ updatedAt: now3
1129
+ };
1130
+ state.boltUsedPairingCodes.add(pairingCode);
1131
+ state.boltConnections.set(id, connection);
1132
+ return json({ connection });
1133
+ }
1134
+ if (!connectionId && request.method === "GET") {
1135
+ return json({ connections: [...state.boltConnections.values()] });
1136
+ }
1137
+ if (connectionId && request.method === "GET") {
1138
+ const connection = findBoltConnection(connectionId);
1139
+ if (!connection) return payErr("Bolt connection not found", 404, "NOT_FOUND");
1140
+ return json({ connection });
1141
+ }
1142
+ if (connectionId && request.method === "DELETE") {
1143
+ const connection = findBoltConnection(connectionId);
1144
+ if (!connection) return payErr("Bolt connection not found", 404, "NOT_FOUND");
1145
+ state.boltConnections.delete(connection.id);
1146
+ return json({ success: true });
1147
+ }
1148
+ }
1149
+ const boltIntentsMatch = pathname.match(
1150
+ /^\/api\/store\/beam\/([^/]+)\/bolt-intents(?:\/([^/]+))?$/
1151
+ );
1152
+ if (boltIntentsMatch) {
1153
+ const intentId = boltIntentsMatch[2];
1154
+ if (!intentId && request.method === "POST") {
1155
+ const body = await readJsonBody(request);
1156
+ const issues = [];
1157
+ if (typeof body.amount !== "number" || !Number.isInteger(body.amount) || body.amount < 1) {
1158
+ issues.push({
1159
+ path: ["amount"],
1160
+ message: "Number must be greater than 0",
1161
+ code: "too_small"
1162
+ });
1163
+ }
1164
+ const boltConnectionIdRaw = body.boltConnectionId;
1165
+ if (typeof boltConnectionIdRaw !== "string" || boltConnectionIdRaw.length < 1) {
1166
+ issues.push({
1167
+ path: ["boltConnectionId"],
1168
+ message: "String must contain at least 1 character(s)",
1169
+ code: "too_small"
1170
+ });
1171
+ }
1172
+ const paymentMethod = body.paymentMethod;
1173
+ if (!paymentMethod || typeof paymentMethod !== "object") {
1174
+ issues.push({
1175
+ path: ["paymentMethod"],
1176
+ message: "Required",
1177
+ code: "invalid_type"
1178
+ });
1179
+ } else if (!isBoltPaymentMethod(paymentMethod.paymentMethodType)) {
1180
+ issues.push({
1181
+ path: ["paymentMethod", "paymentMethodType"],
1182
+ message: "Invalid enum value",
1183
+ code: "invalid_enum_value"
1184
+ });
1185
+ }
1186
+ if (typeof body.expiryDurationInSec !== "number" || !Number.isInteger(body.expiryDurationInSec) || body.expiryDurationInSec < 90 || body.expiryDurationInSec > 600) {
1187
+ issues.push({
1188
+ path: ["expiryDurationInSec"],
1189
+ message: "Number must be between 90 and 600",
1190
+ code: "too_small"
1191
+ });
1192
+ }
1193
+ if (typeof body.deploymentId !== "string" || !UUID_RE.test(body.deploymentId)) {
1194
+ issues.push({
1195
+ path: ["deploymentId"],
1196
+ message: "Invalid uuid",
1197
+ code: "invalid_string"
1198
+ });
1199
+ }
1200
+ if (issues.length > 0) {
1201
+ return payErr("Invalid request body", 400, void 0, issues);
1202
+ }
1203
+ const boltConnectionId = String(body.boltConnectionId);
1204
+ if (!findBoltConnection(boltConnectionId)) {
1205
+ return payErr("Bolt connection not found", 404, "NOT_FOUND");
1206
+ }
1207
+ const paymentMethodType = body.paymentMethod.paymentMethodType;
1208
+ const amount = body.amount;
1209
+ const currency = String(body.currency ?? "THB");
1210
+ const expiryDurationInSec = body.expiryDurationInSec;
1211
+ const id = nextBoltIntentId();
1212
+ const now3 = /* @__PURE__ */ new Date();
1213
+ const intent = {
1214
+ id,
1215
+ beamIntentId: id,
1216
+ boltConnectionId,
1217
+ amount,
1218
+ currency,
1219
+ paymentMethodType,
1220
+ referenceId: body.referenceId != null ? String(body.referenceId) : null,
1221
+ internalNote: body.internalNote != null ? String(body.internalNote) : null,
1222
+ status: "PENDING",
1223
+ isVirtual: false,
1224
+ environment: "sandbox",
1225
+ expiresAt: new Date(now3.getTime() + expiryDurationInSec * 1e3),
1226
+ settledAt: null,
1227
+ chargeId: null,
1228
+ failureReason: null,
1229
+ createdAt: now3
1230
+ };
1231
+ state.boltIntents.set(id, intent);
1232
+ return json({
1233
+ id,
1234
+ status: "PENDING",
1235
+ amount,
1236
+ currency,
1237
+ createdAt: now3.toISOString()
1238
+ });
1239
+ }
1240
+ if (!intentId && request.method === "GET") {
1241
+ const deploymentId = url.searchParams.get("deploymentId");
1242
+ if (!deploymentId) {
1243
+ return payErr("Missing deploymentId parameter", 400);
1244
+ }
1245
+ const now3 = /* @__PURE__ */ new Date();
1246
+ const statusParam = url.searchParams.get("status");
1247
+ let requestedStatuses;
1248
+ if (statusParam) {
1249
+ const parts = statusParam.split(",").map((s) => s.trim()).filter(Boolean);
1250
+ const parsed = [];
1251
+ for (const part of parts) {
1252
+ if (!isBoltIntentStatus(part)) {
1253
+ return payErr(`Invalid status: ${part}`, 400);
1254
+ }
1255
+ parsed.push(part);
1256
+ }
1257
+ requestedStatuses = parsed;
1258
+ }
1259
+ const boltConnectionId = url.searchParams.get("boltConnectionId") ?? void 0;
1260
+ const limitParam = url.searchParams.get("limit");
1261
+ let limit = 50;
1262
+ if (limitParam !== null) {
1263
+ const trimmed = limitParam.trim();
1264
+ if (!/^\d+$/.test(trimmed)) {
1265
+ return payErr("limit must be an integer between 1 and 100", 400);
1266
+ }
1267
+ const parsed = Number.parseInt(trimmed, 10);
1268
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100) {
1269
+ return payErr("limit must be an integer between 1 and 100", 400);
1270
+ }
1271
+ limit = parsed;
1272
+ }
1273
+ let intents = [...state.boltIntents.values()];
1274
+ if (boltConnectionId) {
1275
+ intents = intents.filter((i) => i.boltConnectionId === boltConnectionId);
1276
+ }
1277
+ if (requestedStatuses && requestedStatuses.length > 0) {
1278
+ const storedWanted = new Set(storedStatusesForFilter(requestedStatuses));
1279
+ intents = intents.filter((i) => storedWanted.has(i.status));
1280
+ }
1281
+ let records = intents.map((i) => toBoltIntentRecord(i, now3));
1282
+ if (requestedStatuses && requestedStatuses.length > 0) {
1283
+ const wanted = new Set(requestedStatuses);
1284
+ records = records.filter((r) => wanted.has(r.status));
1285
+ }
1286
+ records = records.slice(0, limit);
1287
+ return json({ intents: records });
1288
+ }
1289
+ if (intentId && request.method === "DELETE") {
1290
+ const intent = findBoltIntent(intentId);
1291
+ if (!intent) return payErr("Bolt intent not found", 404, "NOT_FOUND");
1292
+ const derived = deriveBoltIntentStatus(intent.status, intent.expiresAt);
1293
+ if (derived !== "PENDING") {
1294
+ return payErr(`Bolt intent is ${derived}, only PENDING intents can be canceled`, 409);
1295
+ }
1296
+ intent.status = "CANCELED";
1297
+ intent.settledAt = /* @__PURE__ */ new Date();
1298
+ return json({ success: true });
1299
+ }
1300
+ }
1301
+ const chargesMatch = pathname.match(/^\/api\/store\/beam\/([^/]+)\/charges$/);
1302
+ if (chargesMatch && request.method === "GET") {
1303
+ const sourceId = url.searchParams.get("sourceId");
1304
+ if (!sourceId) {
1305
+ return payErr("Missing sourceId parameter", 400);
1306
+ }
1307
+ const charges = state.boltCharges.filter((c) => c.sourceId === sourceId);
1308
+ return json({ charges });
1309
+ }
1021
1310
  const beamProductsMatch = pathname.match(
1022
1311
  /^\/api\/store\/beam\/([^/]+)\/payment-links(?:\/([^/]+))?$/
1023
1312
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stardeck-customer-apps/testing",
3
- "version": "0.7.1",
3
+ "version": "0.9.0",
4
4
  "description": "Vitest test harness for Stardeck customer apps — in-process Postgres (PGlite) plus a control-plane simulator so the real Stardeck SDKs run unmodified in tests",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -102,7 +102,7 @@
102
102
  "@types/node": "^24.10.1",
103
103
  "kysely": "^0.27.0",
104
104
  "kysely-neon": "^2.0.0",
105
- "next": "^16.2.12",
105
+ "next": "^16.3.0",
106
106
  "tsup": "^8.0.0",
107
107
  "typescript": "^5.0.0",
108
108
  "typescript-eslint": "^8.0.0",