playttm 0.0.7 → 0.0.9
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/lib.d.ts +8 -5
- package/dist/lib.js +890 -332
- package/package.json +1 -1
- package/src/lib.ts +944 -394
package/src/lib.ts
CHANGED
|
@@ -258,22 +258,143 @@ export class ThaiTicketMajor {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
|
|
261
262
|
/**
|
|
262
|
-
* Setup declarativeNetRequest session rules
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
* Setup declarativeNetRequest session rules in Chrome Extension to set Referer headers accurately for each endpoint.
|
|
263
|
+
* Setup declarativeNetRequest session rules for Chrome Extension environment.
|
|
264
|
+
* Overrides forbidden headers (Referer, Origin) to bypass TTM and Kasikorn referrer checks.
|
|
265
|
+
* Uses excludedInitiatorDomains to NEVER alter requests made directly by tabs on thaiticketmajor.com.
|
|
266
266
|
*/
|
|
267
|
-
public static async setupExtensionRules(): Promise<void> {
|
|
267
|
+
public static async setupExtensionRules(context?: { k?: string; zone?: string; roundId?: string }): Promise<void> {
|
|
268
268
|
if (typeof chrome === "undefined" || !chrome?.declarativeNetRequest?.updateSessionRules) {
|
|
269
269
|
return;
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
+
const fixedReferer = (context?.k && context?.zone && context?.roundId)
|
|
273
|
+
? `https://booking.thaiticketmajor.com/booking/3m/fixed.php?k=${context.k}&zone=${context.zone}&round=${context.roundId}`
|
|
274
|
+
: "https://booking.thaiticketmajor.com/booking/3m/fixed.php";
|
|
275
|
+
|
|
276
|
+
const festivalReferer = (context?.k && context?.zone && context?.roundId)
|
|
277
|
+
? `https://booking.thaiticketmajor.com/booking/3m/festival.php?k=${context.k}&zone=${context.zone}&round=${context.roundId}`
|
|
278
|
+
: "https://booking.thaiticketmajor.com/booking/3m/festival.php";
|
|
279
|
+
|
|
280
|
+
const enrollReferer = (context?.k && context?.zone && context?.roundId)
|
|
281
|
+
? `https://booking.thaiticketmajor.com/booking/3m/enroll.php?k=${context.k}&zone=${context.zone}&round=${context.roundId}`
|
|
282
|
+
: "https://booking.thaiticketmajor.com/booking/3m/enroll.php";
|
|
283
|
+
|
|
284
|
+
const excludedDomains = ["booking.thaiticketmajor.com", "www.thaiticketmajor.com", "thaiticketmajor.com"];
|
|
285
|
+
|
|
272
286
|
try {
|
|
273
|
-
const ruleIdsToRemove = [
|
|
287
|
+
const ruleIdsToRemove = [
|
|
288
|
+
1001, 1002, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019,
|
|
289
|
+
1020, 1021, 1022, 1023
|
|
290
|
+
];
|
|
274
291
|
await chrome.declarativeNetRequest.updateSessionRules({
|
|
275
292
|
removeRuleIds: ruleIdsToRemove,
|
|
276
293
|
addRules: [
|
|
294
|
+
{
|
|
295
|
+
id: 1020,
|
|
296
|
+
priority: 25,
|
|
297
|
+
action: {
|
|
298
|
+
type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
|
|
299
|
+
requestHeaders: [
|
|
300
|
+
{
|
|
301
|
+
header: "Referer",
|
|
302
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
303
|
+
value: fixedReferer,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
header: "Origin",
|
|
307
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
308
|
+
value: "https://booking.thaiticketmajor.com",
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
condition: {
|
|
313
|
+
urlFilter: "bookingseats.php",
|
|
314
|
+
resourceTypes: [
|
|
315
|
+
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
316
|
+
],
|
|
317
|
+
excludedInitiatorDomains: excludedDomains,
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: 1021,
|
|
322
|
+
priority: 25,
|
|
323
|
+
action: {
|
|
324
|
+
type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
|
|
325
|
+
requestHeaders: [
|
|
326
|
+
{
|
|
327
|
+
header: "Referer",
|
|
328
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
329
|
+
value: fixedReferer,
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
header: "Origin",
|
|
333
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
334
|
+
value: "https://booking.thaiticketmajor.com",
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
},
|
|
338
|
+
condition: {
|
|
339
|
+
urlFilter: "validateseat.php",
|
|
340
|
+
resourceTypes: [
|
|
341
|
+
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
342
|
+
],
|
|
343
|
+
excludedInitiatorDomains: excludedDomains,
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: 1022,
|
|
348
|
+
priority: 25,
|
|
349
|
+
action: {
|
|
350
|
+
type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
|
|
351
|
+
requestHeaders: [
|
|
352
|
+
{
|
|
353
|
+
header: "Referer",
|
|
354
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
355
|
+
value: festivalReferer,
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
header: "Origin",
|
|
359
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
360
|
+
value: "https://booking.thaiticketmajor.com",
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
condition: {
|
|
365
|
+
urlFilter: "bookingfestival.php",
|
|
366
|
+
resourceTypes: [
|
|
367
|
+
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
368
|
+
],
|
|
369
|
+
excludedInitiatorDomains: excludedDomains,
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
id: 1023,
|
|
374
|
+
priority: 25,
|
|
375
|
+
action: {
|
|
376
|
+
type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
|
|
377
|
+
requestHeaders: [
|
|
378
|
+
{
|
|
379
|
+
header: "Referer",
|
|
380
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
381
|
+
value: enrollReferer,
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
header: "Origin",
|
|
385
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
386
|
+
value: "https://booking.thaiticketmajor.com",
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
},
|
|
390
|
+
condition: {
|
|
391
|
+
urlFilter: "enroll_process.php",
|
|
392
|
+
resourceTypes: [
|
|
393
|
+
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
394
|
+
],
|
|
395
|
+
excludedInitiatorDomains: excludedDomains,
|
|
396
|
+
},
|
|
397
|
+
},
|
|
277
398
|
{
|
|
278
399
|
id: 1010,
|
|
279
400
|
priority: 20,
|
|
@@ -285,6 +406,11 @@ export class ThaiTicketMajor {
|
|
|
285
406
|
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
286
407
|
value: "https://booking.thaiticketmajor.com/booking/3m/orderenc_kbankqr.php",
|
|
287
408
|
},
|
|
409
|
+
{
|
|
410
|
+
header: "Origin",
|
|
411
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
412
|
+
value: "https://booking.thaiticketmajor.com",
|
|
413
|
+
},
|
|
288
414
|
],
|
|
289
415
|
},
|
|
290
416
|
condition: {
|
|
@@ -292,6 +418,7 @@ export class ThaiTicketMajor {
|
|
|
292
418
|
resourceTypes: [
|
|
293
419
|
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
294
420
|
],
|
|
421
|
+
excludedInitiatorDomains: excludedDomains,
|
|
295
422
|
},
|
|
296
423
|
},
|
|
297
424
|
{
|
|
@@ -305,6 +432,11 @@ export class ThaiTicketMajor {
|
|
|
305
432
|
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
306
433
|
value: "https://booking.thaiticketmajor.com/booking/3m/orderenc_kbankqr.php",
|
|
307
434
|
},
|
|
435
|
+
{
|
|
436
|
+
header: "Origin",
|
|
437
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
438
|
+
value: "https://booking.thaiticketmajor.com",
|
|
439
|
+
},
|
|
308
440
|
],
|
|
309
441
|
},
|
|
310
442
|
condition: {
|
|
@@ -312,6 +444,7 @@ export class ThaiTicketMajor {
|
|
|
312
444
|
resourceTypes: [
|
|
313
445
|
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
314
446
|
],
|
|
447
|
+
excludedInitiatorDomains: excludedDomains,
|
|
315
448
|
},
|
|
316
449
|
},
|
|
317
450
|
{
|
|
@@ -325,6 +458,11 @@ export class ThaiTicketMajor {
|
|
|
325
458
|
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
326
459
|
value: "https://booking.thaiticketmajor.com/booking/3m/paycfmall.php",
|
|
327
460
|
},
|
|
461
|
+
{
|
|
462
|
+
header: "Origin",
|
|
463
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
464
|
+
value: "https://booking.thaiticketmajor.com",
|
|
465
|
+
},
|
|
328
466
|
],
|
|
329
467
|
},
|
|
330
468
|
condition: {
|
|
@@ -332,6 +470,7 @@ export class ThaiTicketMajor {
|
|
|
332
470
|
resourceTypes: [
|
|
333
471
|
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
334
472
|
],
|
|
473
|
+
excludedInitiatorDomains: excludedDomains,
|
|
335
474
|
},
|
|
336
475
|
},
|
|
337
476
|
{
|
|
@@ -345,6 +484,11 @@ export class ThaiTicketMajor {
|
|
|
345
484
|
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
346
485
|
value: "https://booking.thaiticketmajor.com/booking/3m/paymentall.php",
|
|
347
486
|
},
|
|
487
|
+
{
|
|
488
|
+
header: "Origin",
|
|
489
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
490
|
+
value: "https://booking.thaiticketmajor.com",
|
|
491
|
+
},
|
|
348
492
|
],
|
|
349
493
|
},
|
|
350
494
|
condition: {
|
|
@@ -352,6 +496,7 @@ export class ThaiTicketMajor {
|
|
|
352
496
|
resourceTypes: [
|
|
353
497
|
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
354
498
|
],
|
|
499
|
+
excludedInitiatorDomains: excludedDomains,
|
|
355
500
|
},
|
|
356
501
|
},
|
|
357
502
|
{
|
|
@@ -365,6 +510,11 @@ export class ThaiTicketMajor {
|
|
|
365
510
|
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
366
511
|
value: "https://booking.thaiticketmajor.com/booking/3m/fixed.php",
|
|
367
512
|
},
|
|
513
|
+
{
|
|
514
|
+
header: "Origin",
|
|
515
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
516
|
+
value: "https://booking.thaiticketmajor.com",
|
|
517
|
+
},
|
|
368
518
|
],
|
|
369
519
|
},
|
|
370
520
|
condition: {
|
|
@@ -372,6 +522,7 @@ export class ThaiTicketMajor {
|
|
|
372
522
|
resourceTypes: [
|
|
373
523
|
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
374
524
|
],
|
|
525
|
+
excludedInitiatorDomains: excludedDomains,
|
|
375
526
|
},
|
|
376
527
|
},
|
|
377
528
|
{
|
|
@@ -385,13 +536,19 @@ export class ThaiTicketMajor {
|
|
|
385
536
|
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
386
537
|
value: "https://booking.thaiticketmajor.com/booking/3m/fixed.php",
|
|
387
538
|
},
|
|
539
|
+
{
|
|
540
|
+
header: "Origin",
|
|
541
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
542
|
+
value: "https://booking.thaiticketmajor.com",
|
|
543
|
+
},
|
|
388
544
|
],
|
|
389
545
|
},
|
|
390
546
|
condition: {
|
|
391
|
-
urlFilter: "enroll",
|
|
547
|
+
urlFilter: "enroll.php",
|
|
392
548
|
resourceTypes: [
|
|
393
549
|
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
394
550
|
],
|
|
551
|
+
excludedInitiatorDomains: excludedDomains,
|
|
395
552
|
},
|
|
396
553
|
},
|
|
397
554
|
{
|
|
@@ -405,6 +562,11 @@ export class ThaiTicketMajor {
|
|
|
405
562
|
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
406
563
|
value: "https://booking.thaiticketmajor.com/booking/3m/zones.php",
|
|
407
564
|
},
|
|
565
|
+
{
|
|
566
|
+
header: "Origin",
|
|
567
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
568
|
+
value: "https://booking.thaiticketmajor.com",
|
|
569
|
+
},
|
|
408
570
|
],
|
|
409
571
|
},
|
|
410
572
|
condition: {
|
|
@@ -412,6 +574,7 @@ export class ThaiTicketMajor {
|
|
|
412
574
|
resourceTypes: [
|
|
413
575
|
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
414
576
|
],
|
|
577
|
+
excludedInitiatorDomains: excludedDomains,
|
|
415
578
|
},
|
|
416
579
|
},
|
|
417
580
|
{
|
|
@@ -425,6 +588,11 @@ export class ThaiTicketMajor {
|
|
|
425
588
|
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
426
589
|
value: "https://kpaymentgateway.kasikornbank.com/",
|
|
427
590
|
},
|
|
591
|
+
{
|
|
592
|
+
header: "Origin",
|
|
593
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
594
|
+
value: "https://kpaymentgateway.kasikornbank.com",
|
|
595
|
+
},
|
|
428
596
|
],
|
|
429
597
|
},
|
|
430
598
|
condition: {
|
|
@@ -432,6 +600,7 @@ export class ThaiTicketMajor {
|
|
|
432
600
|
resourceTypes: [
|
|
433
601
|
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
434
602
|
],
|
|
603
|
+
excludedInitiatorDomains: excludedDomains,
|
|
435
604
|
},
|
|
436
605
|
},
|
|
437
606
|
{
|
|
@@ -445,6 +614,11 @@ export class ThaiTicketMajor {
|
|
|
445
614
|
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
446
615
|
value: "https://booking.thaiticketmajor.com/booking/3m/",
|
|
447
616
|
},
|
|
617
|
+
{
|
|
618
|
+
header: "Origin",
|
|
619
|
+
operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
|
|
620
|
+
value: "https://booking.thaiticketmajor.com",
|
|
621
|
+
},
|
|
448
622
|
],
|
|
449
623
|
},
|
|
450
624
|
condition: {
|
|
@@ -452,6 +626,7 @@ export class ThaiTicketMajor {
|
|
|
452
626
|
resourceTypes: [
|
|
453
627
|
chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
|
|
454
628
|
],
|
|
629
|
+
excludedInitiatorDomains: excludedDomains,
|
|
455
630
|
},
|
|
456
631
|
},
|
|
457
632
|
],
|
|
@@ -481,44 +656,64 @@ export class ThaiTicketMajor {
|
|
|
481
656
|
* Step 1: ดึงรอบการแสดงทั้งหมดจากหน้าหลักคอนเสิร์ต
|
|
482
657
|
*/
|
|
483
658
|
public async getRounds(eventUrl: string): Promise<RoundInfo[]> {
|
|
484
|
-
this.emitProgress("GET_ROUNDS", `Fetching rounds from ${eventUrl}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
headers: {
|
|
488
|
-
...this.defaultHeaders,
|
|
489
|
-
referer: "https://www.thaiticketmajor.com/all-event/",
|
|
490
|
-
},
|
|
659
|
+
this.emitProgress("GET_ROUNDS", `Fetching rounds from ${eventUrl}`, {
|
|
660
|
+
url: eventUrl,
|
|
661
|
+
requestPayload: null,
|
|
491
662
|
});
|
|
492
663
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
time
|
|
515
|
-
|
|
516
|
-
query
|
|
517
|
-
|
|
664
|
+
try {
|
|
665
|
+
const response = await this.client.get(eventUrl, {
|
|
666
|
+
headers: {
|
|
667
|
+
...this.defaultHeaders,
|
|
668
|
+
referer: "https://www.thaiticketmajor.com/all-event/",
|
|
669
|
+
},
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
const $ = cheerio.load(response.data);
|
|
673
|
+
const rounds: RoundInfo[] = [];
|
|
674
|
+
|
|
675
|
+
$("a[data-button]").each((_, el) => {
|
|
676
|
+
const link = $(el);
|
|
677
|
+
const href = link.attr("href") ?? "";
|
|
678
|
+
const onclick = link.attr("onclick") ?? "";
|
|
679
|
+
const dataUrl = link.attr("data-url") ?? "";
|
|
680
|
+
const dataHref = link.attr("data-href") ?? "";
|
|
681
|
+
const allLinkAttrs = `${href} ${onclick} ${dataUrl} ${dataHref}`;
|
|
682
|
+
|
|
683
|
+
const queryMatch = allLinkAttrs.match(/query=([0-9a-zA-Z_-]+)/);
|
|
684
|
+
const query = queryMatch ? queryMatch[1] : "";
|
|
685
|
+
const time = link.find(".item-show").text().trim();
|
|
686
|
+
const date = link.closest(".row").find(".date").text().trim();
|
|
687
|
+
const isDisabled = link.is(":disabled") || link.attr("disabled") !== undefined || !query;
|
|
688
|
+
|
|
689
|
+
rounds.push({
|
|
690
|
+
id: query || link.attr("data-button") || "",
|
|
691
|
+
label: `${date} ${time}`.trim(),
|
|
692
|
+
date,
|
|
693
|
+
time,
|
|
694
|
+
url: href,
|
|
695
|
+
query,
|
|
696
|
+
isDisabled,
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
this.emitProgress("GET_ROUNDS_RES", `Found ${rounds.length} rounds`, {
|
|
701
|
+
url: eventUrl,
|
|
702
|
+
requestPayload: null,
|
|
703
|
+
responseStatus: response.status,
|
|
704
|
+
responseData: rounds,
|
|
518
705
|
});
|
|
519
|
-
});
|
|
520
706
|
|
|
521
|
-
|
|
707
|
+
return rounds;
|
|
708
|
+
} catch (err: any) {
|
|
709
|
+
this.emitProgress("GET_ROUNDS_ERR", `Failed to fetch rounds: ${err.message}`, {
|
|
710
|
+
url: eventUrl,
|
|
711
|
+
requestPayload: null,
|
|
712
|
+
responseStatus: err.response?.status,
|
|
713
|
+
responseData: err.response?.data || err.message,
|
|
714
|
+
});
|
|
715
|
+
throw err;
|
|
716
|
+
}
|
|
522
717
|
}
|
|
523
718
|
|
|
524
719
|
/**
|
|
@@ -529,113 +724,180 @@ export class ThaiTicketMajor {
|
|
|
529
724
|
throw new Error("Query ID is missing. The selected round might be disabled or not open for sale yet.");
|
|
530
725
|
}
|
|
531
726
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
);
|
|
727
|
+
const url = `${this.endpoint}/verify_checkcondition.php`;
|
|
728
|
+
const payload = {
|
|
729
|
+
rdagree: "1",
|
|
730
|
+
rdId: "",
|
|
731
|
+
autopopup: "",
|
|
732
|
+
query: query,
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
this.emitProgress("VERIFY_CONDITION", `Verifying condition for query ${query}`, {
|
|
736
|
+
url,
|
|
737
|
+
requestPayload: payload,
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
try {
|
|
741
|
+
const response = await this.client.post(
|
|
742
|
+
url,
|
|
743
|
+
new URLSearchParams(payload),
|
|
744
|
+
{ headers: this.defaultHeaders }
|
|
745
|
+
);
|
|
544
746
|
|
|
545
|
-
|
|
747
|
+
this.emitProgress("VERIFY_CONDITION_RES", `Condition check: ${response.data?.result ? "PASS" : "FAIL"}`, {
|
|
748
|
+
url,
|
|
749
|
+
requestPayload: payload,
|
|
750
|
+
responseStatus: response.status,
|
|
751
|
+
responseData: response.data,
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
return response.data;
|
|
755
|
+
} catch (err: any) {
|
|
756
|
+
this.emitProgress("VERIFY_CONDITION_ERR", `Condition check error: ${err.message}`, {
|
|
757
|
+
url,
|
|
758
|
+
requestPayload: payload,
|
|
759
|
+
responseStatus: err.response?.status,
|
|
760
|
+
responseData: err.response?.data || err.message,
|
|
761
|
+
});
|
|
762
|
+
throw err;
|
|
763
|
+
}
|
|
546
764
|
}
|
|
547
765
|
|
|
548
766
|
/**
|
|
549
767
|
* Step 3: ดึงข้อมูลโซน, Token (k, tk) และรอบการแสดง
|
|
550
768
|
*/
|
|
551
769
|
public async getZones(query: string): Promise<{ k: string; tk: string; query: string; rounds: { id: string; label: string }[]; rawInputs: Record<string, string> }> {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
770
|
+
const url = `${this.endpoint}/zones.php?query=${query}`;
|
|
771
|
+
this.emitProgress("GET_ZONES", `Fetching zone data and tokens for query ${query}`, {
|
|
772
|
+
url,
|
|
773
|
+
requestPayload: { query },
|
|
556
774
|
});
|
|
557
775
|
|
|
558
|
-
|
|
559
|
-
|
|
776
|
+
try {
|
|
777
|
+
const response = await this.client.get(url, {
|
|
778
|
+
headers: this.defaultHeaders,
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
const $ = cheerio.load(response.data);
|
|
782
|
+
const rawInputs: Record<string, string> = {};
|
|
560
783
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
784
|
+
$("#frm input[type='hidden']").each((_, el) => {
|
|
785
|
+
const name = $(el).attr("name");
|
|
786
|
+
const value = $(el).attr("value") ?? "";
|
|
787
|
+
if (name) {
|
|
788
|
+
rawInputs[name] = value;
|
|
789
|
+
}
|
|
790
|
+
});
|
|
568
791
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
792
|
+
const rounds = $("#rdId option")
|
|
793
|
+
.filter((_, el) => !!$(el).attr("value"))
|
|
794
|
+
.map((_, el) => {
|
|
795
|
+
const opt = $(el);
|
|
796
|
+
return {
|
|
797
|
+
id: opt.attr("value") ?? "",
|
|
798
|
+
label: opt.text().trim(),
|
|
799
|
+
};
|
|
800
|
+
})
|
|
801
|
+
.get();
|
|
802
|
+
|
|
803
|
+
const result = {
|
|
804
|
+
k: rawInputs["k"] || "",
|
|
805
|
+
tk: rawInputs["tk"] || "",
|
|
806
|
+
query,
|
|
807
|
+
rounds,
|
|
808
|
+
rawInputs,
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
this.emitProgress("GET_ZONES_RES", `Extracted tokens k=${result.k ? result.k.slice(0, 10) + '...' : 'none'} and ${rounds.length} rounds`, {
|
|
812
|
+
url,
|
|
813
|
+
requestPayload: { query },
|
|
814
|
+
responseStatus: response.status,
|
|
815
|
+
responseData: result,
|
|
816
|
+
});
|
|
579
817
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
818
|
+
return result;
|
|
819
|
+
} catch (err: any) {
|
|
820
|
+
this.emitProgress("GET_ZONES_ERR", `Failed to get zones: ${err.message}`, {
|
|
821
|
+
url,
|
|
822
|
+
requestPayload: { query },
|
|
823
|
+
responseStatus: err.response?.status,
|
|
824
|
+
responseData: err.response?.data || err.message,
|
|
825
|
+
});
|
|
826
|
+
throw err;
|
|
827
|
+
}
|
|
587
828
|
}
|
|
588
829
|
|
|
589
830
|
/**
|
|
590
831
|
* Step 4: ตรวจสอบสถานะที่นั่งว่างของแต่ละโซน
|
|
591
832
|
*/
|
|
592
833
|
public async getZoneAvailability(roundId: string, tk: string, k: string, query: string): Promise<ZoneItem[]> {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
...this.defaultHeaders,
|
|
598
|
-
referer: `${this.endpoint}/zones.php?query=${query}`,
|
|
599
|
-
},
|
|
834
|
+
const url = `${this.endpoint}/zonesavail.php?round=${roundId}&tk=${tk}`;
|
|
835
|
+
this.emitProgress("GET_ZONE_AVAIL", `Checking zone availability for round ${roundId}`, {
|
|
836
|
+
url,
|
|
837
|
+
requestPayload: { roundId, tk, query },
|
|
600
838
|
});
|
|
601
839
|
|
|
602
|
-
|
|
603
|
-
|
|
840
|
+
try {
|
|
841
|
+
const response = await this.client.get(url, {
|
|
842
|
+
headers: {
|
|
843
|
+
...this.defaultHeaders,
|
|
844
|
+
referer: `${this.endpoint}/zones.php?query=${query}`,
|
|
845
|
+
},
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
const $ = cheerio.load(response.data);
|
|
849
|
+
const zones: ZoneItem[] = [];
|
|
604
850
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
851
|
+
$(".table tbody tr").each((_, el) => {
|
|
852
|
+
const row = $(el);
|
|
853
|
+
const zoneId = row.find("td:nth-child(1) a").attr("id") ?? "";
|
|
854
|
+
const onclick = row.attr("onclick") ?? "";
|
|
855
|
+
const match = onclick.match(/gonextstep\('([^']+)','([^']+)'/);
|
|
610
856
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
857
|
+
const page = (match?.[1] ?? "") as "fixed.php" | "festival.php" | string;
|
|
858
|
+
const zoneValue = match?.[2] ?? "";
|
|
859
|
+
const statusText = row.find("td:nth-child(2) a").text().trim();
|
|
614
860
|
|
|
615
|
-
|
|
616
|
-
|
|
861
|
+
let available = 0;
|
|
862
|
+
let isAvailable = false;
|
|
617
863
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
isAvailable = true;
|
|
621
|
-
} else {
|
|
622
|
-
const num = Number(statusText);
|
|
623
|
-
if (!Number.isNaN(num) && num > 0) {
|
|
624
|
-
available = num;
|
|
864
|
+
if (statusText.toLowerCase() === "available") {
|
|
865
|
+
available = 1;
|
|
625
866
|
isAvailable = true;
|
|
867
|
+
} else {
|
|
868
|
+
const num = Number(statusText);
|
|
869
|
+
if (!Number.isNaN(num) && num > 0) {
|
|
870
|
+
available = num;
|
|
871
|
+
isAvailable = true;
|
|
872
|
+
}
|
|
626
873
|
}
|
|
627
|
-
}
|
|
628
874
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
875
|
+
zones.push({
|
|
876
|
+
id: zoneId,
|
|
877
|
+
value: zoneValue,
|
|
878
|
+
available,
|
|
879
|
+
page,
|
|
880
|
+
isAvailable,
|
|
881
|
+
});
|
|
635
882
|
});
|
|
636
|
-
});
|
|
637
883
|
|
|
638
|
-
|
|
884
|
+
this.emitProgress("GET_ZONE_AVAIL_RES", `Found ${zones.length} zones (${zones.filter(z => z.isAvailable).length} available)`, {
|
|
885
|
+
url,
|
|
886
|
+
requestPayload: { roundId, tk, query },
|
|
887
|
+
responseStatus: response.status,
|
|
888
|
+
responseData: zones,
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
return zones;
|
|
892
|
+
} catch (err: any) {
|
|
893
|
+
this.emitProgress("GET_ZONE_AVAIL_ERR", `Failed to check zone availability: ${err.message}`, {
|
|
894
|
+
url,
|
|
895
|
+
requestPayload: { roundId, tk, query },
|
|
896
|
+
responseStatus: err.response?.status,
|
|
897
|
+
responseData: err.response?.data || err.message,
|
|
898
|
+
});
|
|
899
|
+
throw err;
|
|
900
|
+
}
|
|
639
901
|
}
|
|
640
902
|
|
|
641
903
|
// --- Flow บัตรนั่ง (Fixed Seats) ---
|
|
@@ -644,33 +906,58 @@ export class ThaiTicketMajor {
|
|
|
644
906
|
* Step 5 (Fixed): ดึงผังที่นั่งว่างและ form payload
|
|
645
907
|
*/
|
|
646
908
|
public async getFixedSeats(k: string, zone: string, roundId: string): Promise<{ seats: SeatItem[]; form: Record<string, string> }> {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
909
|
+
const url = `${this.endpoint}/fixed.php?k=${k}&zone=${zone}&round=${roundId}`;
|
|
910
|
+
this.emitProgress("GET_FIXED_SEATS", `Fetching seat layout for zone ${zone}`, {
|
|
911
|
+
url,
|
|
912
|
+
requestPayload: { k, zone, roundId },
|
|
651
913
|
});
|
|
652
914
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
}
|
|
671
|
-
|
|
915
|
+
try {
|
|
916
|
+
const response = await this.client.get(url, {
|
|
917
|
+
headers: this.defaultHeaders,
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
const $ = cheerio.load(response.data);
|
|
921
|
+
const seats: SeatItem[] = $("#tableseats .seatuncheck")
|
|
922
|
+
.map((_, el) => {
|
|
923
|
+
const seat = $(el);
|
|
924
|
+
return {
|
|
925
|
+
id: seat.attr("id") ?? "",
|
|
926
|
+
seat: seat.attr("data-seat") ?? "",
|
|
927
|
+
seatk: seat.attr("data-seatk") ?? "",
|
|
928
|
+
};
|
|
929
|
+
})
|
|
930
|
+
.get();
|
|
931
|
+
|
|
932
|
+
const form: Record<string, string> = {};
|
|
933
|
+
$("#frmPayment input").each((_, el) => {
|
|
934
|
+
const name = $(el).attr("name");
|
|
935
|
+
if (name) {
|
|
936
|
+
form[name] = $(el).attr("value") ?? "";
|
|
937
|
+
}
|
|
938
|
+
});
|
|
672
939
|
|
|
673
|
-
|
|
940
|
+
this.emitProgress("GET_FIXED_SEATS_RES", `Found ${seats.length} available seats in zone ${zone}`, {
|
|
941
|
+
url,
|
|
942
|
+
requestPayload: { k, zone, roundId },
|
|
943
|
+
responseStatus: response.status,
|
|
944
|
+
responseData: {
|
|
945
|
+
seatsCount: seats.length,
|
|
946
|
+
seatsSample: seats.slice(0, 10),
|
|
947
|
+
form,
|
|
948
|
+
},
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
return { seats, form };
|
|
952
|
+
} catch (err: any) {
|
|
953
|
+
this.emitProgress("GET_FIXED_SEATS_ERR", `Failed to get fixed seats: ${err.message}`, {
|
|
954
|
+
url,
|
|
955
|
+
requestPayload: { k, zone, roundId },
|
|
956
|
+
responseStatus: err.response?.status,
|
|
957
|
+
responseData: err.response?.data || err.message,
|
|
958
|
+
});
|
|
959
|
+
throw err;
|
|
960
|
+
}
|
|
674
961
|
}
|
|
675
962
|
|
|
676
963
|
/**
|
|
@@ -732,22 +1019,42 @@ export class ThaiTicketMajor {
|
|
|
732
1019
|
formData.append("seat", seatNo);
|
|
733
1020
|
formData.append("book_type", bookType);
|
|
734
1021
|
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1022
|
+
const url = `${this.endpoint}/validateseat.php?k=${k}&zw=${zone}`;
|
|
1023
|
+
const reqPayloadObj = Object.fromEntries(formData.entries());
|
|
1024
|
+
|
|
1025
|
+
try {
|
|
1026
|
+
const response = await this.client.post(
|
|
1027
|
+
url,
|
|
1028
|
+
formData.toString(),
|
|
1029
|
+
{
|
|
1030
|
+
headers: {
|
|
1031
|
+
...this.defaultHeaders,
|
|
1032
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
1033
|
+
"x-requested-with": "XMLHttpRequest",
|
|
1034
|
+
referer: `${this.endpoint}/fixed.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
|
|
1035
|
+
},
|
|
1036
|
+
}
|
|
1037
|
+
);
|
|
1038
|
+
|
|
1039
|
+
lastResponse = response.data;
|
|
1040
|
+
this.emitProgress("VALIDATE_FIXED_SEATS_RES", `Seat validated (${currentSeat.seat}): ${lastResponse?.result ? "SUCCESS" : "FAIL"}`, {
|
|
1041
|
+
url,
|
|
1042
|
+
requestPayload: reqPayloadObj,
|
|
1043
|
+
responseStatus: response.status,
|
|
1044
|
+
responseData: lastResponse,
|
|
1045
|
+
});
|
|
747
1046
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1047
|
+
if (!lastResponse.result) {
|
|
1048
|
+
throw new Error(`Validate seat failed (${currentSeat.seat}): ${lastResponse.message || "Unknown error"}`);
|
|
1049
|
+
}
|
|
1050
|
+
} catch (err: any) {
|
|
1051
|
+
this.emitProgress("VALIDATE_FIXED_SEATS_ERR", `Validate seat failed (${currentSeat.seat}): ${err.message}`, {
|
|
1052
|
+
url,
|
|
1053
|
+
requestPayload: reqPayloadObj,
|
|
1054
|
+
responseStatus: err.response?.status,
|
|
1055
|
+
responseData: err.response?.data || err.message,
|
|
1056
|
+
});
|
|
1057
|
+
throw err;
|
|
751
1058
|
}
|
|
752
1059
|
}
|
|
753
1060
|
|
|
@@ -800,25 +1107,45 @@ export class ThaiTicketMajor {
|
|
|
800
1107
|
bodyParams.set("inclvat", "");
|
|
801
1108
|
bodyParams.set("seatklist", seatklistvalue);
|
|
802
1109
|
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
bodyParams.toString(),
|
|
806
|
-
{
|
|
807
|
-
headers: {
|
|
808
|
-
...this.defaultHeaders,
|
|
809
|
-
accept: "application/json, text/javascript, */*; q=0.01",
|
|
810
|
-
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
811
|
-
"x-requested-with": "XMLHttpRequest",
|
|
812
|
-
referer: `${this.endpoint}/fixed.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
|
|
813
|
-
origin: "https://booking.thaiticketmajor.com",
|
|
814
|
-
},
|
|
815
|
-
}
|
|
816
|
-
);
|
|
1110
|
+
const url = `${this.endpoint}/bookingseats.php?k=${k}`;
|
|
1111
|
+
const reqPayloadObj = Object.fromEntries(bodyParams.entries());
|
|
817
1112
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1113
|
+
try {
|
|
1114
|
+
const response = await this.client.post(
|
|
1115
|
+
url,
|
|
1116
|
+
bodyParams.toString(),
|
|
1117
|
+
{
|
|
1118
|
+
headers: {
|
|
1119
|
+
...this.defaultHeaders,
|
|
1120
|
+
accept: "application/json, text/javascript, */*; q=0.01",
|
|
1121
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
1122
|
+
"x-requested-with": "XMLHttpRequest",
|
|
1123
|
+
referer: `${this.endpoint}/fixed.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
|
|
1124
|
+
origin: "https://booking.thaiticketmajor.com",
|
|
1125
|
+
},
|
|
1126
|
+
}
|
|
1127
|
+
);
|
|
1128
|
+
|
|
1129
|
+
this.emitProgress("BOOK_FIXED_SEATS_RES", `Book fixed seats completed`, {
|
|
1130
|
+
url,
|
|
1131
|
+
requestPayload: reqPayloadObj,
|
|
1132
|
+
responseStatus: response.status,
|
|
1133
|
+
responseData: response.data,
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
return {
|
|
1137
|
+
data: response.data,
|
|
1138
|
+
nextPayload: bodyParams,
|
|
1139
|
+
};
|
|
1140
|
+
} catch (err: any) {
|
|
1141
|
+
this.emitProgress("BOOK_FIXED_SEATS_ERR", `Booking seats error: ${err.message}`, {
|
|
1142
|
+
url,
|
|
1143
|
+
requestPayload: reqPayloadObj,
|
|
1144
|
+
responseStatus: err.response?.status,
|
|
1145
|
+
responseData: err.response?.data || err.message,
|
|
1146
|
+
});
|
|
1147
|
+
throw err;
|
|
1148
|
+
}
|
|
822
1149
|
}
|
|
823
1150
|
|
|
824
1151
|
// --- Flow บัตรยืน (Festival / Standing) ---
|
|
@@ -827,26 +1154,47 @@ export class ThaiTicketMajor {
|
|
|
827
1154
|
* Step 5 (Festival): ดึงข้อมูลหน้าบัตรยืน
|
|
828
1155
|
*/
|
|
829
1156
|
public async getFestival(k: string, tk: string, query: string, zone: string, roundId: string): Promise<Record<string, string>> {
|
|
830
|
-
this.
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
...this.defaultHeaders,
|
|
835
|
-
referer: `${this.endpoint}/zones.php?rdId=${roundId}&k=${k}&tk=${tk}&query=${query}`,
|
|
836
|
-
},
|
|
1157
|
+
const url = `${this.endpoint}/festival.php?k=${k}&zone=${zone}&round=${roundId}`;
|
|
1158
|
+
this.emitProgress("GET_FESTIVAL", `Fetching festival zone ${zone}`, {
|
|
1159
|
+
url,
|
|
1160
|
+
requestPayload: { k, tk, query, zone, roundId },
|
|
837
1161
|
});
|
|
838
1162
|
|
|
839
|
-
|
|
840
|
-
|
|
1163
|
+
try {
|
|
1164
|
+
const response = await this.client.get(url, {
|
|
1165
|
+
headers: {
|
|
1166
|
+
...this.defaultHeaders,
|
|
1167
|
+
referer: `${this.endpoint}/zones.php?rdId=${roundId}&k=${k}&tk=${tk}&query=${query}`,
|
|
1168
|
+
},
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
const $ = cheerio.load(response.data);
|
|
1172
|
+
const formData: Record<string, string> = {};
|
|
841
1173
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1174
|
+
$("#frm input").each((_, el) => {
|
|
1175
|
+
const name = $(el).attr("name");
|
|
1176
|
+
if (name) {
|
|
1177
|
+
formData[name] = $(el).attr("value") ?? "";
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
848
1180
|
|
|
849
|
-
|
|
1181
|
+
this.emitProgress("GET_FESTIVAL_RES", `Loaded festival form (${Object.keys(formData).length} fields)`, {
|
|
1182
|
+
url,
|
|
1183
|
+
requestPayload: { k, tk, query, zone, roundId },
|
|
1184
|
+
responseStatus: response.status,
|
|
1185
|
+
responseData: formData,
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
return formData;
|
|
1189
|
+
} catch (err: any) {
|
|
1190
|
+
this.emitProgress("GET_FESTIVAL_ERR", `Failed to get festival page: ${err.message}`, {
|
|
1191
|
+
url,
|
|
1192
|
+
requestPayload: { k, tk, query, zone, roundId },
|
|
1193
|
+
responseStatus: err.response?.status,
|
|
1194
|
+
responseData: err.response?.data || err.message,
|
|
1195
|
+
});
|
|
1196
|
+
throw err;
|
|
1197
|
+
}
|
|
850
1198
|
}
|
|
851
1199
|
|
|
852
1200
|
/**
|
|
@@ -862,21 +1210,39 @@ export class ThaiTicketMajor {
|
|
|
862
1210
|
};
|
|
863
1211
|
|
|
864
1212
|
const formData = new URLSearchParams(preparePayload);
|
|
1213
|
+
const url = `${this.endpoint}/validateseat.php?k=${k}&zw=${payload.zone}`;
|
|
865
1214
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1215
|
+
try {
|
|
1216
|
+
const response = await this.client.post(
|
|
1217
|
+
url,
|
|
1218
|
+
formData.toString(),
|
|
1219
|
+
{
|
|
1220
|
+
headers: {
|
|
1221
|
+
...this.defaultHeaders,
|
|
1222
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
1223
|
+
"x-requested-with": "XMLHttpRequest",
|
|
1224
|
+
referer: `${this.endpoint}/festival.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
|
|
1225
|
+
},
|
|
1226
|
+
}
|
|
1227
|
+
);
|
|
1228
|
+
|
|
1229
|
+
this.emitProgress("VALIDATE_FESTIVAL_RES", `Validate festival tickets: ${response.data?.result ? "SUCCESS" : "FAIL"}`, {
|
|
1230
|
+
url,
|
|
1231
|
+
requestPayload: preparePayload,
|
|
1232
|
+
responseStatus: response.status,
|
|
1233
|
+
responseData: response.data,
|
|
1234
|
+
});
|
|
878
1235
|
|
|
879
|
-
|
|
1236
|
+
return response.data;
|
|
1237
|
+
} catch (err: any) {
|
|
1238
|
+
this.emitProgress("VALIDATE_FESTIVAL_ERR", `Validate festival error: ${err.message}`, {
|
|
1239
|
+
url,
|
|
1240
|
+
requestPayload: preparePayload,
|
|
1241
|
+
responseStatus: err.response?.status,
|
|
1242
|
+
responseData: err.response?.data || err.message,
|
|
1243
|
+
});
|
|
1244
|
+
throw err;
|
|
1245
|
+
}
|
|
880
1246
|
}
|
|
881
1247
|
|
|
882
1248
|
/**
|
|
@@ -891,21 +1257,39 @@ export class ThaiTicketMajor {
|
|
|
891
1257
|
};
|
|
892
1258
|
|
|
893
1259
|
const formData = new URLSearchParams(preparePayload);
|
|
1260
|
+
const url = `${this.endpoint}/bookingfestival.php?k=${k}`;
|
|
894
1261
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1262
|
+
try {
|
|
1263
|
+
const response = await this.client.post(
|
|
1264
|
+
url,
|
|
1265
|
+
formData.toString(),
|
|
1266
|
+
{
|
|
1267
|
+
headers: {
|
|
1268
|
+
...this.defaultHeaders,
|
|
1269
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
1270
|
+
"x-requested-with": "XMLHttpRequest",
|
|
1271
|
+
referer: `${this.endpoint}/festival.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
|
|
1272
|
+
},
|
|
1273
|
+
}
|
|
1274
|
+
);
|
|
1275
|
+
|
|
1276
|
+
this.emitProgress("BOOK_FESTIVAL_RES", `Book festival tickets completed`, {
|
|
1277
|
+
url,
|
|
1278
|
+
requestPayload: preparePayload,
|
|
1279
|
+
responseStatus: response.status,
|
|
1280
|
+
responseData: response.data,
|
|
1281
|
+
});
|
|
907
1282
|
|
|
908
|
-
|
|
1283
|
+
return response.data;
|
|
1284
|
+
} catch (err: any) {
|
|
1285
|
+
this.emitProgress("BOOK_FESTIVAL_ERR", `Book festival error: ${err.message}`, {
|
|
1286
|
+
url,
|
|
1287
|
+
requestPayload: preparePayload,
|
|
1288
|
+
responseStatus: err.response?.status,
|
|
1289
|
+
responseData: err.response?.data || err.message,
|
|
1290
|
+
});
|
|
1291
|
+
throw err;
|
|
1292
|
+
}
|
|
909
1293
|
}
|
|
910
1294
|
|
|
911
1295
|
// --- Flow การลงทะเบียนผู้เข้าชม (Enrollment) ---
|
|
@@ -914,30 +1298,51 @@ export class ThaiTicketMajor {
|
|
|
914
1298
|
* Step 8 (Enroll): ดึง Form ลงทะเบียนผู้เข้าชม
|
|
915
1299
|
*/
|
|
916
1300
|
public async enroll(k: string, zone: string, roundId: string, payload: URLSearchParams | string): Promise<Record<string, string>> {
|
|
917
|
-
|
|
1301
|
+
const url = `${this.endpoint}/enroll.php?k=${k}&zone=${zone}&round=${roundId}`;
|
|
1302
|
+
this.emitProgress("GET_ENROLL", `Fetching enrollment form`, {
|
|
1303
|
+
url,
|
|
1304
|
+
requestPayload: payload.toString(),
|
|
1305
|
+
});
|
|
918
1306
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1307
|
+
try {
|
|
1308
|
+
const response = await this.client.post(
|
|
1309
|
+
url,
|
|
1310
|
+
payload.toString(),
|
|
1311
|
+
{
|
|
1312
|
+
headers: {
|
|
1313
|
+
...this.defaultHeaders,
|
|
1314
|
+
referer: `${this.endpoint}/fixed.php?k=${k}&zone=${zone}&round=${roundId}`,
|
|
1315
|
+
},
|
|
1316
|
+
}
|
|
1317
|
+
);
|
|
929
1318
|
|
|
930
|
-
|
|
931
|
-
|
|
1319
|
+
const $ = cheerio.load(response.data);
|
|
1320
|
+
const formData: Record<string, string> = {};
|
|
932
1321
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1322
|
+
$("#form input, #form select").each((_, el) => {
|
|
1323
|
+
const name = $(el).attr("name");
|
|
1324
|
+
if (name) {
|
|
1325
|
+
formData[name] = $(el).attr("value") ?? $(el).find("option:selected").attr("value") ?? "";
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
939
1328
|
|
|
940
|
-
|
|
1329
|
+
this.emitProgress("GET_ENROLL_RES", `Loaded enrollment form (${Object.keys(formData).length} fields)`, {
|
|
1330
|
+
url,
|
|
1331
|
+
requestPayload: payload.toString(),
|
|
1332
|
+
responseStatus: response.status,
|
|
1333
|
+
responseData: formData,
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
return formData;
|
|
1337
|
+
} catch (err: any) {
|
|
1338
|
+
this.emitProgress("GET_ENROLL_ERR", `Enroll form error: ${err.message}`, {
|
|
1339
|
+
url,
|
|
1340
|
+
requestPayload: payload.toString(),
|
|
1341
|
+
responseStatus: err.response?.status,
|
|
1342
|
+
responseData: err.response?.data || err.message,
|
|
1343
|
+
});
|
|
1344
|
+
throw err;
|
|
1345
|
+
}
|
|
941
1346
|
}
|
|
942
1347
|
|
|
943
1348
|
/**
|
|
@@ -948,7 +1353,11 @@ export class ThaiTicketMajor {
|
|
|
948
1353
|
payload: Record<string, any>,
|
|
949
1354
|
attendees: Attendee[]
|
|
950
1355
|
): Promise<{ data: any; nextPayload: URLSearchParams }> {
|
|
951
|
-
|
|
1356
|
+
const url = `${this.endpoint}/enroll_process.php?k=${k}`;
|
|
1357
|
+
this.emitProgress("ENROLL_PROCESS", `Submitting info for ${attendees.length} attendees`, {
|
|
1358
|
+
url,
|
|
1359
|
+
requestPayload: { attendeesCount: attendees.length, attendees },
|
|
1360
|
+
});
|
|
952
1361
|
|
|
953
1362
|
const formData = new URLSearchParams();
|
|
954
1363
|
|
|
@@ -978,23 +1387,42 @@ export class ThaiTicketMajor {
|
|
|
978
1387
|
}
|
|
979
1388
|
}
|
|
980
1389
|
|
|
981
|
-
const
|
|
982
|
-
`${this.endpoint}/enroll_process.php?k=${k}`,
|
|
983
|
-
formData.toString(),
|
|
984
|
-
{
|
|
985
|
-
headers: {
|
|
986
|
-
...this.defaultHeaders,
|
|
987
|
-
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
988
|
-
"x-requested-with": "XMLHttpRequest",
|
|
989
|
-
referer: `${this.endpoint}/enroll.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
|
|
990
|
-
},
|
|
991
|
-
}
|
|
992
|
-
);
|
|
1390
|
+
const reqPayloadObj = Object.fromEntries(formData.entries());
|
|
993
1391
|
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1392
|
+
try {
|
|
1393
|
+
const response = await this.client.post(
|
|
1394
|
+
url,
|
|
1395
|
+
formData.toString(),
|
|
1396
|
+
{
|
|
1397
|
+
headers: {
|
|
1398
|
+
...this.defaultHeaders,
|
|
1399
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
1400
|
+
"x-requested-with": "XMLHttpRequest",
|
|
1401
|
+
referer: `${this.endpoint}/enroll.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
|
|
1402
|
+
},
|
|
1403
|
+
}
|
|
1404
|
+
);
|
|
1405
|
+
|
|
1406
|
+
this.emitProgress("ENROLL_PROCESS_RES", `Enrollment processed successfully`, {
|
|
1407
|
+
url,
|
|
1408
|
+
requestPayload: reqPayloadObj,
|
|
1409
|
+
responseStatus: response.status,
|
|
1410
|
+
responseData: response.data,
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
return {
|
|
1414
|
+
data: response.data,
|
|
1415
|
+
nextPayload: formData,
|
|
1416
|
+
};
|
|
1417
|
+
} catch (err: any) {
|
|
1418
|
+
this.emitProgress("ENROLL_PROCESS_ERR", `Enrollment error: ${err.message}`, {
|
|
1419
|
+
url,
|
|
1420
|
+
requestPayload: reqPayloadObj,
|
|
1421
|
+
responseStatus: err.response?.status,
|
|
1422
|
+
responseData: err.response?.data || err.message,
|
|
1423
|
+
});
|
|
1424
|
+
throw err;
|
|
1425
|
+
}
|
|
998
1426
|
}
|
|
999
1427
|
|
|
1000
1428
|
// --- Flow การชำระเงิน (Payment) ---
|
|
@@ -1009,61 +1437,83 @@ export class ThaiTicketMajor {
|
|
|
1009
1437
|
roundId: string,
|
|
1010
1438
|
delayMs: number = 1000
|
|
1011
1439
|
): Promise<Record<string, string>> {
|
|
1012
|
-
|
|
1013
|
-
|
|
1440
|
+
const url = `${this.endpoint}/paymentall.php?k=${k}`;
|
|
1014
1441
|
const body = formData instanceof URLSearchParams ? formData.toString() : new URLSearchParams(formData).toString();
|
|
1442
|
+
const reqPayloadObj = formData instanceof URLSearchParams ? Object.fromEntries(formData.entries()) : formData;
|
|
1015
1443
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
1020
|
-
"x-requested-with": "XMLHttpRequest",
|
|
1021
|
-
referer: `${this.endpoint}/enroll.php?k=${k}&zone=${zone}&round=${roundId}`,
|
|
1022
|
-
},
|
|
1444
|
+
this.emitProgress("GET_PAYMENT_DETAILS", `Loading payment confirmation page`, {
|
|
1445
|
+
url,
|
|
1446
|
+
requestPayload: reqPayloadObj,
|
|
1023
1447
|
});
|
|
1024
1448
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1449
|
+
try {
|
|
1450
|
+
const response = await this.client.post(url, body, {
|
|
1451
|
+
headers: {
|
|
1452
|
+
...this.defaultHeaders,
|
|
1453
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
1454
|
+
"x-requested-with": "XMLHttpRequest",
|
|
1455
|
+
referer: `${this.endpoint}/enroll.php?k=${k}&zone=${zone}&round=${roundId}`,
|
|
1456
|
+
},
|
|
1457
|
+
});
|
|
1027
1458
|
|
|
1028
|
-
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
|
|
1459
|
+
const $ = cheerio.load(response.data);
|
|
1460
|
+
const hiddenInputs: Record<string, string> = {};
|
|
1461
|
+
|
|
1462
|
+
$("#frm-confirm input").each((_, el) => {
|
|
1463
|
+
const key = $(el).attr("name") ?? $(el).attr("id");
|
|
1464
|
+
if (key) {
|
|
1465
|
+
hiddenInputs[key] = $(el).val()?.toString() ?? "";
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
const addressForm = $("#frm-address");
|
|
1470
|
+
if (addressForm.length > 0) {
|
|
1471
|
+
const fname = addressForm.find("input[name='c_fname_m']").val()?.toString() ?? "";
|
|
1472
|
+
const lname = addressForm.find("input[name='c_lname_m']").val()?.toString() ?? "";
|
|
1473
|
+
const address = addressForm.find("textarea[name='c_address_m']").val()?.toString() ?? "";
|
|
1474
|
+
const ctId = addressForm.find("select[name='c_ctCode'] option:selected").val()?.toString() ?? "";
|
|
1475
|
+
const pvId = addressForm.find("select[name='c_pvId'] option:selected").val()?.toString() ?? "";
|
|
1476
|
+
const amId = addressForm.find("select[name='c_amId'] option:selected").val()?.toString() ?? "";
|
|
1477
|
+
const zipcode = addressForm.find("input[name='c_PostCode']").val()?.toString() ?? "";
|
|
1478
|
+
|
|
1479
|
+
let rawPhone = addressForm.find("input[name='c_ContactRecipient']").val()?.toString() ?? "";
|
|
1480
|
+
if (rawPhone.startsWith("66")) {
|
|
1481
|
+
rawPhone = rawPhone.slice(2);
|
|
1482
|
+
}
|
|
1483
|
+
const phoneArea = addressForm.find("input[name='telephoneArea']").val()?.toString() ?? "66";
|
|
1484
|
+
|
|
1485
|
+
hiddenInputs["adr_fname"] = fname;
|
|
1486
|
+
hiddenInputs["adr_lname"] = lname;
|
|
1487
|
+
hiddenInputs["adr_address"] = address;
|
|
1488
|
+
hiddenInputs["adr_ctId"] = ctId;
|
|
1489
|
+
hiddenInputs["adr_pvId"] = pvId;
|
|
1490
|
+
hiddenInputs["adr_amId"] = amId;
|
|
1491
|
+
hiddenInputs["adr_zipcode"] = zipcode;
|
|
1492
|
+
hiddenInputs["adr_mobile"] = rawPhone.replace(/(\d{2})(\d{3})(\d{4})/, "$1 $2 $3");
|
|
1493
|
+
hiddenInputs["adr_mobilearea"] = phoneArea;
|
|
1032
1494
|
}
|
|
1033
|
-
});
|
|
1034
1495
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
let rawPhone = addressForm.find("input[name='c_ContactRecipient']").val()?.toString() ?? "";
|
|
1046
|
-
if (rawPhone.startsWith("66")) {
|
|
1047
|
-
rawPhone = rawPhone.slice(2);
|
|
1496
|
+
this.emitProgress("GET_PAYMENT_DETAILS_RES", `Loaded payment confirmation details (${Object.keys(hiddenInputs).length} fields)`, {
|
|
1497
|
+
url,
|
|
1498
|
+
requestPayload: reqPayloadObj,
|
|
1499
|
+
responseStatus: response.status,
|
|
1500
|
+
responseData: hiddenInputs,
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
if (delayMs > 0) {
|
|
1504
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1048
1505
|
}
|
|
1049
|
-
const phoneArea = addressForm.find("input[name='telephoneArea']").val()?.toString() ?? "66";
|
|
1050
|
-
|
|
1051
|
-
hiddenInputs["adr_fname"] = fname;
|
|
1052
|
-
hiddenInputs["adr_lname"] = lname;
|
|
1053
|
-
hiddenInputs["adr_address"] = address;
|
|
1054
|
-
hiddenInputs["adr_ctId"] = ctId;
|
|
1055
|
-
hiddenInputs["adr_pvId"] = pvId;
|
|
1056
|
-
hiddenInputs["adr_amId"] = amId;
|
|
1057
|
-
hiddenInputs["adr_zipcode"] = zipcode;
|
|
1058
|
-
hiddenInputs["adr_mobile"] = rawPhone.replace(/(\d{2})(\d{3})(\d{4})/, "$1 $2 $3");
|
|
1059
|
-
hiddenInputs["adr_mobilearea"] = phoneArea;
|
|
1060
|
-
}
|
|
1061
1506
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1507
|
+
return hiddenInputs;
|
|
1508
|
+
} catch (err: any) {
|
|
1509
|
+
this.emitProgress("GET_PAYMENT_DETAILS_ERR", `Payment details error: ${err.message}`, {
|
|
1510
|
+
url,
|
|
1511
|
+
requestPayload: reqPayloadObj,
|
|
1512
|
+
responseStatus: err.response?.status,
|
|
1513
|
+
responseData: err.response?.data || err.message,
|
|
1514
|
+
});
|
|
1515
|
+
throw err;
|
|
1064
1516
|
}
|
|
1065
|
-
|
|
1066
|
-
return hiddenInputs;
|
|
1067
1517
|
}
|
|
1068
1518
|
|
|
1069
1519
|
/**
|
|
@@ -1075,8 +1525,7 @@ export class ThaiTicketMajor {
|
|
|
1075
1525
|
deliver: string = "1",
|
|
1076
1526
|
payType: string = "KBQR"
|
|
1077
1527
|
): Promise<Record<string, string>> {
|
|
1078
|
-
|
|
1079
|
-
|
|
1528
|
+
const url = `${this.endpoint}/paycfmall.php?k=${k}`;
|
|
1080
1529
|
const cntTicket = parseInt(payload.cal_cntticket || "0", 10);
|
|
1081
1530
|
const amountCode = parseFloat(payload.amountcode || "0");
|
|
1082
1531
|
const deliverFee = parseFloat(payload.cal_deliverfee || payload.val_deliverfee || "80");
|
|
@@ -1095,6 +1544,11 @@ export class ThaiTicketMajor {
|
|
|
1095
1544
|
cal_totalamount: Math.round(totalAmount).toString(),
|
|
1096
1545
|
};
|
|
1097
1546
|
|
|
1547
|
+
this.emitProgress("CONFIRM_PAYMENT", `Calculating total and confirming payment (${payType})`, {
|
|
1548
|
+
url,
|
|
1549
|
+
requestPayload: computedPayload,
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1098
1552
|
const formData = new URLSearchParams();
|
|
1099
1553
|
for (const [key, value] of Object.entries(computedPayload)) {
|
|
1100
1554
|
if (key !== "check-protect") {
|
|
@@ -1102,92 +1556,137 @@ export class ThaiTicketMajor {
|
|
|
1102
1556
|
}
|
|
1103
1557
|
}
|
|
1104
1558
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1559
|
+
try {
|
|
1560
|
+
const response = await this.client.post(url, formData.toString(), {
|
|
1561
|
+
headers: {
|
|
1562
|
+
...this.defaultHeaders,
|
|
1563
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
1564
|
+
"x-requested-with": "XMLHttpRequest",
|
|
1565
|
+
referer: `${this.endpoint}/paymentall.php?k=${k}`,
|
|
1566
|
+
},
|
|
1567
|
+
});
|
|
1113
1568
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1569
|
+
const $ = cheerio.load(response.data);
|
|
1570
|
+
const nextJsonData: Record<string, string> = {};
|
|
1116
1571
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1572
|
+
$("#payallfrm input[type='hidden']").each((_, el) => {
|
|
1573
|
+
const name = $(el).attr("name");
|
|
1574
|
+
const value = $(el).attr("value") ?? "";
|
|
1575
|
+
if (name) {
|
|
1576
|
+
nextJsonData[name] = value;
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
this.emitProgress("CONFIRM_PAYMENT_RES", `Confirmed payment (Total: ฿${computedPayload.cal_totalamount})`, {
|
|
1581
|
+
url,
|
|
1582
|
+
requestPayload: computedPayload,
|
|
1583
|
+
responseStatus: response.status,
|
|
1584
|
+
responseData: nextJsonData,
|
|
1585
|
+
});
|
|
1124
1586
|
|
|
1125
|
-
|
|
1587
|
+
return nextJsonData;
|
|
1588
|
+
} catch (err: any) {
|
|
1589
|
+
this.emitProgress("CONFIRM_PAYMENT_ERR", `Payment confirmation error: ${err.message}`, {
|
|
1590
|
+
url,
|
|
1591
|
+
requestPayload: computedPayload,
|
|
1592
|
+
responseStatus: err.response?.status,
|
|
1593
|
+
responseData: err.response?.data || err.message,
|
|
1594
|
+
});
|
|
1595
|
+
throw err;
|
|
1596
|
+
}
|
|
1126
1597
|
}
|
|
1127
1598
|
|
|
1128
1599
|
/**
|
|
1129
1600
|
* Step 12: ดึง payload เข้ารหัสสำหรับ KBank
|
|
1130
1601
|
*/
|
|
1131
1602
|
public async orderEncKBankQR(k: string, payCfmData: Record<string, string>): Promise<Record<string, string>> {
|
|
1132
|
-
|
|
1603
|
+
const url = `${this.endpoint}/orderenc_kbankqr.php`;
|
|
1604
|
+
this.emitProgress("ORDER_ENC_KBANK", `Encrypting order payload for KBank`, {
|
|
1605
|
+
url,
|
|
1606
|
+
requestPayload: payCfmData,
|
|
1607
|
+
});
|
|
1133
1608
|
|
|
1134
1609
|
const formData = new URLSearchParams(payCfmData);
|
|
1135
1610
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1611
|
+
try {
|
|
1612
|
+
const response = await this.client.post(url, formData.toString(), {
|
|
1613
|
+
headers: {
|
|
1614
|
+
...this.defaultHeaders,
|
|
1615
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
1616
|
+
"x-requested-with": "XMLHttpRequest",
|
|
1617
|
+
referer: `${this.endpoint}/paycfmall.php?k=${k}`,
|
|
1618
|
+
},
|
|
1619
|
+
});
|
|
1144
1620
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
requestPayload: payCfmData,
|
|
1148
|
-
responseStatus: response.status,
|
|
1149
|
-
responseData: response.data,
|
|
1150
|
-
});
|
|
1621
|
+
const $ = cheerio.load(response.data);
|
|
1622
|
+
const kbankqrData: Record<string, string> = {};
|
|
1151
1623
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1624
|
+
$("#kbankqr input[type='hidden']").each((_, el) => {
|
|
1625
|
+
const name = $(el).attr("name");
|
|
1626
|
+
const value = $(el).attr("value") ?? "";
|
|
1627
|
+
if (name) {
|
|
1628
|
+
kbankqrData[name] = value;
|
|
1629
|
+
}
|
|
1630
|
+
});
|
|
1154
1631
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
kbankqrData
|
|
1160
|
-
}
|
|
1161
|
-
});
|
|
1632
|
+
this.emitProgress("ORDER_ENC_KBANK_RES", `Received orderenc_kbankqr.php response`, {
|
|
1633
|
+
url,
|
|
1634
|
+
requestPayload: payCfmData,
|
|
1635
|
+
responseStatus: response.status,
|
|
1636
|
+
responseData: Object.keys(kbankqrData).length > 0 ? kbankqrData : response.data,
|
|
1637
|
+
});
|
|
1162
1638
|
|
|
1163
|
-
|
|
1639
|
+
return kbankqrData;
|
|
1640
|
+
} catch (err: any) {
|
|
1641
|
+
this.emitProgress("ORDER_ENC_KBANK_ERR", `Order encrypt error: ${err.message}`, {
|
|
1642
|
+
url,
|
|
1643
|
+
requestPayload: payCfmData,
|
|
1644
|
+
responseStatus: err.response?.status,
|
|
1645
|
+
responseData: err.response?.data || err.message,
|
|
1646
|
+
});
|
|
1647
|
+
throw err;
|
|
1648
|
+
}
|
|
1164
1649
|
}
|
|
1165
1650
|
|
|
1166
1651
|
/**
|
|
1167
1652
|
* Step 13: เรียก getkbankqr.php
|
|
1168
1653
|
*/
|
|
1169
1654
|
public async getKBankQR(orderEncData: Record<string, string>): Promise<any> {
|
|
1170
|
-
|
|
1655
|
+
const url = `${this.endpoint}/getkbankqr.php`;
|
|
1656
|
+
this.emitProgress("GET_KBANK_QR", `Calling KBank Gateway info`, {
|
|
1657
|
+
url,
|
|
1658
|
+
requestPayload: orderEncData,
|
|
1659
|
+
});
|
|
1171
1660
|
|
|
1172
1661
|
const formData = new URLSearchParams(orderEncData);
|
|
1173
1662
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1663
|
+
try {
|
|
1664
|
+
const response = await this.client.post(url, formData.toString(), {
|
|
1665
|
+
headers: {
|
|
1666
|
+
...this.defaultHeaders,
|
|
1667
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
1668
|
+
"x-requested-with": "XMLHttpRequest",
|
|
1669
|
+
referer: `${this.endpoint}/orderenc_kbankqr.php`,
|
|
1670
|
+
},
|
|
1671
|
+
});
|
|
1182
1672
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1673
|
+
this.emitProgress("GET_KBANK_QR_RES", `Received getkbankqr.php response`, {
|
|
1674
|
+
url,
|
|
1675
|
+
requestPayload: orderEncData,
|
|
1676
|
+
responseStatus: response.status,
|
|
1677
|
+
responseData: response.data,
|
|
1678
|
+
});
|
|
1189
1679
|
|
|
1190
|
-
|
|
1680
|
+
return response.data;
|
|
1681
|
+
} catch (err: any) {
|
|
1682
|
+
this.emitProgress("GET_KBANK_QR_ERR", `Get KBank QR error: ${err.message}`, {
|
|
1683
|
+
url,
|
|
1684
|
+
requestPayload: orderEncData,
|
|
1685
|
+
responseStatus: err.response?.status,
|
|
1686
|
+
responseData: err.response?.data || err.message,
|
|
1687
|
+
});
|
|
1688
|
+
throw err;
|
|
1689
|
+
}
|
|
1191
1690
|
}
|
|
1192
1691
|
|
|
1193
1692
|
/**
|
|
@@ -1197,8 +1696,6 @@ export class ThaiTicketMajor {
|
|
|
1197
1696
|
orderEncData: Record<string, string>,
|
|
1198
1697
|
kbankPayload: Record<string, any>
|
|
1199
1698
|
): Promise<{ orderId: string; apiKey: string }> {
|
|
1200
|
-
this.emitProgress("PAYMENT_KBANK_QR", `Extracting KBank OrderID and API Key`);
|
|
1201
|
-
|
|
1202
1699
|
const preparePayload = {
|
|
1203
1700
|
...orderEncData,
|
|
1204
1701
|
reasoncode: kbankPayload?.reasoncode,
|
|
@@ -1209,43 +1706,58 @@ export class ThaiTicketMajor {
|
|
|
1209
1706
|
kbankqr_txExpiry: kbankPayload?.kbankqr_txExpiry,
|
|
1210
1707
|
};
|
|
1211
1708
|
|
|
1709
|
+
const url = `${this.endpoint}/payment_kbankqr.php`;
|
|
1710
|
+
this.emitProgress("PAYMENT_KBANK_QR", `Extracting KBank OrderID and API Key`, {
|
|
1711
|
+
url,
|
|
1712
|
+
requestPayload: preparePayload,
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1212
1715
|
const formData = new URLSearchParams(preparePayload);
|
|
1213
1716
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1717
|
+
try {
|
|
1718
|
+
const response = await this.client.post(url, formData.toString(), {
|
|
1719
|
+
headers: {
|
|
1720
|
+
...this.defaultHeaders,
|
|
1721
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
1722
|
+
"x-requested-with": "XMLHttpRequest",
|
|
1723
|
+
referer: `${this.endpoint}/orderenc_kbankqr.php`,
|
|
1724
|
+
},
|
|
1725
|
+
});
|
|
1222
1726
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
requestPayload: preparePayload,
|
|
1226
|
-
responseStatus: response.status,
|
|
1227
|
-
responseData: response.data,
|
|
1228
|
-
});
|
|
1727
|
+
const $ = cheerio.load(response.data);
|
|
1728
|
+
const script = $("#kbankpost script[data-order-id]");
|
|
1229
1729
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1730
|
+
const orderId = script.attr("data-order-id") ?? "";
|
|
1731
|
+
const apiKey = script.attr("data-apikey") ?? "";
|
|
1232
1732
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1733
|
+
this.emitProgress("PAYMENT_KBANK_QR_RES", `Received payment_kbankqr.php response (OrderID: ${orderId || "N/A"})`, {
|
|
1734
|
+
url,
|
|
1735
|
+
requestPayload: preparePayload,
|
|
1736
|
+
responseStatus: response.status,
|
|
1737
|
+
responseData: orderId && apiKey ? { orderId, apiKey, htmlSnippet: $("#kbankpost").html() } : response.data,
|
|
1738
|
+
});
|
|
1235
1739
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1740
|
+
if (!orderId || !apiKey) {
|
|
1741
|
+
throw new Error("Unable to extract KBank orderId or apiKey from response");
|
|
1742
|
+
}
|
|
1239
1743
|
|
|
1240
|
-
|
|
1744
|
+
return { orderId, apiKey };
|
|
1745
|
+
} catch (err: any) {
|
|
1746
|
+
this.emitProgress("PAYMENT_KBANK_QR_ERR", `Payment KBank QR error: ${err.message}`, {
|
|
1747
|
+
url,
|
|
1748
|
+
requestPayload: preparePayload,
|
|
1749
|
+
responseStatus: err.response?.status,
|
|
1750
|
+
responseData: err.response?.data || err.message,
|
|
1751
|
+
});
|
|
1752
|
+
throw err;
|
|
1753
|
+
}
|
|
1241
1754
|
}
|
|
1242
1755
|
|
|
1243
1756
|
/**
|
|
1244
1757
|
* Step 15: ยิงตรง Kasikorn Bank Gateway เพื่อสร้าง ThaiQR Code
|
|
1245
1758
|
*/
|
|
1246
1759
|
public async generateThaiQR(orderId: string, apiKey: string, amount: string | number): Promise<any> {
|
|
1247
|
-
|
|
1248
|
-
|
|
1760
|
+
const url = "https://kpaymentgateway-services.kasikornbank.com/qr/v2/qr";
|
|
1249
1761
|
const payload = {
|
|
1250
1762
|
amount: amount,
|
|
1251
1763
|
currency: "THB",
|
|
@@ -1254,15 +1766,37 @@ export class ThaiTicketMajor {
|
|
|
1254
1766
|
sof: "ThaiQR",
|
|
1255
1767
|
};
|
|
1256
1768
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
referer: "https://kpaymentgateway.kasikornbank.com/",
|
|
1261
|
-
"x-api-key": apiKey,
|
|
1262
|
-
},
|
|
1769
|
+
this.emitProgress("GENERATE_THAI_QR", `Requesting ThaiQR code from Kasikorn Bank`, {
|
|
1770
|
+
url,
|
|
1771
|
+
requestPayload: payload,
|
|
1263
1772
|
});
|
|
1264
1773
|
|
|
1265
|
-
|
|
1774
|
+
try {
|
|
1775
|
+
const response = await this.client.post(url, payload, {
|
|
1776
|
+
headers: {
|
|
1777
|
+
...this.defaultHeaders,
|
|
1778
|
+
referer: "https://kpaymentgateway.kasikornbank.com/",
|
|
1779
|
+
"x-api-key": apiKey,
|
|
1780
|
+
},
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
this.emitProgress("GENERATE_THAI_QR_RES", `Received ThaiQR from Kasikorn Bank`, {
|
|
1784
|
+
url,
|
|
1785
|
+
requestPayload: payload,
|
|
1786
|
+
responseStatus: response.status,
|
|
1787
|
+
responseData: response.data,
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
return response.data;
|
|
1791
|
+
} catch (err: any) {
|
|
1792
|
+
this.emitProgress("GENERATE_THAI_QR_ERR", `Kasikorn ThaiQR error: ${err.message}`, {
|
|
1793
|
+
url,
|
|
1794
|
+
requestPayload: payload,
|
|
1795
|
+
responseStatus: err.response?.status,
|
|
1796
|
+
responseData: err.response?.data || err.message,
|
|
1797
|
+
});
|
|
1798
|
+
throw err;
|
|
1799
|
+
}
|
|
1266
1800
|
}
|
|
1267
1801
|
|
|
1268
1802
|
// ==========================================
|
|
@@ -1367,6 +1901,14 @@ export class ThaiTicketMajor {
|
|
|
1367
1901
|
|
|
1368
1902
|
// 5. ดำเนินการตามประเภทโซน (Fixed vs Festival)
|
|
1369
1903
|
if (targetZone.page === "fixed.php") {
|
|
1904
|
+
if (this.inBrowser) {
|
|
1905
|
+
await ThaiTicketMajor.setupExtensionRules({
|
|
1906
|
+
k: zoneData.k,
|
|
1907
|
+
zone: targetZone.value,
|
|
1908
|
+
roundId: targetPerformRound.id,
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1370
1912
|
const fixedData = await this.getFixedSeats(zoneData.k, targetZone.value, targetPerformRound.id);
|
|
1371
1913
|
const availableSeats = fixedData.seats;
|
|
1372
1914
|
|
|
@@ -1412,6 +1954,14 @@ export class ThaiTicketMajor {
|
|
|
1412
1954
|
}
|
|
1413
1955
|
} else {
|
|
1414
1956
|
// Festival Flow
|
|
1957
|
+
if (this.inBrowser) {
|
|
1958
|
+
await ThaiTicketMajor.setupExtensionRules({
|
|
1959
|
+
k: zoneData.k,
|
|
1960
|
+
zone: targetZone.value,
|
|
1961
|
+
roundId: targetPerformRound.id,
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1415
1965
|
const festivalForm = await this.getFestival(zoneData.k, zoneData.tk, selectedRound.query, targetZone.value, targetPerformRound.id);
|
|
1416
1966
|
|
|
1417
1967
|
await this.validateFestivalSeats(zoneData.k, festivalForm, ticketCount);
|