playttm 0.0.7 → 0.0.8

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.
Files changed (3) hide show
  1. package/dist/lib.js +671 -297
  2. package/package.json +1 -1
  3. package/src/lib.ts +746 -387
package/src/lib.ts CHANGED
@@ -481,44 +481,64 @@ export class ThaiTicketMajor {
481
481
  * Step 1: ดึงรอบการแสดงทั้งหมดจากหน้าหลักคอนเสิร์ต
482
482
  */
483
483
  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
- },
484
+ this.emitProgress("GET_ROUNDS", `Fetching rounds from ${eventUrl}`, {
485
+ url: eventUrl,
486
+ requestPayload: null,
491
487
  });
492
488
 
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,
489
+ try {
490
+ const response = await this.client.get(eventUrl, {
491
+ headers: {
492
+ ...this.defaultHeaders,
493
+ referer: "https://www.thaiticketmajor.com/all-event/",
494
+ },
495
+ });
496
+
497
+ const $ = cheerio.load(response.data);
498
+ const rounds: RoundInfo[] = [];
499
+
500
+ $("a[data-button]").each((_, el) => {
501
+ const link = $(el);
502
+ const href = link.attr("href") ?? "";
503
+ const onclick = link.attr("onclick") ?? "";
504
+ const dataUrl = link.attr("data-url") ?? "";
505
+ const dataHref = link.attr("data-href") ?? "";
506
+ const allLinkAttrs = `${href} ${onclick} ${dataUrl} ${dataHref}`;
507
+
508
+ const queryMatch = allLinkAttrs.match(/query=([0-9a-zA-Z_-]+)/);
509
+ const query = queryMatch ? queryMatch[1] : "";
510
+ const time = link.find(".item-show").text().trim();
511
+ const date = link.closest(".row").find(".date").text().trim();
512
+ const isDisabled = link.is(":disabled") || link.attr("disabled") !== undefined || !query;
513
+
514
+ rounds.push({
515
+ id: query || link.attr("data-button") || "",
516
+ label: `${date} ${time}`.trim(),
517
+ date,
518
+ time,
519
+ url: href,
520
+ query,
521
+ isDisabled,
522
+ });
518
523
  });
519
- });
520
524
 
521
- return rounds;
525
+ this.emitProgress("GET_ROUNDS_RES", `Found ${rounds.length} rounds`, {
526
+ url: eventUrl,
527
+ requestPayload: null,
528
+ responseStatus: response.status,
529
+ responseData: rounds,
530
+ });
531
+
532
+ return rounds;
533
+ } catch (err: any) {
534
+ this.emitProgress("GET_ROUNDS_ERR", `Failed to fetch rounds: ${err.message}`, {
535
+ url: eventUrl,
536
+ requestPayload: null,
537
+ responseStatus: err.response?.status,
538
+ responseData: err.response?.data || err.message,
539
+ });
540
+ throw err;
541
+ }
522
542
  }
523
543
 
524
544
  /**
@@ -529,113 +549,180 @@ export class ThaiTicketMajor {
529
549
  throw new Error("Query ID is missing. The selected round might be disabled or not open for sale yet.");
530
550
  }
531
551
 
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
- );
552
+ const url = `${this.endpoint}/verify_checkcondition.php`;
553
+ const payload = {
554
+ rdagree: "1",
555
+ rdId: "",
556
+ autopopup: "",
557
+ query: query,
558
+ };
559
+
560
+ this.emitProgress("VERIFY_CONDITION", `Verifying condition for query ${query}`, {
561
+ url,
562
+ requestPayload: payload,
563
+ });
564
+
565
+ try {
566
+ const response = await this.client.post(
567
+ url,
568
+ new URLSearchParams(payload),
569
+ { headers: this.defaultHeaders }
570
+ );
544
571
 
545
- return response.data;
572
+ this.emitProgress("VERIFY_CONDITION_RES", `Condition check: ${response.data?.result ? "PASS" : "FAIL"}`, {
573
+ url,
574
+ requestPayload: payload,
575
+ responseStatus: response.status,
576
+ responseData: response.data,
577
+ });
578
+
579
+ return response.data;
580
+ } catch (err: any) {
581
+ this.emitProgress("VERIFY_CONDITION_ERR", `Condition check error: ${err.message}`, {
582
+ url,
583
+ requestPayload: payload,
584
+ responseStatus: err.response?.status,
585
+ responseData: err.response?.data || err.message,
586
+ });
587
+ throw err;
588
+ }
546
589
  }
547
590
 
548
591
  /**
549
592
  * Step 3: ดึงข้อมูลโซน, Token (k, tk) และรอบการแสดง
550
593
  */
551
594
  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,
595
+ const url = `${this.endpoint}/zones.php?query=${query}`;
596
+ this.emitProgress("GET_ZONES", `Fetching zone data and tokens for query ${query}`, {
597
+ url,
598
+ requestPayload: { query },
556
599
  });
557
600
 
558
- const $ = cheerio.load(response.data);
559
- const rawInputs: Record<string, string> = {};
601
+ try {
602
+ const response = await this.client.get(url, {
603
+ headers: this.defaultHeaders,
604
+ });
560
605
 
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
- });
606
+ const $ = cheerio.load(response.data);
607
+ const rawInputs: Record<string, string> = {};
568
608
 
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();
609
+ $("#frm input[type='hidden']").each((_, el) => {
610
+ const name = $(el).attr("name");
611
+ const value = $(el).attr("value") ?? "";
612
+ if (name) {
613
+ rawInputs[name] = value;
614
+ }
615
+ });
579
616
 
