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.
- package/dist/lib.js +671 -297
- package/package.json +1 -1
- 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
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
time
|
|
515
|
-
|
|
516
|
-
query
|
|
517
|
-
|
|
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
|
-
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
-
|
|
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
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
-
|
|
559
|
-
|
|
601
|
+
try {
|
|
602
|
+
const response = await this.client.get(url, {
|
|
603
|
+
headers: this.defaultHeaders,
|
|
604
|
+
});
|
|
560
605
|
|
|
561
|
-
|
|
562
|
-
const
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
-
|
|
603
|
-
|
|
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
|
-
|
|
606
|
-
const
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
616
|
-
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
|
|
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
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
-
|
|
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
|
|
736
|
-
|
|
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
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
|
804
|
-
|
|
805
|
-
bodyParams.toString(),
|
|
806
|
-
{
|
|
807
|
-
headers: {
|
|
808
|
-
...this.defaultHeaders,
|
|
809
|
-
accept: "application/json, text/javascript, */*; q=0.01",
|
|
810
|
-
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
811
|
-
"x-requested-with": "XMLHttpRequest",
|
|
812
|
-
referer: `${this.endpoint}/fixed.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
|
|
813
|
-
origin: "https://booking.thaiticketmajor.com",
|
|
814
|
-
},
|
|
815
|
-
}
|
|
816
|
-
);
|
|
935
|
+
const url = `${this.endpoint}/bookingseats.php?k=${k}`;
|
|
936
|
+
const reqPayloadObj = Object.fromEntries(bodyParams.entries());
|
|
817
937
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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.
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
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
|
-
|
|
840
|
-
|
|
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
|
-
|
|
843
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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
|
-
|
|
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
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
-
|
|
931
|
-
|
|
1144
|
+
const $ = cheerio.load(response.data);
|
|
1145
|
+
const formData: Record<string, string> = {};
|
|
932
1146
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
1020
|
-
"x-requested-with": "XMLHttpRequest",
|
|
1021
|
-
referer: `${this.endpoint}/enroll.php?k=${k}&zone=${zone}&round=${roundId}`,
|
|
1022
|
-
},
|
|
1269
|
+
this.emitProgress("GET_PAYMENT_DETAILS", `Loading payment confirmation page`, {
|
|
1270
|
+
url,
|
|
1271
|
+
requestPayload: reqPayloadObj,
|
|
1023
1272
|
});
|
|
1024
1273
|
|
|
1025
|
-
|
|
1026
|
-
|
|
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
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
let rawPhone = addressForm.find("input[name='c_ContactRecipient']").val()?.toString() ?? "";
|
|
1046
|
-
if (rawPhone.startsWith("66")) {
|
|
1047
|
-
rawPhone = rawPhone.slice(2);
|
|
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
|
-
|
|
1063
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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
|
-
|
|
1115
|
-
|
|
1394
|
+
const $ = cheerio.load(response.data);
|
|
1395
|
+
const nextJsonData: Record<string, string> = {};
|
|
1116
1396
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
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
|
-
|
|
1146
|
-
|
|
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
|
-
|
|
1153
|
-
|
|
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
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
kbankqrData
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
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
|
-
|
|
1224
|
-
|
|
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
|
-
|
|
1231
|
-
|
|
1555
|
+
const orderId = script.attr("data-order-id") ?? "";
|
|
1556
|
+
const apiKey = script.attr("data-apikey") ?? "";
|
|
1232
1557
|
|
|
1233
|
-
|
|
1234
|
-
|
|
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
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1565
|
+
if (!orderId || !apiKey) {
|
|
1566
|
+
throw new Error("Unable to extract KBank orderId or apiKey from response");
|
|
1567
|
+
}
|
|
1239
1568
|
|
|
1240
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
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
|
-
|
|
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
|
// ==========================================
|