@ibiliaze/stringman 3.6.0 → 3.8.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.d.ts +22 -5
- package/dist/index.js +66 -3
- package/dist/seat.d.ts +9 -0
- package/dist/seat.js +13 -0
- package/dist/ticket.d.ts +17 -0
- package/dist/ticket.js +78 -0
- package/package.json +6 -2
- package/src/index.ts +49 -10
- package/src/seat.ts +10 -0
- package/src/ticket.ts +83 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './seat';
|
|
1
2
|
/**
|
|
2
3
|
* Clean up extra whitespace in a string.
|
|
3
4
|
*
|
|
@@ -138,9 +139,25 @@ export declare const isVideoUrl: (url: string) => boolean;
|
|
|
138
139
|
* @throws Error if no endpoint returns a valid IP.
|
|
139
140
|
*/
|
|
140
141
|
export declare const getPublicIP: () => Promise<string>;
|
|
141
|
-
export declare const getTicketId: ({ fixtureId, sectionName, seatName, }: {
|
|
142
|
-
fixtureId: string;
|
|
143
|
-
sectionName: string;
|
|
144
|
-
seatName: string;
|
|
145
|
-
}) => string;
|
|
146
142
|
export declare const invalidPw: (password: string, passwordLength?: number) => string | void;
|
|
143
|
+
export declare const b36: (n: number) => string;
|
|
144
|
+
export declare const luhn36: (s: string) => string;
|
|
145
|
+
export declare const ticket: {
|
|
146
|
+
getTicketId: ({ fixtureId, sectionName, seatName, }: {
|
|
147
|
+
fixtureId: string;
|
|
148
|
+
sectionName: string;
|
|
149
|
+
seatName: string;
|
|
150
|
+
}) => string;
|
|
151
|
+
buildTicketCode: (args: {
|
|
152
|
+
venueCode: string;
|
|
153
|
+
fixtureLocalIso: string;
|
|
154
|
+
sectionId: string;
|
|
155
|
+
row: number;
|
|
156
|
+
col: number;
|
|
157
|
+
rndLen?: number;
|
|
158
|
+
}) => string;
|
|
159
|
+
validateTicketCode: (code: string) => boolean;
|
|
160
|
+
rndBlock: (len?: number) => string;
|
|
161
|
+
fixtureBlock: (fixtureLocalIso: string) => string;
|
|
162
|
+
seatBlock: (sectionId: string, row: number, col: number, len?: number) => string;
|
|
163
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
2
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
17
|
+
exports.ticket = exports.luhn36 = exports.b36 = exports.invalidPw = exports.getPublicIP = exports.isVideoUrl = exports.getRandomString = exports.extractImageSrcs = exports.select = exports.query = exports.getCloudinaryPublicId = exports.dp = exports.megaTrim = exports.superTrim = void 0;
|
|
18
|
+
__exportStar(require("./seat"), exports);
|
|
19
|
+
const ticket_1 = require("./ticket");
|
|
4
20
|
/**
|
|
5
21
|
* Clean up extra whitespace in a string.
|
|
6
22
|
*
|
|
@@ -226,8 +242,6 @@ const getPublicIP = async () => {
|
|
|
226
242
|
throw new Error('Public IP unavailable');
|
|
227
243
|
};
|
|
228
244
|
exports.getPublicIP = getPublicIP;
|
|
229
|
-
const getTicketId = ({ fixtureId, sectionName, seatName, }) => `${sectionName}-${seatName}-${fixtureId}`;
|
|
230
|
-
exports.getTicketId = getTicketId;
|
|
231
245
|
const invalidPw = (password, passwordLength = 8) => {
|
|
232
246
|
try {
|
|
233
247
|
if (password.length < passwordLength)
|
|
@@ -246,3 +260,52 @@ const invalidPw = (password, passwordLength = 8) => {
|
|
|
246
260
|
}
|
|
247
261
|
};
|
|
248
262
|
exports.invalidPw = invalidPw;
|
|
263
|
+
const BASE36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
264
|
+
const b36 = (n) => {
|
|
265
|
+
try {
|
|
266
|
+
if (n === 0)
|
|
267
|
+
return '0';
|
|
268
|
+
let s = '';
|
|
269
|
+
let x = Math.floor(Math.abs(n));
|
|
270
|
+
while (x > 0) {
|
|
271
|
+
s = BASE36[x % 36] + s;
|
|
272
|
+
x = Math.floor(x / 36);
|
|
273
|
+
}
|
|
274
|
+
return s;
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
return '';
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
exports.b36 = b36;
|
|
281
|
+
const luhn36 = (s) => {
|
|
282
|
+
try {
|
|
283
|
+
let sum = 0;
|
|
284
|
+
let alt = false;
|
|
285
|
+
for (let i = s.length - 1; i >= 0; i--) {
|
|
286
|
+
let v = BASE36.indexOf(s[i].toUpperCase());
|
|
287
|
+
if (v < 0)
|
|
288
|
+
continue;
|
|
289
|
+
if (alt) {
|
|
290
|
+
v *= 2;
|
|
291
|
+
v = Math.floor(v / 36) + (v % 36);
|
|
292
|
+
}
|
|
293
|
+
sum += v;
|
|
294
|
+
alt = !alt;
|
|
295
|
+
}
|
|
296
|
+
const check = (36 - (sum % 36)) % 36;
|
|
297
|
+
return BASE36[check];
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
return '';
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
exports.luhn36 = luhn36;
|
|
304
|
+
exports.ticket = {
|
|
305
|
+
getTicketId: ticket_1.getTicketId,
|
|
306
|
+
buildTicketCode: ticket_1.buildTicketCode,
|
|
307
|
+
validateTicketCode: ticket_1.validateTicketCode,
|
|
308
|
+
rndBlock: ticket_1.rndBlock,
|
|
309
|
+
fixtureBlock: ticket_1.fixtureBlock,
|
|
310
|
+
seatBlock: ticket_1.seatBlock,
|
|
311
|
+
};
|
package/dist/seat.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const getSeatRowFromName: (seatName: string) => string;
|
|
2
|
+
export declare const getSeatColFromName: (seatName: string) => string;
|
|
3
|
+
export declare const seatMapKey: (row: number, col: number) => string;
|
|
4
|
+
export declare const getItemId: (fixtureId: string, sectionId: string, seatId: string) => string;
|
|
5
|
+
export declare const getFromItemId: (itemId: string) => {
|
|
6
|
+
fixtureId: string;
|
|
7
|
+
sectionId: string;
|
|
8
|
+
seatId: string;
|
|
9
|
+
};
|
package/dist/seat.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getFromItemId = exports.getItemId = exports.seatMapKey = exports.getSeatColFromName = exports.getSeatRowFromName = void 0;
|
|
4
|
+
const getSeatRowFromName = (seatName) => seatName?.split(':')?.[0];
|
|
5
|
+
exports.getSeatRowFromName = getSeatRowFromName;
|
|
6
|
+
const getSeatColFromName = (seatName) => seatName?.split(':')?.[1];
|
|
7
|
+
exports.getSeatColFromName = getSeatColFromName;
|
|
8
|
+
const seatMapKey = (row, col) => `${row}:${col}`;
|
|
9
|
+
exports.seatMapKey = seatMapKey;
|
|
10
|
+
const getItemId = (fixtureId, sectionId, seatId) => JSON.stringify({ fixtureId, sectionId, seatId });
|
|
11
|
+
exports.getItemId = getItemId;
|
|
12
|
+
const getFromItemId = (itemId) => JSON.parse(itemId);
|
|
13
|
+
exports.getFromItemId = getFromItemId;
|
package/dist/ticket.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const seatBlock: (sectionId: string, row: number, col: number, len?: number) => string;
|
|
2
|
+
export declare const fixtureBlock: (fixtureLocalIso: string) => string;
|
|
3
|
+
export declare const rndBlock: (len?: number) => string;
|
|
4
|
+
export declare const buildTicketCode: (args: {
|
|
5
|
+
venueCode: string;
|
|
6
|
+
fixtureLocalIso: string;
|
|
7
|
+
sectionId: string;
|
|
8
|
+
row: number;
|
|
9
|
+
col: number;
|
|
10
|
+
rndLen?: number;
|
|
11
|
+
}) => string;
|
|
12
|
+
export declare const validateTicketCode: (code: string) => boolean;
|
|
13
|
+
export declare const getTicketId: ({ fixtureId, sectionName, seatName, }: {
|
|
14
|
+
fixtureId: string;
|
|
15
|
+
sectionName: string;
|
|
16
|
+
seatName: string;
|
|
17
|
+
}) => string;
|
package/dist/ticket.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTicketId = exports.validateTicketCode = exports.buildTicketCode = exports.rndBlock = exports.fixtureBlock = exports.seatBlock = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const _1 = require(".");
|
|
6
|
+
const seatBlock = (sectionId, row, col, len = 3) => {
|
|
7
|
+
try {
|
|
8
|
+
const h = (0, node_crypto_1.createHash)('sha1').update(`${sectionId}:${row}:${col}`).digest(); // 20 bytes
|
|
9
|
+
// take first 2 bytes -> 0..65535
|
|
10
|
+
const n = (h[0] << 8) | h[1];
|
|
11
|
+
const code = (0, _1.b36)(n).padStart(3, '0'); // 3 chars base36
|
|
12
|
+
return code.slice(-len);
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
exports.seatBlock = seatBlock;
|
|
19
|
+
// Fixture datetime block (local TZ string -> YYMMDDhhmm -> base36)
|
|
20
|
+
const fixtureBlock = (fixtureLocalIso) => {
|
|
21
|
+
try {
|
|
22
|
+
// fixtureLocalIso like "2025-10-31T20:00" in venue TZ (no Z)
|
|
23
|
+
const m = fixtureLocalIso.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/);
|
|
24
|
+
if (!m)
|
|
25
|
+
throw new Error('Bad fixtureLocalIso');
|
|
26
|
+
const [_, y, M, d, h, mnt] = m;
|
|
27
|
+
const compact = `${y.slice(2)}${M}${d}${h}${mnt}`; // YYMMDDhhmm as number
|
|
28
|
+
return (0, _1.b36)(Number(compact)).padStart(5, '0'); // ~5-6 chars
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
exports.fixtureBlock = fixtureBlock;
|
|
35
|
+
// Random block
|
|
36
|
+
const rndBlock = (len = 3) => {
|
|
37
|
+
try {
|
|
38
|
+
// 16-bit random -> base36 3-4 chars
|
|
39
|
+
const n = (0, node_crypto_1.randomBytes)(2).readUInt16BE(0);
|
|
40
|
+
return (0, _1.b36)(n).padStart(3, '0').slice(-len);
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
exports.rndBlock = rndBlock;
|
|
47
|
+
const buildTicketCode = (args) => {
|
|
48
|
+
try {
|
|
49
|
+
const VV = args.venueCode.toUpperCase().slice(0, 2);
|
|
50
|
+
const E = (0, exports.fixtureBlock)(args.fixtureLocalIso); // 5-6 chars
|
|
51
|
+
const S = (0, exports.seatBlock)(args.sectionId, args.row, args.col, 3); // 3 chars
|
|
52
|
+
const R = (0, exports.rndBlock)(args.rndLen ?? 3); // 3 chars
|
|
53
|
+
const body = `${VV}${E}${S}${R}`;
|
|
54
|
+
const C = (0, _1.luhn36)(body);
|
|
55
|
+
// Format with dashes for readability (optional)
|
|
56
|
+
return `${VV}-${E}-${S}-${R}-${C}`;
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
exports.buildTicketCode = buildTicketCode;
|
|
63
|
+
const validateTicketCode = (code) => {
|
|
64
|
+
try {
|
|
65
|
+
const clean = code.replace(/-/g, '').toUpperCase();
|
|
66
|
+
if (!/^[0-9A-Z]{12,14}$/.test(clean))
|
|
67
|
+
return false;
|
|
68
|
+
const body = clean.slice(0, -1);
|
|
69
|
+
const check = clean.slice(-1);
|
|
70
|
+
return (0, _1.luhn36)(body) === check;
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
exports.validateTicketCode = validateTicketCode;
|
|
77
|
+
const getTicketId = ({ fixtureId, sectionName, seatName, }) => `${sectionName}-${seatName}-${fixtureId}`;
|
|
78
|
+
exports.getTicketId = getTicketId;
|
package/package.json
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ibiliaze/stringman",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"pub": "npm publish --access public",
|
|
10
|
-
"git": "git add .; git commit -m 'changes'; git tag -a v3.
|
|
10
|
+
"git": "git add .; git commit -m 'changes'; git tag -a v3.8.0 -m 'v3.8.0'; git push origin v3.8.0; git push",
|
|
11
11
|
"push": "npm run build; npm run git; npm run pub"
|
|
12
12
|
},
|
|
13
13
|
"author": "Ibi Hasanli",
|
|
14
14
|
"license": "ISC",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"crypto": "^1.0.1"
|
|
17
|
+
},
|
|
15
18
|
"devDependencies": {
|
|
19
|
+
"@types/node": "^24.7.2",
|
|
16
20
|
"typescript": "^5.0.0"
|
|
17
21
|
}
|
|
18
22
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export * from './seat';
|
|
2
|
+
import { buildTicketCode, fixtureBlock, getTicketId, rndBlock, seatBlock, validateTicketCode } from './ticket';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Clean up extra whitespace in a string.
|
|
3
6
|
*
|
|
@@ -225,16 +228,6 @@ export const getPublicIP = async (): Promise<string> => {
|
|
|
225
228
|
throw new Error('Public IP unavailable');
|
|
226
229
|
};
|
|
227
230
|
|
|
228
|
-
export const getTicketId = ({
|
|
229
|
-
fixtureId,
|
|
230
|
-
sectionName,
|
|
231
|
-
seatName,
|
|
232
|
-
}: {
|
|
233
|
-
fixtureId: string;
|
|
234
|
-
sectionName: string;
|
|
235
|
-
seatName: string;
|
|
236
|
-
}) => `${sectionName}-${seatName}-${fixtureId}`;
|
|
237
|
-
|
|
238
231
|
export const invalidPw = (password: string, passwordLength: number = 8): string | void => {
|
|
239
232
|
try {
|
|
240
233
|
if (password.length < passwordLength) return `Password must be at least ${passwordLength} characters`;
|
|
@@ -246,3 +239,49 @@ export const invalidPw = (password: string, passwordLength: number = 8): string
|
|
|
246
239
|
return 'Failed to validate password';
|
|
247
240
|
}
|
|
248
241
|
};
|
|
242
|
+
|
|
243
|
+
const BASE36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' as const;
|
|
244
|
+
export const b36 = (n: number) => {
|
|
245
|
+
try {
|
|
246
|
+
if (n === 0) return '0';
|
|
247
|
+
let s = '';
|
|
248
|
+
let x = Math.floor(Math.abs(n));
|
|
249
|
+
while (x > 0) {
|
|
250
|
+
s = BASE36[x % 36] + s;
|
|
251
|
+
x = Math.floor(x / 36);
|
|
252
|
+
}
|
|
253
|
+
return s;
|
|
254
|
+
} catch (e) {
|
|
255
|
+
return '';
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export const luhn36 = (s: string): string => {
|
|
260
|
+
try {
|
|
261
|
+
let sum = 0;
|
|
262
|
+
let alt = false;
|
|
263
|
+
for (let i = s.length - 1; i >= 0; i--) {
|
|
264
|
+
let v = BASE36.indexOf(s[i].toUpperCase());
|
|
265
|
+
if (v < 0) continue;
|
|
266
|
+
if (alt) {
|
|
267
|
+
v *= 2;
|
|
268
|
+
v = Math.floor(v / 36) + (v % 36);
|
|
269
|
+
}
|
|
270
|
+
sum += v;
|
|
271
|
+
alt = !alt;
|
|
272
|
+
}
|
|
273
|
+
const check = (36 - (sum % 36)) % 36;
|
|
274
|
+
return BASE36[check];
|
|
275
|
+
} catch (e) {
|
|
276
|
+
return '';
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export const ticket = {
|
|
281
|
+
getTicketId,
|
|
282
|
+
buildTicketCode,
|
|
283
|
+
validateTicketCode,
|
|
284
|
+
rndBlock,
|
|
285
|
+
fixtureBlock,
|
|
286
|
+
seatBlock,
|
|
287
|
+
};
|
package/src/seat.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const getSeatRowFromName = (seatName: string) => seatName?.split(':')?.[0];
|
|
2
|
+
export const getSeatColFromName = (seatName: string) => seatName?.split(':')?.[1];
|
|
3
|
+
|
|
4
|
+
export const seatMapKey = (row: number, col: number) => `${row}:${col}`;
|
|
5
|
+
|
|
6
|
+
export const getItemId = (fixtureId: string, sectionId: string, seatId: string) =>
|
|
7
|
+
JSON.stringify({ fixtureId, sectionId, seatId });
|
|
8
|
+
|
|
9
|
+
export const getFromItemId = (itemId: string): { fixtureId: string; sectionId: string; seatId: string } =>
|
|
10
|
+
JSON.parse(itemId);
|
package/src/ticket.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { b36, luhn36 } from '.';
|
|
3
|
+
|
|
4
|
+
export const seatBlock = (sectionId: string, row: number, col: number, len = 3) => {
|
|
5
|
+
try {
|
|
6
|
+
const h = createHash('sha1').update(`${sectionId}:${row}:${col}`).digest(); // 20 bytes
|
|
7
|
+
// take first 2 bytes -> 0..65535
|
|
8
|
+
const n = (h[0] << 8) | h[1];
|
|
9
|
+
const code = b36(n).padStart(3, '0'); // 3 chars base36
|
|
10
|
+
return code.slice(-len);
|
|
11
|
+
} catch (e) {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Fixture datetime block (local TZ string -> YYMMDDhhmm -> base36)
|
|
17
|
+
export const fixtureBlock = (fixtureLocalIso: string) => {
|
|
18
|
+
try {
|
|
19
|
+
// fixtureLocalIso like "2025-10-31T20:00" in venue TZ (no Z)
|
|
20
|
+
const m = fixtureLocalIso.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/);
|
|
21
|
+
if (!m) throw new Error('Bad fixtureLocalIso');
|
|
22
|
+
const [_, y, M, d, h, mnt] = m;
|
|
23
|
+
const compact = `${y.slice(2)}${M}${d}${h}${mnt}`; // YYMMDDhhmm as number
|
|
24
|
+
return b36(Number(compact)).padStart(5, '0'); // ~5-6 chars
|
|
25
|
+
} catch (e) {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Random block
|
|
31
|
+
export const rndBlock = (len = 3) => {
|
|
32
|
+
try {
|
|
33
|
+
// 16-bit random -> base36 3-4 chars
|
|
34
|
+
const n = randomBytes(2).readUInt16BE(0);
|
|
35
|
+
return b36(n).padStart(3, '0').slice(-len);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const buildTicketCode = (args: {
|
|
42
|
+
venueCode: string; // "SB"
|
|
43
|
+
fixtureLocalIso: string; // "2025-10-31T20:00" in venue TZ
|
|
44
|
+
sectionId: string;
|
|
45
|
+
row: number;
|
|
46
|
+
col: number;
|
|
47
|
+
rndLen?: number; // 3 or 4
|
|
48
|
+
}) => {
|
|
49
|
+
try {
|
|
50
|
+
const VV = args.venueCode.toUpperCase().slice(0, 2);
|
|
51
|
+
const E = fixtureBlock(args.fixtureLocalIso); // 5-6 chars
|
|
52
|
+
const S = seatBlock(args.sectionId, args.row, args.col, 3); // 3 chars
|
|
53
|
+
const R = rndBlock(args.rndLen ?? 3); // 3 chars
|
|
54
|
+
const body = `${VV}${E}${S}${R}`;
|
|
55
|
+
const C = luhn36(body);
|
|
56
|
+
// Format with dashes for readability (optional)
|
|
57
|
+
return `${VV}-${E}-${S}-${R}-${C}`;
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const validateTicketCode = (code: string) => {
|
|
64
|
+
try {
|
|
65
|
+
const clean = code.replace(/-/g, '').toUpperCase();
|
|
66
|
+
if (!/^[0-9A-Z]{12,14}$/.test(clean)) return false;
|
|
67
|
+
const body = clean.slice(0, -1);
|
|
68
|
+
const check = clean.slice(-1);
|
|
69
|
+
return luhn36(body) === check;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const getTicketId = ({
|
|
76
|
+
fixtureId,
|
|
77
|
+
sectionName,
|
|
78
|
+
seatName,
|
|
79
|
+
}: {
|
|
80
|
+
fixtureId: string;
|
|
81
|
+
sectionName: string;
|
|
82
|
+
seatName: string;
|
|
83
|
+
}) => `${sectionName}-${seatName}-${fixtureId}`;
|