580
- return {
581
- k: rawInputs["k"] || "",
582
- tk: rawInputs["tk"] || "",
583
- query,
584
- rounds,
585
- rawInputs,
586
- };
617
+ const rounds = $("#rdId option")
618
+ .filter((_, el) => !!$(el).attr("value"))
619
+ .map((_, el) => {
620
+ const opt = $(el);
621
+ return {
622
+ id: opt.attr("value") ?? "",
623
+ label: opt.text().trim(),
624
+ };
625
+ })
626
+ .get();
627
+
628
+ const result = {
629
+ k: rawInputs["k"] || "",
630
+ tk: rawInputs["tk"] || "",
631
+ query,
632
+ rounds,
633
+ rawInputs,
634
+ };
635
+
636
+ this.emitProgress("GET_ZONES_RES", `Extracted tokens k=${result.k ? result.k.slice(0, 10) + '...' : 'none'} and ${rounds.length} rounds`, {
637
+ url,
638
+ requestPayload: { query },
639
+ responseStatus: response.status,
640
+ responseData: result,
641
+ });
642
+
643
+ return result;
644
+ } catch (err: any) {
645
+ this.emitProgress("GET_ZONES_ERR", `Failed to get zones: ${err.message}`, {
646
+ url,
647
+ requestPayload: { query },
648
+ responseStatus: err.response?.status,
649
+ responseData: err.response?.data || err.message,
650
+ });
651
+ throw err;
652
+ }
587
653
  }
588
654
 
589
655
  /**
590
656
  * Step 4: ตรวจสอบสถานะที่นั่งว่างของแต่ละโซน
591
657
  */
592
658
  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
- },
659
+ const url = `${this.endpoint}/zonesavail.php?round=${roundId}&tk=${tk}`;
660
+ this.emitProgress("GET_ZONE_AVAIL", `Checking zone availability for round ${roundId}`, {
661
+ url,
662
+ requestPayload: { roundId, tk, query },
600
663
  });
601
664
 
602
- const $ = cheerio.load(response.data);
603
- const zones: ZoneItem[] = [];
665
+ try {
666
+ const response = await this.client.get(url, {
667
+ headers: {
668
+ ...this.defaultHeaders,
669
+ referer: `${this.endpoint}/zones.php?query=${query}`,
670
+ },
671
+ });
604
672
 
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\('([^']+)','([^']+)'/);
673
+ const $ = cheerio.load(response.data);
674
+ const zones: ZoneItem[] = [];
610
675
 
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();
676
+ $(".table tbody tr").each((_, el) => {
677
+ const row = $(el);
678
+ const zoneId = row.find("td:nth-child(1) a").attr("id") ?? "";
679
+ const onclick = row.attr("onclick") ?? "";
680
+ const match = onclick.match(/gonextstep\('([^']+)','([^']+)'/);
614
681
 
615
- let available = 0;
616
- let isAvailable = false;
682
+ const page = (match?.[1] ?? "") as "fixed.php" | "festival.php" | string;
683
+ const zoneValue = match?.[2] ?? "";
684
+ const statusText = row.find("td:nth-child(2) a").text().trim();
617
685
 
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;
686
+ let available = 0;
687
+ let isAvailable = false;
688
+
689
+ if (statusText.toLowerCase() === "available") {
690
+ available = 1;
625
691
  isAvailable = true;
692
+ } else {
693
+ const num = Number(statusText);
694
+ if (!Number.isNaN(num) && num > 0) {
695
+ available = num;
696
+ isAvailable = true;
697
+ }
626
698
  }
627
- }
628
699
 
629
- zones.push({
630
- id: zoneId,
631
- value: zoneValue,
632
- available,
633
- page,
634
- isAvailable,
700
+ zones.push({
701
+ id: zoneId,
702
+ value: zoneValue,
703
+ available,
704
+ page,
705
+ isAvailable,
706
+ });
707
+ });
708
+
709
+ this.emitProgress("GET_ZONE_AVAIL_RES", `Found ${zones.length} zones (${zones.filter(z => z.isAvailable).length} available)`, {
710
+ url,
711
+ requestPayload: { roundId, tk, query },
712
+ responseStatus: response.status,
713
+ responseData: zones,
635
714
  });
636
- });
637
715
 
638
- return zones;
716
+ return zones;
717
+ } catch (err: any) {
718
+ this.emitProgress("GET_ZONE_AVAIL_ERR", `Failed to check zone availability: ${err.message}`, {
719
+ url,
720
+ requestPayload: { roundId, tk, query },
721
+ responseStatus: err.response?.status,
722
+ responseData: err.response?.data || err.message,
723
+ });
724
+ throw err;
725
+ }
639
726
  }
640
727
 
641
728
  // --- Flow บัตรนั่ง (Fixed Seats) ---
