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/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 in Chrome Extension to set Referer header for API requests only.
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 = [1001, 1002, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019];
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
- const response = await this.client.get(eventUrl, {
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
- const $ = cheerio.load(response.data);
494
- const rounds: RoundInfo[] = [];
495
-
496
- $("a[data-button]").each((_, el) => {
497
- const link = $(el);
498
- const href = link.attr("href") ?? "";
499
- const onclick = link.attr("onclick") ?? "";
500
- const dataUrl = link.attr("data-url") ?? "";
501
- const dataHref = link.attr("data-href") ?? "";
502
- const allLinkAttrs = `${href} ${onclick} ${dataUrl} ${dataHref}`;
503
-
504
- const queryMatch = allLinkAttrs.match(/query=([0-9a-zA-Z_-]+)/);
505
- const query = queryMatch ? queryMatch[1] : "";
506
- const time = link.find(".item-show").text().trim();
507
- const date = link.closest(".row").find(".date").text().trim();
508
- const isDisabled = link.is(":disabled") || link.attr("disabled") !== undefined || !query;
509
-
510
- rounds.push({
511
- id: query || link.attr("data-button") || "",
512
- label: `${date} ${time}`.trim(),
513
- date,
514
- time,
515
- url: href,
516
- query,
517
- isDisabled,
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
- return rounds;
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
- this.emitProgress("VERIFY_CONDITION", `Verifying condition for query ${query}`);
533
-
534
- const response = await this.client.post(
535
- `${this.endpoint}/verify_checkcondition.php`,
536
- new URLSearchParams({
537
- rdagree: "1",
538
- rdId: "",
539
- autopopup: "",
540
- query: query,
541
- }),
542
- { headers: this.defaultHeaders }
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
- return response.data;
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
- this.emitProgress("GET_ZONES", `Fetching zone data and tokens for query ${query}`);
553
-
554
- const response = await this.client.get(`${this.endpoint}/zones.php?query=${query}`, {
555
- headers: this.defaultHeaders,
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
- const $ = cheerio.load(response.data);
559
- const rawInputs: Record<string, string> = {};
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
- $("#frm input[type='hidden']").each((_, el) => {
562
- const name = $(el).attr("name");
563
- const value = $(el).attr("value") ?? "";
564
- if (name) {
565
- rawInputs[name] = value;
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
- const rounds = $("#rdId option")
570
- .filter((_, el) => !!$(el).attr("value"))
571
- .map((_, el) => {
572
- const opt = $(el);
573
- return {
574
- id: opt.attr("value") ?? "",
575
- label: opt.text().trim(),
576
- };
577
- })
578
- .get();
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
- return {
581
- k: rawInputs["k"] || "",
582
- tk: rawInputs["tk"] || "",
583
- query,
584
- rounds,
585
- rawInputs,
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
- this.emitProgress("GET_ZONE_AVAIL", `Checking zone availability for round ${roundId}`);
594
-
595
- const response = await this.client.get(`${this.endpoint}/zonesavail.php?round=${roundId}&tk=${tk}`, {
596
- headers: {
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
- const $ = cheerio.load(response.data);
603
- const zones: ZoneItem[] = [];
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
- $(".table tbody tr").each((_, el) => {
606
- const row = $(el);
607
- const zoneId = row.find("td:nth-child(1) a").attr("id") ?? "";
608
- const onclick = row.attr("onclick") ?? "";
609
- const match = onclick.match(/gonextstep\('([^']+)','([^']+)'/);
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
- const page = (match?.[1] ?? "") as "fixed.php" | "festival.php" | string;
612
- const zoneValue = match?.[2] ?? "";
613
- const statusText = row.find("td:nth-child(2) a").text().trim();
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
- let available = 0;
616
- let isAvailable = false;
861
+ let available = 0;
862
+ let isAvailable = false;
617
863
 
618
- if (statusText.toLowerCase() === "available") {
619
- available = 1;
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
- zones.push({
630
- id: zoneId,
631
- value: zoneValue,
632
- available,
633
- page,
634
- isAvailable,
875
+ zones.push({
876
+ id: zoneId,
877
+ value: zoneValue,
878
+ available,
879
+ page,
880
+ isAvailable,
881
+ });
635
882
  });
636
- });
637
883
 
638
- return zones;
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
- this.emitProgress("GET_FIXED_SEATS", `Fetching seat layout for zone ${zone}`);
648
-
649
- const response = await this.client.get(`${this.endpoint}/fixed.php?k=${k}&zone=${zone}&round=${roundId}`, {
650
- headers: this.defaultHeaders,
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
- const $ = cheerio.load(response.data);
654
- const seats: SeatItem[] = $("#tableseats .seatuncheck")
655
- .map((_, el) => {
656
- const seat = $(el);
657
- return {
658
- id: seat.attr("id") ?? "",
659
- seat: seat.attr("data-seat") ?? "",
660
- seatk: seat.attr("data-seatk") ?? "",
661
- };
662
- })
663
- .get();
664
-
665
- const form: Record<string, string> = {};
666
- $("#frmPayment input").each((_, el) => {
667
- const name = $(el).attr("name");
668
- if (name) {
669
- form[name] = $(el).attr("value") ?? "";
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
- return { seats, form };
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 response = await this.client.post(
736
- `${this.endpoint}/validateseat.php?k=${k}&zw=${zone}`,
737
- formData.toString(),
738
- {
739
- headers: {
740
- ...this.defaultHeaders,
741
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
742
- "x-requested-with": "XMLHttpRequest",
743
- referer: `${this.endpoint}/fixed.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
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
- lastResponse = response.data;
749
- if (!lastResponse.result) {
750
- throw new Error(`Validate seat failed (${currentSeat.seat}): ${lastResponse.message || "Unknown error"}`);
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 response = await this.client.post(
804
- `${this.endpoint}/bookingseats.php?k=${k}`,
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
- return {
819
- data: response.data,
820
- nextPayload: bodyParams,
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.emitProgress("GET_FESTIVAL", `Fetching festival zone ${zone}`);
831
-
832
- const response = await this.client.get(`${this.endpoint}/festival.php?k=${k}&zone=${zone}&round=${roundId}`, {
833
- headers: {
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
- const $ = cheerio.load(response.data);
840
- const formData: Record<string, string> = {};
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
- $("#frm input").each((_, el) => {
843
- const name = $(el).attr("name");
844
- if (name) {
845
- formData[name] = $(el).attr("value") ?? "";
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
- return formData;
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
- const response = await this.client.post(
867
- `${this.endpoint}/validateseat.php?k=${k}&zw=${payload.zone}`,
868
- formData.toString(),
869
- {
870
- headers: {
871
- ...this.defaultHeaders,
872
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
873
- "x-requested-with": "XMLHttpRequest",
874
- referer: `${this.endpoint}/festival.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
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
- return response.data;
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
- const response = await this.client.post(
896
- `${this.endpoint}/bookingfestival.php?k=${k}`,
897
- formData.toString(),
898
- {
899
- headers: {
900
- ...this.defaultHeaders,
901
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
902
- "x-requested-with": "XMLHttpRequest",
903
- referer: `${this.endpoint}/festival.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
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
- return response.data;
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
- this.emitProgress("GET_ENROLL", `Fetching enrollment form`);
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
- const response = await this.client.post(
920
- `${this.endpoint}/enroll.php?k=${k}&zone=${zone}&round=${roundId}`,
921
- payload.toString(),
922
- {
923
- headers: {
924
- ...this.defaultHeaders,
925
- referer: `${this.endpoint}/fixed.php?k=${k}&zone=${zone}&round=${roundId}`,
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
- const $ = cheerio.load(response.data);
931
- const formData: Record<string, string> = {};
1319
+ const $ = cheerio.load(response.data);
1320
+ const formData: Record<string, string> = {};
932
1321
 
933
- $("#form input, #form select").each((_, el) => {
934
- const name = $(el).attr("name");
935
- if (name) {
936
- formData[name] = $(el).attr("value") ?? $(el).find("option:selected").attr("value") ?? "";
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
- return formData;
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
- this.emitProgress("ENROLL_PROCESS", `Submitting info for ${attendees.length} attendees`);
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 response = await this.client.post(
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
- return {
995
- data: response.data,
996
- nextPayload: formData,
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
- this.emitProgress("GET_PAYMENT_DETAILS", `Loading payment confirmation page`);
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
- const response = await this.client.post(`${this.endpoint}/paymentall.php?k=${k}`, body, {
1017
- headers: {
1018
- ...this.defaultHeaders,
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
- const $ = cheerio.load(response.data);
1026
- const hiddenInputs: Record<string, string> = {};
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
- $("#frm-confirm input").each((_, el) => {
1029
- const key = $(el).attr("name") ?? $(el).attr("id");
1030
- if (key) {
1031
- hiddenInputs[key] = $(el).val()?.toString() ?? "";
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
- const addressForm = $("#frm-address");
1036
- if (addressForm.length > 0) {
1037
- const fname = addressForm.find("input[name='c_fname_m']").val()?.toString() ?? "";
1038
- const lname = addressForm.find("input[name='c_lname_m']").val()?.toString() ?? "";
1039
- const address = addressForm.find("textarea[name='c_address_m']").val()?.toString() ?? "";
1040
- const ctId = addressForm.find("select[name='c_ctCode'] option:selected").val()?.toString() ?? "";
1041
- const pvId = addressForm.find("select[name='c_pvId'] option:selected").val()?.toString() ?? "";
1042
- const amId = addressForm.find("select[name='c_amId'] option:selected").val()?.toString() ?? "";
1043
- const zipcode = addressForm.find("input[name='c_PostCode']").val()?.toString() ?? "";
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
- if (delayMs > 0) {
1063
- await new Promise((resolve) => setTimeout(resolve, delayMs));
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
- this.emitProgress("CONFIRM_PAYMENT", `Calculating total and confirming payment (${payType})`);
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
- const response = await this.client.post(`${this.endpoint}/paycfmall.php?k=${k}`, formData.toString(), {
1106
- headers: {
1107
- ...this.defaultHeaders,
1108
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1109
- "x-requested-with": "XMLHttpRequest",
1110
- referer: `${this.endpoint}/paymentall.php?k=${k}`,
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
- const $ = cheerio.load(response.data);
1115
- const nextJsonData: Record<string, string> = {};
1569
+ const $ = cheerio.load(response.data);
1570
+ const nextJsonData: Record<string, string> = {};
1116
1571
 
1117
- $("#payallfrm input[type='hidden']").each((_, el) => {
1118
- const name = $(el).attr("name");
1119
- const value = $(el).attr("value") ?? "";
1120
- if (name) {
1121
- nextJsonData[name] = value;
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
- return nextJsonData;
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
- this.emitProgress("ORDER_ENC_KBANK", `Encrypting order payload for KBank`);
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
- const response = await this.client.post(`${this.endpoint}/orderenc_kbankqr.php`, formData.toString(), {
1137
- headers: {
1138
- ...this.defaultHeaders,
1139
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1140
- "x-requested-with": "XMLHttpRequest",
1141
- referer: `${this.endpoint}/paycfmall.php?k=${k}`,
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
- this.emitProgress("ORDER_ENC_KBANK_RES", `Received orderenc_kbankqr.php response`, {
1146
- url: `${this.endpoint}/orderenc_kbankqr.php`,
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
- const $ = cheerio.load(response.data);
1153
- const kbankqrData: Record<string, string> = {};
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
- $("#kbankqr input[type='hidden']").each((_, el) => {
1156
- const name = $(el).attr("name");
1157
- const value = $(el).attr("value") ?? "";
1158
- if (name) {
1159
- kbankqrData[name] = value;
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
- return kbankqrData;
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
- this.emitProgress("GET_KBANK_QR", `Calling KBank Gateway info`);
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
- const response = await this.client.post(`${this.endpoint}/getkbankqr.php`, formData.toString(), {
1175
- headers: {
1176
- ...this.defaultHeaders,
1177
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1178
- "x-requested-with": "XMLHttpRequest",
1179
- referer: `${this.endpoint}/orderenc_kbankqr.php`,
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
- this.emitProgress("GET_KBANK_QR_RES", `Received getkbankqr.php response`, {
1184
- url: `${this.endpoint}/getkbankqr.php`,
1185
- requestPayload: orderEncData,
1186
- responseStatus: response.status,
1187
- responseData: response.data,
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
- return response.data;
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
- const response = await this.client.post(`${this.endpoint}/payment_kbankqr.php`, formData.toString(), {
1215
- headers: {
1216
- ...this.defaultHeaders,
1217
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1218
- "x-requested-with": "XMLHttpRequest",
1219
- referer: `${this.endpoint}/orderenc_kbankqr.php`,
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
- this.emitProgress("PAYMENT_KBANK_QR_RES", `Received payment_kbankqr.php response`, {
1224
- url: `${this.endpoint}/payment_kbankqr.php`,
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
- const $ = cheerio.load(response.data);
1231
- const script = $("#kbankpost script[data-order-id]");
1730
+ const orderId = script.attr("data-order-id") ?? "";
1731
+ const apiKey = script.attr("data-apikey") ?? "";
1232
1732
 
1233
- const orderId = script.attr("data-order-id") ?? "";
1234
- const apiKey = script.attr("data-apikey") ?? "";
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
- if (!orderId || !apiKey) {
1237
- throw new Error("Unable to extract KBank orderId or apiKey from response");
1238
- }
1740
+ if (!orderId || !apiKey) {
1741
+ throw new Error("Unable to extract KBank orderId or apiKey from response");
1742
+ }
1239
1743
 
1240
- return { orderId, apiKey };
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
- this.emitProgress("GENERATE_THAI_QR", `Requesting ThaiQR code from Kasikorn Bank`);
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
- const response = await this.client.post("https://kpaymentgateway-services.kasikornbank.com/qr/v2/qr", payload, {
1258
- headers: {
1259
- ...this.defaultHeaders,
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
- return response.data;
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);