@@ -644,33 +731,58 @@ export class ThaiTicketMajor {
644
731
  * Step 5 (Fixed): ดึงผังที่นั่งว่างและ form payload
645
732
  */
646
733
  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,
734
+ const url = `${this.endpoint}/fixed.php?k=${k}&zone=${zone}&round=${roundId}`;
735
+ this.emitProgress("GET_FIXED_SEATS", `Fetching seat layout for zone ${zone}`, {
736
+ url,
737
+ requestPayload: { k, zone, roundId },
651
738
  });
652
739
 
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
- });
740
+ try {
741
+ const response = await this.client.get(url, {
742
+ headers: this.defaultHeaders,
743
+ });
744
+
745
+ const $ = cheerio.load(response.data);
746
+ const seats: SeatItem[] = $("#tableseats .seatuncheck")
747
+ .map((_, el) => {
748
+ const seat = $(el);
749
+ return {
750
+ id: seat.attr("id") ?? "",
751
+ seat: seat.attr("data-seat") ?? "",
752
+ seatk: seat.attr("data-seatk") ?? "",
753
+ };
754
+ })
755
+ .get();
756
+
757
+ const form: Record<string, string> = {};
758
+ $("#frmPayment input").each((_, el) => {
759
+ const name = $(el).attr("name");
760
+ if (name) {
761
+ form[name] = $(el).attr("value") ?? "";
762
+ }
763
+ });
672
764
 
673
- return { seats, form };
765
+ this.emitProgress("GET_FIXED_SEATS_RES", `Found ${seats.length} available seats in zone ${zone}`, {
766
+ url,
767
+ requestPayload: { k, zone, roundId },
768
+ responseStatus: response.status,
769
+ responseData: {
770
+ seatsCount: seats.length,
771
+ seatsSample: seats.slice(0, 10),
772
+ form,
773
+ },
774
+ });
775
+
776
+ return { seats, form };
777
+ } catch (err: any) {
778
+ this.emitProgress("GET_FIXED_SEATS_ERR", `Failed to get fixed seats: ${err.message}`, {
779
+ url,
780
+ requestPayload: { k, zone, roundId },
781
+ responseStatus: err.response?.status,
782
+ responseData: err.response?.data || err.message,
783
+ });
784
+ throw err;
785
+ }
674
786
  }
675
787
 
676
788
  /**
@@ -732,22 +844,42 @@ export class ThaiTicketMajor {
732
844
  formData.append("seat", seatNo);
733
845
  formData.append("book_type", bookType);
734
846
 
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
- );
847
+ const url = `${this.endpoint}/validateseat.php?k=${k}&zw=${zone}`;
848
+ const reqPayloadObj = Object.fromEntries(formData.entries());
747
849
 
748
- lastResponse = response.data;
749
- if (!lastResponse.result) {
750
- throw new Error(`Validate seat failed (${currentSeat.seat}): ${lastResponse.message || "Unknown error"}`);
850
+ try {
851
+ const response = await this.client.post(
852
+ url,
853
+ formData.toString(),
854
+ {
855
+ headers: {
856
+ ...this.defaultHeaders,
857
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
858
+ "x-requested-with": "XMLHttpRequest",
859
+ referer: `${this.endpoint}/fixed.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
860
+ },
861
+ }
862
+ );
863
+
864
+ lastResponse = response.data;
865
+ this.emitProgress("VALIDATE_FIXED_SEATS_RES", `Seat validated (${currentSeat.seat}): ${lastResponse?.result ? "SUCCESS" : "FAIL"}`, {
866
+ url,
867
+ requestPayload: reqPayloadObj,
868
+ responseStatus: response.status,
869
+ responseData: lastResponse,
870
+ });
871
+
872
+ if (!lastResponse.result) {
873
+ throw new Error(`Validate seat failed (${currentSeat.seat}): ${lastResponse.message || "Unknown error"}`);
874
+ }
875
+ } catch (err: any) {
876
+ this.emitProgress("VALIDATE_FIXED_SEATS_ERR", `Validate seat failed (${currentSeat.seat}): ${err.message}`, {
877
+ url,
878
+ requestPayload: reqPayloadObj,
879
+ responseStatus: err.response?.status,
880
+ responseData: err.response?.data || err.message,
881
+ });
882
+ throw err;
751
883
  }
752
884
  }
753
885
 
@@ -800,25 +932,45 @@ export class ThaiTicketMajor {
800
932
  bodyParams.set("inclvat", "");
801
933
  bodyParams.set("seatklist", seatklistvalue);
802
934
 
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
- );
935
+ const url = `${this.endpoint}/bookingseats.php?k=${k}`;
936
+ const reqPayloadObj = Object.fromEntries(bodyParams.entries());
817
937
 
818
- return {
819
- data: response.data,
820
- nextPayload: bodyParams,
821
- };
938
+ try {
939
+ const response = await this.client.post(
940
+ url,
941
+ bodyParams.toString(),
942
+ {
943
+ headers: {
944
+ ...this.defaultHeaders,
945
+ accept: "application/json, text/javascript, */*; q=0.01",
946
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
947
+ "x-requested-with": "XMLHttpRequest",
948
+ referer: `${this.endpoint}/fixed.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
949
+ origin: "https://booking.thaiticketmajor.com",
950
+ },
951
+ }
952
+ );
953
+
954
+ this.emitProgress("BOOK_FIXED_SEATS_RES", `Book fixed seats completed`, {
955
+ url,
956
+ requestPayload: reqPayloadObj,
957
+ responseStatus: response.status,
958
+ responseData: response.data,
959
+ });
960
+
961
+ return {
962
+ data: response.data,
963
+ nextPayload: bodyParams,
964
+ };
965
+ } catch (err: any) {
966
+ this.emitProgress("BOOK_FIXED_SEATS_ERR", `Booking seats error: ${err.message}`, {
967
+ url,
968
+ requestPayload: reqPayloadObj,
969
+ responseStatus: err.response?.status,
970
+ responseData: err.response?.data || err.message,
971
+ });
972
+ throw err;
973
+ }
822
974
  }
823
975
 
824
976
  // --- Flow บัตรยืน (Festival / Standing) ---
@@ -827,26 +979,47 @@ export class ThaiTicketMajor {
827
979
  * Step 5 (Festival): ดึงข้อมูลหน้าบัตรยืน
828
980
  */
829
981
  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
- },
982
+ const url = `${this.endpoint}/festival.php?k=${k}&zone=${zone}&round=${roundId}`;
983
+ this.emitProgress("GET_FESTIVAL", `Fetching festival zone ${zone}`, {
984
+ url,
985
+ requestPayload: { k, tk, query, zone, roundId },
837
986
  });
838
987
 
839
- const $ = cheerio.load(response.data);
840
- const formData: Record<string, string> = {};
988
+ try {
989
+ const response = await this.client.get(url, {
990
+ headers: {
991
+ ...this.defaultHeaders,
992
+ referer: `${this.endpoint}/zones.php?rdId=${roundId}&k=${k}&tk=${tk}&query=${query}`,
993
+ },
994
+ });
841
995
 
842
- $("#frm input").each((_, el) => {
843
- const name = $(el).attr("name");
844
- if (name) {
845
- formData[name] = $(el).attr("value") ?? "";
846
- }
847
- });
996
+ const $ = cheerio.load(response.data);
997
+ const formData: Record<string, string> = {};
848
998
 
849
- return formData;
999
+ $("#frm input").each((_, el) => {
1000
+ const name = $(el).attr("name");
1001
+ if (name) {
1002
+ formData[name] = $(el).attr("value") ?? "";
1003
+ }
1004
+ });
1005
+
1006
+ this.emitProgress("GET_FESTIVAL_RES", `Loaded festival form (${Object.keys(formData).length} fields)`, {
1007
+ url,
1008
+ requestPayload: { k, tk, query, zone, roundId },
1009
+ responseStatus: response.status,
1010
+ responseData: formData,
1011
+ });
1012
+
1013
+ return formData;
1014
+ } catch (err: any) {
1015
+ this.emitProgress("GET_FESTIVAL_ERR", `Failed to get festival page: ${err.message}`, {
1016
+ url,
1017
+ requestPayload: { k, tk, query, zone, roundId },
1018
+ responseStatus: err.response?.status,
1019
+ responseData: err.response?.data || err.message,
1020
+ });
1021
+ throw err;
1022
+ }
850
1023
  }
851
1024
 
852
1025
  /**
@@ -862,21 +1035,39 @@ export class ThaiTicketMajor {
862
1035
  };
863
1036
 
864
1037
  const formData = new URLSearchParams(preparePayload);
1038
+ const url = `${this.endpoint}/validateseat.php?k=${k}&zw=${payload.zone}`;
865
1039
 
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
- );
1040
+ try {
1041
+ const response = await this.client.post(
1042
+ url,
1043
+ formData.toString(),
1044
+ {
1045
+ headers: {
1046
+ ...this.defaultHeaders,
1047
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1048
+ "x-requested-with": "XMLHttpRequest",
1049
+ referer: `${this.endpoint}/festival.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
1050
+ },
1051
+ }
1052
+ );
1053
+
1054
+ this.emitProgress("VALIDATE_FESTIVAL_RES", `Validate festival tickets: ${response.data?.result ? "SUCCESS" : "FAIL"}`, {
1055
+ url,
1056
+ requestPayload: preparePayload,
1057
+ responseStatus: response.status,
1058
+ responseData: response.data,
1059
+ });
878
1060
 
879
- return response.data;
1061
+ return response.data;
1062
+ } catch (err: any) {
1063
+ this.emitProgress("VALIDATE_FESTIVAL_ERR", `Validate festival error: ${err.message}`, {
1064
+ url,
1065
+ requestPayload: preparePayload,
1066
+ responseStatus: err.response?.status,
1067
+ responseData: err.response?.data || err.message,
1068
+ });
1069
+ throw err;
1070
+ }
880
1071
  }
881
1072
 
882
1073
  /**
@@ -891,21 +1082,39 @@ export class ThaiTicketMajor {
891
1082
  };
892
1083
 
893
1084
  const formData = new URLSearchParams(preparePayload);
1085
+ const url = `${this.endpoint}/bookingfestival.php?k=${k}`;
894
1086
 
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
- );
1087
+ try {
1088
+ const response = await this.client.post(
1089
+ url,
1090
+ formData.toString(),
1091
+ {
1092
+ headers: {
1093
+ ...this.defaultHeaders,
1094
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1095
+ "x-requested-with": "XMLHttpRequest",
1096
+ referer: `${this.endpoint}/festival.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
1097
+ },
1098
+ }
1099
+ );
907
1100
 
908
- return response.data;
1101
+ this.emitProgress("BOOK_FESTIVAL_RES", `Book festival tickets completed`, {
1102
+ url,
1103
+ requestPayload: preparePayload,
1104
+ responseStatus: response.status,
1105
+ responseData: response.data,
1106
+ });
1107
+
1108
+ return response.data;
1109
+ } catch (err: any) {
1110
+ this.emitProgress("BOOK_FESTIVAL_ERR", `Book festival error: ${err.message}`, {
1111
+ url,
1112
+ requestPayload: preparePayload,
1113
+ responseStatus: err.response?.status,
1114
+ responseData: err.response?.data || err.message,
1115
+ });
1116
+ throw err;
1117
+ }
909
1118
  }
910
1119
 
911
1120
  // --- Flow การลงทะเบียนผู้เข้าชม (Enrollment) ---
@@ -914,30 +1123,51 @@ export class ThaiTicketMajor {
914
1123
  * Step 8 (Enroll): ดึง Form ลงทะเบียนผู้เข้าชม
915
1124
  */
916
1125
  public async enroll(k: string, zone: string, roundId: string, payload: URLSearchParams | string): Promise<Record<string, string>> {
917
- this.emitProgress("GET_ENROLL", `Fetching enrollment form`);
1126
+ const url = `${this.endpoint}/enroll.php?k=${k}&zone=${zone}&round=${roundId}`;
1127
+ this.emitProgress("GET_ENROLL", `Fetching enrollment form`, {
1128
+ url,
1129
+ requestPayload: payload.toString(),
1130
+ });
918
1131
 
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
- );
1132
+ try {
1133
+ const response = await this.client.post(
1134
+ url,
1135
+ payload.toString(),
1136
+ {
1137
+ headers: {
1138
+ ...this.defaultHeaders,
1139
+ referer: `${this.endpoint}/fixed.php?k=${k}&zone=${zone}&round=${roundId}`,
1140
+ },
1141
+ }
1142
+ );
929
1143
 
930
- const $ = cheerio.load(response.data);
931
- const formData: Record<string, string> = {};
1144
+ const $ = cheerio.load(response.data);
1145
+ const formData: Record<string, string> = {};
932
1146
 
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
- });
1147
+ $("#form input, #form select").each((_, el) => {
1148
+ const name = $(el).attr("name");
1149
+ if (name) {
1150
+ formData[name] = $(el).attr("value") ?? $(el).find("option:selected").attr("value") ?? "";
1151
+ }
1152
+ });
1153
+
1154
+ this.emitProgress("GET_ENROLL_RES", `Loaded enrollment form (${Object.keys(formData).length} fields)`, {
1155
+ url,
1156
+ requestPayload: payload.toString(),
1157
+ responseStatus: response.status,
1158
+ responseData: formData,
1159
+ });
939
1160
 
940
- return formData;
1161
+ return formData;
1162
+ } catch (err: any) {
1163
+ this.emitProgress("GET_ENROLL_ERR", `Enroll form error: ${err.message}`, {
1164
+ url,
1165
+ requestPayload: payload.toString(),
1166
+ responseStatus: err.response?.status,
1167
+ responseData: err.response?.data || err.message,
1168
+ });
1169
+ throw err;
1170
+ }
941
1171
  }
942
1172
 
943
1173
  /**
@@ -948,7 +1178,11 @@ export class ThaiTicketMajor {
948
1178
  payload: Record<string, any>,
949
1179
  attendees: Attendee[]
950
1180
  ): Promise<{ data: any; nextPayload: URLSearchParams }> {
951
- this.emitProgress("ENROLL_PROCESS", `Submitting info for ${attendees.length} attendees`);
1181
+ const url = `${this.endpoint}/enroll_process.php?k=${k}`;
1182
+ this.emitProgress("ENROLL_PROCESS", `Submitting info for ${attendees.length} attendees`, {
1183
+ url,
1184
+ requestPayload: { attendeesCount: attendees.length, attendees },
1185
+ });
952
1186
 
953
1187
  const formData = new URLSearchParams();
954
1188
 
@@ -978,23 +1212,42 @@ export class ThaiTicketMajor {
978
1212
  }
979
1213
  }
980
1214
 
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
- );
1215
+ const reqPayloadObj = Object.fromEntries(formData.entries());
993
1216
 
994
- return {
995
- data: response.data,
996
- nextPayload: formData,
997
- };
1217
+ try {
1218
+ const response = await this.client.post(
1219
+ url,
1220
+ formData.toString(),
1221
+ {
1222
+ headers: {
1223
+ ...this.defaultHeaders,
1224
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1225
+ "x-requested-with": "XMLHttpRequest",
1226
+ referer: `${this.endpoint}/enroll.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
1227
+ },
1228
+ }
1229
+ );
1230
+
1231
+ this.emitProgress("ENROLL_PROCESS_RES", `Enrollment processed successfully`, {
1232
+ url,
1233
+ requestPayload: reqPayloadObj,
1234
+ responseStatus: response.status,
1235
+ responseData: response.data,
1236
+ });
1237
+
1238
+ return {
1239
+ data: response.data,
1240
+ nextPayload: formData,
1241
+ };
1242
+ } catch (err: any) {
1243
+ this.emitProgress("ENROLL_PROCESS_ERR", `Enrollment error: ${err.message}`, {
1244
+ url,
1245
+ requestPayload: reqPayloadObj,
1246
+ responseStatus: err.response?.status,
1247
+ responseData: err.response?.data || err.message,
1248
+ });
1249
+ throw err;
1250
+ }
998
1251
  }
999
1252
 
1000
1253
  // --- Flow การชำระเงิน (Payment) ---
@@ -1009,61 +1262,83 @@ export class ThaiTicketMajor {
1009
1262
  roundId: string,
1010
1263
  delayMs: number = 1000
1011
1264
  ): Promise<Record<string, string>> {
1012
- this.emitProgress("GET_PAYMENT_DETAILS", `Loading payment confirmation page`);
1013
-
1265
+ const url = `${this.endpoint}/paymentall.php?k=${k}`;
1014
1266
  const body = formData instanceof URLSearchParams ? formData.toString() : new URLSearchParams(formData).toString();
1267
+ const reqPayloadObj = formData instanceof URLSearchParams ? Object.fromEntries(formData.entries()) : formData;
1015
1268
 
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
- },
1269
+ this.emitProgress("GET_PAYMENT_DETAILS", `Loading payment confirmation page`, {
1270
+ url,
1271
+ requestPayload: reqPayloadObj,
1023
1272
  });
1024
1273
 
1025
- const $ = cheerio.load(response.data);
1026
- const hiddenInputs: Record<string, string> = {};
1274
+ try {
1275
+ const response = await this.client.post(url, body, {
1276
+ headers: {
1277
+ ...this.defaultHeaders,
1278
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1279
+ "x-requested-with": "XMLHttpRequest",
1280
+ referer: `${this.endpoint}/enroll.php?k=${k}&zone=${zone}&round=${roundId}`,
1281
+ },
1282
+ });
1283
+
1284
+ const $ = cheerio.load(response.data);
1285
+ const hiddenInputs: Record<string, string> = {};
1027
1286
 
1028
- $("#frm-confirm input").each((_, el) => {
1029
- const key = $(el).attr("name") ?? $(el).attr("id");
1030
- if (key) {
1031
- hiddenInputs[key] = $(el).val()?.toString() ?? "";
1287
+ $("#frm-confirm input").each((_, el) => {
1288
+ const key = $(el).attr("name") ?? $(el).attr("id");
1289
+ if (key) {
1290
+ hiddenInputs[key] = $(el).val()?.toString() ?? "";
1291
+ }
1292
+ });
1293
+
1294
+ const addressForm = $("#frm-address");
1295
+ if (addressForm.length > 0) {
1296
+ const fname = addressForm.find("input[name='c_fname_m']").val()?.toString() ?? "";
1297
+ const lname = addressForm.find("input[name='c_lname_m']").val()?.toString() ?? "";
1298
+ const address = addressForm.find("textarea[name='c_address_m']").val()?.toString() ?? "";
1299
+ const ctId = addressForm.find("select[name='c_ctCode'] option:selected").val()?.toString() ?? "";
1300
+ const pvId = addressForm.find("select[name='c_pvId'] option:selected").val()?.toString() ?? "";
1301
+ const amId = addressForm.find("select[name='c_amId'] option:selected").val()?.toString() ?? "";
1302
+ const zipcode = addressForm.find("input[name='c_PostCode']").val()?.toString() ?? "";
1303
+
1304
+ let rawPhone = addressForm.find("input[name='c_ContactRecipient']").val()?.toString() ?? "";
1305
+ if (rawPhone.startsWith("66")) {
1306
+ rawPhone = rawPhone.slice(2);
1307
+ }
1308
+ const phoneArea = addressForm.find("input[name='telephoneArea']").val()?.toString() ?? "66";
1309
+
1310
+ hiddenInputs["adr_fname"] = fname;
1311
+ hiddenInputs["adr_lname"] = lname;
1312
+ hiddenInputs["adr_address"] = address;
1313
+ hiddenInputs["adr_ctId"] = ctId;
1314
+ hiddenInputs["adr_pvId"] = pvId;
1315
+ hiddenInputs["adr_amId"] = amId;
1316
+ hiddenInputs["adr_zipcode"] = zipcode;
1317
+ hiddenInputs["adr_mobile"] = rawPhone.replace(/(\d{2})(\d{3})(\d{4})/, "$1 $2 $3");
1318
+ hiddenInputs["adr_mobilearea"] = phoneArea;
1032
1319
  }
1033
- });
1034
1320
 
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);
1321
+ this.emitProgress("GET_PAYMENT_DETAILS_RES", `Loaded payment confirmation details (${Object.keys(hiddenInputs).length} fields)`, {
1322
+ url,
1323
+ requestPayload: reqPayloadObj,
1324
+ responseStatus: response.status,
1325
+ responseData: hiddenInputs,
1326
+ });
1327
+
1328
+ if (delayMs > 0) {
1329
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1048
1330
  }
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
1331
 
1062
- if (delayMs > 0) {
1063
- await new Promise((resolve) => setTimeout(resolve, delayMs));
1332
+ return hiddenInputs;
1333
+ } catch (err: any) {
1334
+ this.emitProgress("GET_PAYMENT_DETAILS_ERR", `Payment details error: ${err.message}`, {
1335
+ url,
1336
+ requestPayload: reqPayloadObj,
1337
+ responseStatus: err.response?.status,
1338
+ responseData: err.response?.data || err.message,
1339
+ });
1340
+ throw err;
1064
1341
  }
1065
-
1066
- return hiddenInputs;
1067
1342
  }
1068
1343
 
1069
1344
  /**
@@ -1075,8 +1350,7 @@ export class ThaiTicketMajor {
1075
1350
  deliver: string = "1",
1076
1351
  payType: string = "KBQR"
1077
1352
  ): Promise<Record<string, string>> {
1078
- this.emitProgress("CONFIRM_PAYMENT", `Calculating total and confirming payment (${payType})`);
1079
-
1353
+ const url = `${this.endpoint}/paycfmall.php?k=${k}`;
1080
1354
  const cntTicket = parseInt(payload.cal_cntticket || "0", 10);
1081
1355
  const amountCode = parseFloat(payload.amountcode || "0");
1082
1356
  const deliverFee = parseFloat(payload.cal_deliverfee || payload.val_deliverfee || "80");
@@ -1095,6 +1369,11 @@ export class ThaiTicketMajor {
1095
1369
  cal_totalamount: Math.round(totalAmount).toString(),
1096
1370
  };
1097
1371
 
1372
+ this.emitProgress("CONFIRM_PAYMENT", `Calculating total and confirming payment (${payType})`, {
1373
+ url,
1374
+ requestPayload: computedPayload,
1375
+ });
1376
+
1098
1377
  const formData = new URLSearchParams();
1099
1378
  for (const [key, value] of Object.entries(computedPayload)) {
1100
1379
  if (key !== "check-protect") {
@@ -1102,92 +1381,137 @@ export class ThaiTicketMajor {
1102
1381
  }
1103
1382
  }
1104
1383
 
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
- });
1384
+ try {
1385
+ const response = await this.client.post(url, formData.toString(), {
1386
+ headers: {
1387
+ ...this.defaultHeaders,
1388
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1389
+ "x-requested-with": "XMLHttpRequest",
1390
+ referer: `${this.endpoint}/paymentall.php?k=${k}`,
1391
+ },
1392
+ });
1113
1393
 
1114
- const $ = cheerio.load(response.data);
1115
- const nextJsonData: Record<string, string> = {};
1394
+ const $ = cheerio.load(response.data);
1395
+ const nextJsonData: Record<string, string> = {};
1116
1396
 
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
- });
1397
+ $("#payallfrm input[type='hidden']").each((_, el) => {
1398
+ const name = $(el).attr("name");
1399
+ const value = $(el).attr("value") ?? "";
1400
+ if (name) {
1401
+ nextJsonData[name] = value;
1402
+ }
1403
+ });
1124
1404
 
1125
- return nextJsonData;
1405
+ this.emitProgress("CONFIRM_PAYMENT_RES", `Confirmed payment (Total: ฿${computedPayload.cal_totalamount})`, {
1406
+ url,
1407
+ requestPayload: computedPayload,
1408
+ responseStatus: response.status,
1409
+ responseData: nextJsonData,
1410
+ });
1411
+
1412
+ return nextJsonData;
1413
+ } catch (err: any) {
1414
+ this.emitProgress("CONFIRM_PAYMENT_ERR", `Payment confirmation error: ${err.message}`, {
1415
+ url,
1416
+ requestPayload: computedPayload,
1417
+ responseStatus: err.response?.status,
1418
+ responseData: err.response?.data || err.message,
1419
+ });
1420
+ throw err;
1421
+ }
1126
1422
  }
1127
1423
 
1128
1424
  /**
1129
1425
  * Step 12: ดึง payload เข้ารหัสสำหรับ KBank
1130
1426
  */
1131
1427
  public async orderEncKBankQR(k: string, payCfmData: Record<string, string>): Promise<Record<string, string>> {
1132
- this.emitProgress("ORDER_ENC_KBANK", `Encrypting order payload for KBank`);
1428
+ const url = `${this.endpoint}/orderenc_kbankqr.php`;
1429
+ this.emitProgress("ORDER_ENC_KBANK", `Encrypting order payload for KBank`, {
1430
+ url,
1431
+ requestPayload: payCfmData,
1432
+ });
1133
1433
 
1134
1434
  const formData = new URLSearchParams(payCfmData);
1135
1435
 
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
- });
1436
+ try {
1437
+ const response = await this.client.post(url, formData.toString(), {
1438
+ headers: {
1439
+ ...this.defaultHeaders,
1440
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1441
+ "x-requested-with": "XMLHttpRequest",
1442
+ referer: `${this.endpoint}/paycfmall.php?k=${k}`,
1443
+ },
1444
+ });
1144
1445
 
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
- });
1446
+ const $ = cheerio.load(response.data);
1447
+ const kbankqrData: Record<string, string> = {};
1151
1448
 
1152
- const $ = cheerio.load(response.data);
1153
- const kbankqrData: Record<string, string> = {};
1449
+ $("#kbankqr input[type='hidden']").each((_, el) => {
1450
+ const name = $(el).attr("name");
1451
+ const value = $(el).attr("value") ?? "";
1452
+ if (name) {
1453
+ kbankqrData[name] = value;
1454
+ }
1455
+ });
1154
1456
 
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
- });
1457
+ this.emitProgress("ORDER_ENC_KBANK_RES", `Received orderenc_kbankqr.php response`, {
1458
+ url,
1459
+ requestPayload: payCfmData,
1460
+ responseStatus: response.status,
1461
+ responseData: Object.keys(kbankqrData).length > 0 ? kbankqrData : response.data,
1462
+ });
1162
1463
 
1163
- return kbankqrData;
1464
+ return kbankqrData;
1465
+ } catch (err: any) {
1466
+ this.emitProgress("ORDER_ENC_KBANK_ERR", `Order encrypt error: ${err.message}`, {
1467
+ url,
1468
+ requestPayload: payCfmData,
1469
+ responseStatus: err.response?.status,
1470
+ responseData: err.response?.data || err.message,
1471
+ });
1472
+ throw err;
1473
+ }
1164
1474
  }
1165
1475
 
1166
1476
  /**
1167
1477
  * Step 13: เรียก getkbankqr.php
1168
1478
  */
1169
1479
  public async getKBankQR(orderEncData: Record<string, string>): Promise<any> {
1170
- this.emitProgress("GET_KBANK_QR", `Calling KBank Gateway info`);
1480
+ const url = `${this.endpoint}/getkbankqr.php`;
1481
+ this.emitProgress("GET_KBANK_QR", `Calling KBank Gateway info`, {
1482
+ url,
1483
+ requestPayload: orderEncData,
1484
+ });
1171
1485
 
1172
1486
  const formData = new URLSearchParams(orderEncData);
1173
1487
 
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
- });
1488
+ try {
1489
+ const response = await this.client.post(url, formData.toString(), {
1490
+ headers: {
1491
+ ...this.defaultHeaders,
1492
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1493
+ "x-requested-with": "XMLHttpRequest",
1494
+ referer: `${this.endpoint}/orderenc_kbankqr.php`,
1495
+ },
1496
+ });
1182
1497
 
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
- });
1498
+ this.emitProgress("GET_KBANK_QR_RES", `Received getkbankqr.php response`, {
1499
+ url,
1500
+ requestPayload: orderEncData,
1501
+ responseStatus: response.status,
1502
+ responseData: response.data,
1503
+ });
1189
1504
 
1190
- return response.data;
1505
+ return response.data;
1506
+ } catch (err: any) {
1507
+ this.emitProgress("GET_KBANK_QR_ERR", `Get KBank QR error: ${err.message}`, {
1508
+ url,
1509
+ requestPayload: orderEncData,
1510
+ responseStatus: err.response?.status,
1511
+ responseData: err.response?.data || err.message,
1512
+ });
1513
+ throw err;
1514
+ }
1191
1515
  }
1192
1516
 
1193
1517
  /**
@@ -1197,8 +1521,6 @@ export class ThaiTicketMajor {
1197
1521
  orderEncData: Record<string, string>,
1198
1522
  kbankPayload: Record<string, any>
1199
1523
  ): Promise<{ orderId: string; apiKey: string }> {
1200
- this.emitProgress("PAYMENT_KBANK_QR", `Extracting KBank OrderID and API Key`);
1201
-
1202
1524
  const preparePayload = {
1203
1525
  ...orderEncData,
1204
1526
  reasoncode: kbankPayload?.reasoncode,
@@ -1209,43 +1531,58 @@ export class ThaiTicketMajor {
1209
1531
  kbankqr_txExpiry: kbankPayload?.kbankqr_txExpiry,
1210
1532
  };
1211
1533
 
1534
+ const url = `${this.endpoint}/payment_kbankqr.php`;
1535
+ this.emitProgress("PAYMENT_KBANK_QR", `Extracting KBank OrderID and API Key`, {
1536
+ url,
1537
+ requestPayload: preparePayload,
1538
+ });
1539
+
1212
1540
  const formData = new URLSearchParams(preparePayload);
1213
1541
 
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
- });
1542
+ try {
1543
+ const response = await this.client.post(url, formData.toString(), {
1544
+ headers: {
1545
+ ...this.defaultHeaders,
1546
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1547
+ "x-requested-with": "XMLHttpRequest",
1548
+ referer: `${this.endpoint}/orderenc_kbankqr.php`,
1549
+ },
1550
+ });
1222
1551
 
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
- });
1552
+ const $ = cheerio.load(response.data);
1553
+ const script = $("#kbankpost script[data-order-id]");
1229
1554
 
1230
- const $ = cheerio.load(response.data);
1231
- const script = $("#kbankpost script[data-order-id]");
1555
+ const orderId = script.attr("data-order-id") ?? "";
1556
+ const apiKey = script.attr("data-apikey") ?? "";
1232
1557
 
1233
- const orderId = script.attr("data-order-id") ?? "";
1234
- const apiKey = script.attr("data-apikey") ?? "";
1558
+ this.emitProgress("PAYMENT_KBANK_QR_RES", `Received payment_kbankqr.php response (OrderID: ${orderId || "N/A"})`, {
1559
+ url,
1560
+ requestPayload: preparePayload,
1561
+ responseStatus: response.status,
1562
+ responseData: orderId && apiKey ? { orderId, apiKey, htmlSnippet: $("#kbankpost").html() } : response.data,
1563
+ });
1235
1564
 
1236
- if (!orderId || !apiKey) {
1237
- throw new Error("Unable to extract KBank orderId or apiKey from response");
1238
- }
1565
+ if (!orderId || !apiKey) {
1566
+ throw new Error("Unable to extract KBank orderId or apiKey from response");
1567
+ }
1239
1568
 
1240
- return { orderId, apiKey };
1569
+ return { orderId, apiKey };
1570
+ } catch (err: any) {
1571
+ this.emitProgress("PAYMENT_KBANK_QR_ERR", `Payment KBank QR error: ${err.message}`, {
1572
+ url,
1573
+ requestPayload: preparePayload,
1574
+ responseStatus: err.response?.status,
1575
+ responseData: err.response?.data || err.message,
1576
+ });
1577
+ throw err;
1578
+ }
1241
1579
  }
1242
1580
 
1243
1581
  /**
1244
1582
  * Step 15: ยิงตรง Kasikorn Bank Gateway เพื่อสร้าง ThaiQR Code
1245
1583
  */
1246
1584
  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
-
1585
+ const url = "https://kpaymentgateway-services.kasikornbank.com/qr/v2/qr";
1249
1586
  const payload = {
1250
1587
  amount: amount,
1251
1588
  currency: "THB",
@@ -1254,15 +1591,37 @@ export class ThaiTicketMajor {
1254
1591
  sof: "ThaiQR",
1255
1592
  };
1256
1593
 
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
- },
1594
+ this.emitProgress("GENERATE_THAI_QR", `Requesting ThaiQR code from Kasikorn Bank`, {
1595
+ url,
1596
+ requestPayload: payload,
1263
1597
  });
1264
1598
 
1265
- return response.data;
1599
+ try {
1600
+ const response = await this.client.post(url, payload, {
1601
+ headers: {
1602
+ ...this.defaultHeaders,
1603
+ referer: "https://kpaymentgateway.kasikornbank.com/",
1604
+ "x-api-key": apiKey,
1605
+ },
1606
+ });
1607
+
1608
+ this.emitProgress("GENERATE_THAI_QR_RES", `Received ThaiQR from Kasikorn Bank`, {
1609
+ url,
1610
+ requestPayload: payload,
1611
+ responseStatus: response.status,
1612
+ responseData: response.data,
1613
+ });
1614
+
1615
+ return response.data;
1616
+ } catch (err: any) {
1617
+ this.emitProgress("GENERATE_THAI_QR_ERR", `Kasikorn ThaiQR error: ${err.message}`, {
1618
+ url,
1619
+ requestPayload: payload,
1620
+ responseStatus: err.response?.status,
1621
+ responseData: err.response?.data || err.message,
1622
+ });
1623
+ throw err;
1624
+ }
1266
1625
  }
1267
1626
 
1268
1627
  // ==========================================