playttm 0.0.6 → 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/src/lib.ts CHANGED
@@ -261,30 +261,154 @@ export class ThaiTicketMajor {
261
261
  /**
262
262
  * Setup declarativeNetRequest session rules in Chrome Extension to set Referer header for API requests only.
263
263
  */
264
+ /**
265
+ * Setup declarativeNetRequest session rules in Chrome Extension to set Referer headers accurately for each endpoint.
266
+ */
264
267
  public static async setupExtensionRules(): Promise<void> {
265
268
  if (typeof chrome === "undefined" || !chrome?.declarativeNetRequest?.updateSessionRules) {
266
269
  return;
267
270
  }
268
271
 
269
272
  try {
273
+ const ruleIdsToRemove = [1001, 1002, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019];
270
274
  await chrome.declarativeNetRequest.updateSessionRules({
271
- removeRuleIds: [1001, 1002],
275
+ removeRuleIds: ruleIdsToRemove,
272
276
  addRules: [
273
277
  {
274
- id: 1001,
275
- priority: 1,
278
+ id: 1010,
279
+ priority: 20,
276
280
  action: {
277
281
  type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
278
282
  requestHeaders: [
279
283
  {
280
284
  header: "Referer",
281
285
  operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
282
- value: "https://booking.thaiticketmajor.com",
286
+ value: "https://booking.thaiticketmajor.com/booking/3m/orderenc_kbankqr.php",
283
287
  },
284
288
  ],
285
289
  },
286
290
  condition: {
287
- urlFilter: "thaiticketmajor.com",
291
+ urlFilter: "getkbankqr.php",
292
+ resourceTypes: [
293
+ chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
294
+ ],
295
+ },
296
+ },
297
+ {
298
+ id: 1011,
299
+ priority: 20,
300
+ action: {
301
+ type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
302
+ requestHeaders: [
303
+ {
304
+ header: "Referer",
305
+ operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
306
+ value: "https://booking.thaiticketmajor.com/booking/3m/orderenc_kbankqr.php",
307
+ },
308
+ ],
309
+ },
310
+ condition: {
311
+ urlFilter: "payment_kbankqr.php",
312
+ resourceTypes: [
313
+ chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
314
+ ],
315
+ },
316
+ },
317
+ {
318
+ id: 1012,
319
+ priority: 20,
320
+ action: {
321
+ type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
322
+ requestHeaders: [
323
+ {
324
+ header: "Referer",
325
+ operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
326
+ value: "https://booking.thaiticketmajor.com/booking/3m/paycfmall.php",
327
+ },
328
+ ],
329
+ },
330
+ condition: {
331
+ urlFilter: "orderenc_kbankqr.php",
332
+ resourceTypes: [
333
+ chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
334
+ ],
335
+ },
336
+ },
337
+ {
338
+ id: 1013,
339
+ priority: 20,
340
+ action: {
341
+ type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
342
+ requestHeaders: [
343
+ {
344
+ header: "Referer",
345
+ operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
346
+ value: "https://booking.thaiticketmajor.com/booking/3m/paymentall.php",
347
+ },
348
+ ],
349
+ },
350
+ condition: {
351
+ urlFilter: "paycfmall.php",
352
+ resourceTypes: [
353
+ chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
354
+ ],
355
+ },
356
+ },
357
+ {
358
+ id: 1014,
359
+ priority: 20,
360
+ action: {
361
+ type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
362
+ requestHeaders: [
363
+ {
364
+ header: "Referer",
365
+ operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
366
+ value: "https://booking.thaiticketmajor.com/booking/3m/fixed.php",
367
+ },
368
+ ],
369
+ },
370
+ condition: {
371
+ urlFilter: "paymentall.php",
372
+ resourceTypes: [
373
+ chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
374
+ ],
375
+ },
376
+ },
377
+ {
378
+ id: 1015,
379
+ priority: 20,
380
+ action: {
381
+ type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
382
+ requestHeaders: [
383
+ {
384
+ header: "Referer",
385
+ operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
386
+ value: "https://booking.thaiticketmajor.com/booking/3m/fixed.php",
387
+ },
388
+ ],
389
+ },
390
+ condition: {
391
+ urlFilter: "enroll",
392
+ resourceTypes: [
393
+ chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
394
+ ],
395
+ },
396
+ },
397
+ {
398
+ id: 1018,
399
+ priority: 20,
400
+ action: {
401
+ type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
402
+ requestHeaders: [
403
+ {
404
+ header: "Referer",
405
+ operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
406
+ value: "https://booking.thaiticketmajor.com/booking/3m/zones.php",
407
+ },
408
+ ],
409
+ },
410
+ condition: {
411
+ urlFilter: "zonesavail.php",
288
412
  resourceTypes: [
289
413
  chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
290
414
  ],
@@ -292,7 +416,7 @@ export class ThaiTicketMajor {
292
416
  },
293
417
  {
294
418
  id: 1002,
295
- priority: 1,
419
+ priority: 20,
296
420
  action: {
297
421
  type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
298
422
  requestHeaders: [
@@ -310,6 +434,26 @@ export class ThaiTicketMajor {
310
434
  ],
311
435
  },
312
436
  },
437
+ {
438
+ id: 1001,
439
+ priority: 1,
440
+ action: {
441
+ type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
442
+ requestHeaders: [
443
+ {
444
+ header: "Referer",
445
+ operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
446
+ value: "https://booking.thaiticketmajor.com/booking/3m/",
447
+ },
448
+ ],
449
+ },
450
+ condition: {
451
+ urlFilter: "thaiticketmajor.com",
452
+ resourceTypes: [
453
+ chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
454
+ ],
455
+ },
456
+ },
313
457
  ],
314
458
  });
315
459
  } catch (err) {
@@ -337,44 +481,64 @@ export class ThaiTicketMajor {
337
481
  * Step 1: ดึงรอบการแสดงทั้งหมดจากหน้าหลักคอนเสิร์ต
338
482
  */
339
483
  public async getRounds(eventUrl: string): Promise<RoundInfo[]> {
340
- this.emitProgress("GET_ROUNDS", `Fetching rounds from ${eventUrl}`);
341
-
342
- const response = await this.client.get(eventUrl, {
343
- headers: {
344
- ...this.defaultHeaders,
345
- referer: "https://www.thaiticketmajor.com/all-event/",
346
- },
484
+ this.emitProgress("GET_ROUNDS", `Fetching rounds from ${eventUrl}`, {
485
+ url: eventUrl,
486
+ requestPayload: null,
347
487
  });
348
488
 
349
- const $ = cheerio.load(response.data);
350
- const rounds: RoundInfo[] = [];
351
-
352
- $("a[data-button]").each((_, el) => {
353
- const link = $(el);
354
- const href = link.attr("href") ?? "";
355
- const onclick = link.attr("onclick") ?? "";
356
- const dataUrl = link.attr("data-url") ?? "";
357
- const dataHref = link.attr("data-href") ?? "";
358
- const allLinkAttrs = `${href} ${onclick} ${dataUrl} ${dataHref}`;
359
-
360
- const queryMatch = allLinkAttrs.match(/query=([0-9a-zA-Z_-]+)/);
361
- const query = queryMatch ? queryMatch[1] : "";
362
- const time = link.find(".item-show").text().trim();
363
- const date = link.closest(".row").find(".date").text().trim();
364
- const isDisabled = link.is(":disabled") || link.attr("disabled") !== undefined || !query;
365
-
366
- rounds.push({
367
- id: query || link.attr("data-button") || "",
368
- label: `${date} ${time}`.trim(),
369
- date,
370
- time,
371
- url: href,
372
- query,
373
- 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
+ });
523
+ });
524
+
525
+ this.emitProgress("GET_ROUNDS_RES", `Found ${rounds.length} rounds`, {
526
+ url: eventUrl,
527
+ requestPayload: null,
528
+ responseStatus: response.status,
529
+ responseData: rounds,
374
530
  });
375
- });
376
531
 
377
- return rounds;
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
+ }
378
542
  }
379
543
 
380
544
  /**
@@ -385,113 +549,180 @@ export class ThaiTicketMajor {
385
549
  throw new Error("Query ID is missing. The selected round might be disabled or not open for sale yet.");
386
550
  }
387
551
 
388
- this.emitProgress("VERIFY_CONDITION", `Verifying condition for query ${query}`);
389
-
390
- const response = await this.client.post(
391
- `${this.endpoint}/verify_checkcondition.php`,
392
- new URLSearchParams({
393
- rdagree: "1",
394
- rdId: "",
395
- autopopup: "",
396
- query: query,
397
- }),
398
- { headers: this.defaultHeaders }
399
- );
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
+ );
571
+
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
+ });
400
578
 
401
- return response.data;
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
+ }
402
589
  }
403
590
 
404
591
  /**
405
592
  * Step 3: ดึงข้อมูลโซน, Token (k, tk) และรอบการแสดง
406
593
  */
407
594
  public async getZones(query: string): Promise<{ k: string; tk: string; query: string; rounds: { id: string; label: string }[]; rawInputs: Record<string, string> }> {
408
- this.emitProgress("GET_ZONES", `Fetching zone data and tokens for query ${query}`);
409
-
410
- const response = await this.client.get(`${this.endpoint}/zones.php?query=${query}`, {
411
- 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 },
412
599
  });
413
600
 
414
- const $ = cheerio.load(response.data);
415
- const rawInputs: Record<string, string> = {};
601
+ try {
602
+ const response = await this.client.get(url, {
603
+ headers: this.defaultHeaders,
604
+ });
416
605
 
417
- $("#frm input[type='hidden']").each((_, el) => {
418
- const name = $(el).attr("name");
419
- const value = $(el).attr("value") ?? "";
420
- if (name) {
421
- rawInputs[name] = value;
422
- }
423
- });
606
+ const $ = cheerio.load(response.data);
607
+ const rawInputs: Record<string, string> = {};
424
608
 
425
- const rounds = $("#rdId option")
426
- .filter((_, el) => !!$(el).attr("value"))
427
- .map((_, el) => {
428
- const opt = $(el);
429
- return {
430
- id: opt.attr("value") ?? "",
431
- label: opt.text().trim(),
432
- };
433
- })
434
- .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
+ });
435
616
 
436
- return {
437
- k: rawInputs["k"] || "",
438
- tk: rawInputs["tk"] || "",
439
- query,
440
- rounds,
441
- rawInputs,
442
- };
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
+ }
443
653
  }
444
654
 
445
655
  /**
446
656
  * Step 4: ตรวจสอบสถานะที่นั่งว่างของแต่ละโซน
447
657
  */
448
658
  public async getZoneAvailability(roundId: string, tk: string, k: string, query: string): Promise<ZoneItem[]> {
449
- this.emitProgress("GET_ZONE_AVAIL", `Checking zone availability for round ${roundId}`);
450
-
451
- const response = await this.client.get(`${this.endpoint}/zonesavail.php?round=${roundId}&tk=${tk}`, {
452
- headers: {
453
- ...this.defaultHeaders,
454
- referer: `${this.endpoint}/zones.php?query=${query}`,
455
- },
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 },
456
663
  });
457
664
 
458
- const $ = cheerio.load(response.data);
459
- 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
+ });
672
+
673
+ const $ = cheerio.load(response.data);
674
+ const zones: ZoneItem[] = [];
460
675
 
461
- $(".table tbody tr").each((_, el) => {
462
- const row = $(el);
463
- const zoneId = row.find("td:nth-child(1) a").attr("id") ?? "";
464
- const onclick = row.attr("onclick") ?? "";
465
- const match = onclick.match(/gonextstep\('([^']+)','([^']+)'/);
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\('([^']+)','([^']+)'/);
466
681
 
467
- const page = (match?.[1] ?? "") as "fixed.php" | "festival.php" | string;
468
- const zoneValue = match?.[2] ?? "";
469
- const statusText = row.find("td:nth-child(2) a").text().trim();
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();
470
685
 
471
- let available = 0;
472
- let isAvailable = false;
686
+ let available = 0;
687
+ let isAvailable = false;
473
688
 
474
- if (statusText.toLowerCase() === "available") {
475
- available = 1;
476
- isAvailable = true;
477
- } else {
478
- const num = Number(statusText);
479
- if (!Number.isNaN(num) && num > 0) {
480
- available = num;
689
+ if (statusText.toLowerCase() === "available") {
690
+ available = 1;
481
691
  isAvailable = true;
692
+ } else {
693
+ const num = Number(statusText);
694
+ if (!Number.isNaN(num) && num > 0) {
695
+ available = num;
696
+ isAvailable = true;
697
+ }
482
698
  }
483
- }
484
699
 
485
- zones.push({
486
- id: zoneId,
487
- value: zoneValue,
488
- available,
489
- page,
490
- 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,
491
714
  });
492
- });
493
715
 
494
- 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
+ }
495
726
  }
496
727
 
497
728
  // --- Flow บัตรนั่ง (Fixed Seats) ---
@@ -500,33 +731,58 @@ export class ThaiTicketMajor {
500
731
  * Step 5 (Fixed): ดึงผังที่นั่งว่างและ form payload
501
732
  */
502
733
  public async getFixedSeats(k: string, zone: string, roundId: string): Promise<{ seats: SeatItem[]; form: Record<string, string> }> {
503
- this.emitProgress("GET_FIXED_SEATS", `Fetching seat layout for zone ${zone}`);
504
-
505
- const response = await this.client.get(`${this.endpoint}/fixed.php?k=${k}&zone=${zone}&round=${roundId}`, {
506
- 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 },
507
738
  });
508
739
 
509
- const $ = cheerio.load(response.data);
510
- const seats: SeatItem[] = $("#tableseats .seatuncheck")
511
- .map((_, el) => {
512
- const seat = $(el);
513
- return {
514
- id: seat.attr("id") ?? "",
515
- seat: seat.attr("data-seat") ?? "",
516
- seatk: seat.attr("data-seatk") ?? "",
517
- };
518
- })
519
- .get();
520
-
521
- const form: Record<string, string> = {};
522
- $("#frmPayment input").each((_, el) => {
523
- const name = $(el).attr("name");
524
- if (name) {
525
- form[name] = $(el).attr("value") ?? "";
526
- }
527
- });
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
+ });
528
764
 
529
- 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
+ }
530
786
  }
531
787
 
532
788
  /**
@@ -588,22 +844,42 @@ export class ThaiTicketMajor {
588
844
  formData.append("seat", seatNo);
589
845
  formData.append("book_type", bookType);
590
846
 
591
- const response = await this.client.post(
592
- `${this.endpoint}/validateseat.php?k=${k}&zw=${zone}`,
593
- formData.toString(),
594
- {
595
- headers: {
596
- ...this.defaultHeaders,
597
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
598
- "x-requested-with": "XMLHttpRequest",
599
- referer: `${this.endpoint}/fixed.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
600
- },
601
- }
602
- );
847
+ const url = `${this.endpoint}/validateseat.php?k=${k}&zw=${zone}`;
848
+ const reqPayloadObj = Object.fromEntries(formData.entries());
849
+
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
+ });
603
871
 
604
- lastResponse = response.data;
605
- if (!lastResponse.result) {
606
- throw new Error(`Validate seat failed (${currentSeat.seat}): ${lastResponse.message || "Unknown error"}`);
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;
607
883
  }
608
884
  }
609
885
 
@@ -656,25 +932,45 @@ export class ThaiTicketMajor {
656
932
  bodyParams.set("inclvat", "");
657
933
  bodyParams.set("seatklist", seatklistvalue);
658
934
 
659
- const response = await this.client.post(
660
- `${this.endpoint}/bookingseats.php?k=${k}`,
661
- bodyParams.toString(),
662
- {
663
- headers: {
664
- ...this.defaultHeaders,
665
- accept: "application/json, text/javascript, */*; q=0.01",
666
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
667
- "x-requested-with": "XMLHttpRequest",
668
- referer: `${this.endpoint}/fixed.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
669
- origin: "https://booking.thaiticketmajor.com",
670
- },
671
- }
672
- );
935
+ const url = `${this.endpoint}/bookingseats.php?k=${k}`;
936
+ const reqPayloadObj = Object.fromEntries(bodyParams.entries());
673
937
 
674
- return {
675
- data: response.data,
676
- nextPayload: bodyParams,
677
- };
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
+ }
678
974
  }
679
975
 
680
976
  // --- Flow บัตรยืน (Festival / Standing) ---
@@ -683,26 +979,47 @@ export class ThaiTicketMajor {
683
979
  * Step 5 (Festival): ดึงข้อมูลหน้าบัตรยืน
684
980
  */
685
981
  public async getFestival(k: string, tk: string, query: string, zone: string, roundId: string): Promise<Record<string, string>> {
686
- this.emitProgress("GET_FESTIVAL", `Fetching festival zone ${zone}`);
687
-
688
- const response = await this.client.get(`${this.endpoint}/festival.php?k=${k}&zone=${zone}&round=${roundId}`, {
689
- headers: {
690
- ...this.defaultHeaders,
691
- referer: `${this.endpoint}/zones.php?rdId=${roundId}&k=${k}&tk=${tk}&query=${query}`,
692
- },
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 },
693
986
  });
694
987
 
695
- const $ = cheerio.load(response.data);
696
- 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
+ });
995
+
996
+ const $ = cheerio.load(response.data);
997
+ const formData: Record<string, string> = {};
697
998
 
698
- $("#frm input").each((_, el) => {
699
- const name = $(el).attr("name");
700
- if (name) {
701
- formData[name] = $(el).attr("value") ?? "";
702
- }
703
- });
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
+ });
704
1012
 
705
- return formData;
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
+ }
706
1023
  }
707
1024
 
708
1025
  /**
@@ -718,21 +1035,39 @@ export class ThaiTicketMajor {
718
1035
  };
719
1036
 
720
1037
  const formData = new URLSearchParams(preparePayload);
1038
+ const url = `${this.endpoint}/validateseat.php?k=${k}&zw=${payload.zone}`;
721
1039
 
722
- const response = await this.client.post(
723
- `${this.endpoint}/validateseat.php?k=${k}&zw=${payload.zone}`,
724
- formData.toString(),
725
- {
726
- headers: {
727
- ...this.defaultHeaders,
728
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
729
- "x-requested-with": "XMLHttpRequest",
730
- referer: `${this.endpoint}/festival.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
731
- },
732
- }
733
- );
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
+ });
734
1060
 
735
- 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
+ }
736
1071
  }
737
1072
 
738
1073
  /**
@@ -747,21 +1082,39 @@ export class ThaiTicketMajor {
747
1082
  };
748
1083
 
749
1084
  const formData = new URLSearchParams(preparePayload);
1085
+ const url = `${this.endpoint}/bookingfestival.php?k=${k}`;
750
1086
 
751
- const response = await this.client.post(
752
- `${this.endpoint}/bookingfestival.php?k=${k}`,
753
- formData.toString(),
754
- {
755
- headers: {
756
- ...this.defaultHeaders,
757
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
758
- "x-requested-with": "XMLHttpRequest",
759
- referer: `${this.endpoint}/festival.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
760
- },
761
- }
762
- );
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
+ );
763
1100
 
764
- 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
+ }
765
1118
  }
766
1119
 
767
1120
  // --- Flow การลงทะเบียนผู้เข้าชม (Enrollment) ---
@@ -770,30 +1123,51 @@ export class ThaiTicketMajor {
770
1123
  * Step 8 (Enroll): ดึง Form ลงทะเบียนผู้เข้าชม
771
1124
  */
772
1125
  public async enroll(k: string, zone: string, roundId: string, payload: URLSearchParams | string): Promise<Record<string, string>> {
773
- 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
+ });
774
1131
 
775
- const response = await this.client.post(
776
- `${this.endpoint}/enroll.php?k=${k}&zone=${zone}&round=${roundId}`,
777
- payload.toString(),
778
- {
779
- headers: {
780
- ...this.defaultHeaders,
781
- referer: `${this.endpoint}/fixed.php?k=${k}&zone=${zone}&round=${roundId}`,
782
- },
783
- }
784
- );
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
+ );
785
1143
 
786
- const $ = cheerio.load(response.data);
787
- const formData: Record<string, string> = {};
1144
+ const $ = cheerio.load(response.data);
1145
+ const formData: Record<string, string> = {};
788
1146
 
789
- $("#form input, #form select").each((_, el) => {
790
- const name = $(el).attr("name");
791
- if (name) {
792
- formData[name] = $(el).attr("value") ?? $(el).find("option:selected").attr("value") ?? "";
793
- }
794
- });
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
+ });
795
1160
 
796
- 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
+ }
797
1171
  }
798
1172
 
799
1173
  /**
@@ -804,7 +1178,11 @@ export class ThaiTicketMajor {
804
1178
  payload: Record<string, any>,
805
1179
  attendees: Attendee[]
806
1180
  ): Promise<{ data: any; nextPayload: URLSearchParams }> {
807
- 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
+ });
808
1186
 
809
1187
  const formData = new URLSearchParams();
810
1188
 
@@ -834,23 +1212,42 @@ export class ThaiTicketMajor {
834
1212
  }
835
1213
  }
836
1214
 
837
- const response = await this.client.post(
838
- `${this.endpoint}/enroll_process.php?k=${k}`,
839
- formData.toString(),
840
- {
841
- headers: {
842
- ...this.defaultHeaders,
843
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
844
- "x-requested-with": "XMLHttpRequest",
845
- referer: `${this.endpoint}/enroll.php?k=${k}&zone=${payload.zone}&round=${payload.rdId}`,
846
- },
847
- }
848
- );
1215
+ const reqPayloadObj = Object.fromEntries(formData.entries());
849
1216
 
850
- return {
851
- data: response.data,
852
- nextPayload: formData,
853
- };
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
+ }
854
1251
  }
855
1252
 
856
1253
  // --- Flow การชำระเงิน (Payment) ---
@@ -865,61 +1262,83 @@ export class ThaiTicketMajor {
865
1262
  roundId: string,
866
1263
  delayMs: number = 1000
867
1264
  ): Promise<Record<string, string>> {
868
- this.emitProgress("GET_PAYMENT_DETAILS", `Loading payment confirmation page`);
869
-
1265
+ const url = `${this.endpoint}/paymentall.php?k=${k}`;
870
1266
  const body = formData instanceof URLSearchParams ? formData.toString() : new URLSearchParams(formData).toString();
1267
+ const reqPayloadObj = formData instanceof URLSearchParams ? Object.fromEntries(formData.entries()) : formData;
871
1268
 
872
- const response = await this.client.post(`${this.endpoint}/paymentall.php?k=${k}`, body, {
873
- headers: {
874
- ...this.defaultHeaders,
875
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
876
- "x-requested-with": "XMLHttpRequest",
877
- referer: `${this.endpoint}/enroll.php?k=${k}&zone=${zone}&round=${roundId}`,
878
- },
1269
+ this.emitProgress("GET_PAYMENT_DETAILS", `Loading payment confirmation page`, {
1270
+ url,
1271
+ requestPayload: reqPayloadObj,
879
1272
  });
880
1273
 
881
- const $ = cheerio.load(response.data);
882
- 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> = {};
1286
+
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
+ });
883
1293
 
884
- $("#frm-confirm input").each((_, el) => {
885
- const key = $(el).attr("name") ?? $(el).attr("id");
886
- if (key) {
887
- hiddenInputs[key] = $(el).val()?.toString() ?? "";
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;
888
1319
  }
889
- });
890
1320
 
891
- const addressForm = $("#frm-address");
892
- if (addressForm.length > 0) {
893
- const fname = addressForm.find("input[name='c_fname_m']").val()?.toString() ?? "";
894
- const lname = addressForm.find("input[name='c_lname_m']").val()?.toString() ?? "";
895
- const address = addressForm.find("textarea[name='c_address_m']").val()?.toString() ?? "";
896
- const ctId = addressForm.find("select[name='c_ctCode'] option:selected").val()?.toString() ?? "";
897
- const pvId = addressForm.find("select[name='c_pvId'] option:selected").val()?.toString() ?? "";
898
- const amId = addressForm.find("select[name='c_amId'] option:selected").val()?.toString() ?? "";
899
- const zipcode = addressForm.find("input[name='c_PostCode']").val()?.toString() ?? "";
900
-
901
- let rawPhone = addressForm.find("input[name='c_ContactRecipient']").val()?.toString() ?? "";
902
- if (rawPhone.startsWith("66")) {
903
- 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));
904
1330
  }
905
- const phoneArea = addressForm.find("input[name='telephoneArea']").val()?.toString() ?? "66";
906
-
907
- hiddenInputs["adr_fname"] = fname;
908
- hiddenInputs["adr_lname"] = lname;
909
- hiddenInputs["adr_address"] = address;
910
- hiddenInputs["adr_ctId"] = ctId;
911
- hiddenInputs["adr_pvId"] = pvId;
912
- hiddenInputs["adr_amId"] = amId;
913
- hiddenInputs["adr_zipcode"] = zipcode;
914
- hiddenInputs["adr_mobile"] = rawPhone.replace(/(\d{2})(\d{3})(\d{4})/, "$1 $2 $3");
915
- hiddenInputs["adr_mobilearea"] = phoneArea;
916
- }
917
1331
 
918
- if (delayMs > 0) {
919
- 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;
920
1341
  }
921
-
922
- return hiddenInputs;
923
1342
  }
924
1343
 
925
1344
  /**
@@ -931,8 +1350,7 @@ export class ThaiTicketMajor {
931
1350
  deliver: string = "1",
932
1351
  payType: string = "KBQR"
933
1352
  ): Promise<Record<string, string>> {
934
- this.emitProgress("CONFIRM_PAYMENT", `Calculating total and confirming payment (${payType})`);
935
-
1353
+ const url = `${this.endpoint}/paycfmall.php?k=${k}`;
936
1354
  const cntTicket = parseInt(payload.cal_cntticket || "0", 10);
937
1355
  const amountCode = parseFloat(payload.amountcode || "0");
938
1356
  const deliverFee = parseFloat(payload.cal_deliverfee || payload.val_deliverfee || "80");
@@ -951,6 +1369,11 @@ export class ThaiTicketMajor {
951
1369
  cal_totalamount: Math.round(totalAmount).toString(),
952
1370
  };
953
1371
 
1372
+ this.emitProgress("CONFIRM_PAYMENT", `Calculating total and confirming payment (${payType})`, {
1373
+ url,
1374
+ requestPayload: computedPayload,
1375
+ });
1376
+
954
1377
  const formData = new URLSearchParams();
955
1378
  for (const [key, value] of Object.entries(computedPayload)) {
956
1379
  if (key !== "check-protect") {
@@ -958,78 +1381,137 @@ export class ThaiTicketMajor {
958
1381
  }
959
1382
  }
960
1383
 
961
- const response = await this.client.post(`${this.endpoint}/paycfmall.php?k=${k}`, formData.toString(), {
962
- headers: {
963
- ...this.defaultHeaders,
964
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
965
- "x-requested-with": "XMLHttpRequest",
966
- referer: `${this.endpoint}/paymentall.php?k=${k}`,
967
- },
968
- });
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
+ });
969
1393
 
970
- const $ = cheerio.load(response.data);
971
- const nextJsonData: Record<string, string> = {};
1394
+ const $ = cheerio.load(response.data);
1395
+ const nextJsonData: Record<string, string> = {};
972
1396
 
973
- $("#payallfrm input[type='hidden']").each((_, el) => {
974
- const name = $(el).attr("name");
975
- const value = $(el).attr("value") ?? "";
976
- if (name) {
977
- nextJsonData[name] = value;
978
- }
979
- });
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
+ });
980
1404
 
981
- 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
+ }
982
1422
  }
983
1423
 
984
1424
  /**
985
1425
  * Step 12: ดึง payload เข้ารหัสสำหรับ KBank
986
1426
  */
987
1427
  public async orderEncKBankQR(k: string, payCfmData: Record<string, string>): Promise<Record<string, string>> {
988
- 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
+ });
989
1433
 
990
1434
  const formData = new URLSearchParams(payCfmData);
991
1435
 
992
- const response = await this.client.post(`${this.endpoint}/orderenc_kbankqr.php`, formData.toString(), {
993
- headers: {
994
- ...this.defaultHeaders,
995
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
996
- "x-requested-with": "XMLHttpRequest",
997
- referer: `${this.endpoint}/paycfmall.php?k=${k}`,
998
- },
999
- });
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
+ });
1000
1445
 
1001
- const $ = cheerio.load(response.data);
1002
- const kbankqrData: Record<string, string> = {};
1446
+ const $ = cheerio.load(response.data);
1447
+ const kbankqrData: Record<string, string> = {};
1003
1448
 
1004
- $("#kbankqr input[type='hidden']").each((_, el) => {
1005
- const name = $(el).attr("name");
1006
- const value = $(el).attr("value") ?? "";
1007
- if (name) {
1008
- kbankqrData[name] = value;
1009
- }
1010
- });
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
+ });
1456
+
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
+ });
1011
1463
 
1012
- 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
+ }
1013
1474
  }
1014
1475
 
1015
1476
  /**
1016
1477
  * Step 13: เรียก getkbankqr.php
1017
1478
  */
1018
1479
  public async getKBankQR(orderEncData: Record<string, string>): Promise<any> {
1019
- 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
+ });
1020
1485
 
1021
1486
  const formData = new URLSearchParams(orderEncData);
1022
1487
 
1023
- const response = await this.client.post(`${this.endpoint}/getkbankqr.php`, formData.toString(), {
1024
- headers: {
1025
- ...this.defaultHeaders,
1026
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1027
- "x-requested-with": "XMLHttpRequest",
1028
- referer: `${this.endpoint}/orderenc_kbankqr.php`,
1029
- },
1030
- });
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
+ });
1497
+
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
+ });
1031
1504
 
1032
- 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
+ }
1033
1515
  }
1034
1516
 
1035
1517
  /**
@@ -1039,48 +1521,68 @@ export class ThaiTicketMajor {
1039
1521
  orderEncData: Record<string, string>,
1040
1522
  kbankPayload: Record<string, any>
1041
1523
  ): Promise<{ orderId: string; apiKey: string }> {
1042
- this.emitProgress("PAYMENT_KBANK_QR", `Extracting KBank OrderID and API Key`);
1043
-
1044
1524
  const preparePayload = {
1045
1525
  ...orderEncData,
1046
- reasoncode: kbankPayload.reasoncode,
1047
- eventname: kbankPayload.eventname,
1048
- rounddetail: kbankPayload.rounddetail,
1049
- totalamount: kbankPayload.totalamount,
1050
- orderstatus: kbankPayload.orderstatus,
1051
- kbankqr_txExpiry: kbankPayload.kbankqr_txExpiry,
1526
+ reasoncode: kbankPayload?.reasoncode,
1527
+ eventname: kbankPayload?.eventname,
1528
+ rounddetail: kbankPayload?.rounddetail,
1529
+ totalamount: kbankPayload?.totalamount,
1530
+ orderstatus: kbankPayload?.orderstatus,
1531
+ kbankqr_txExpiry: kbankPayload?.kbankqr_txExpiry,
1052
1532
  };
1053
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
+
1054
1540
  const formData = new URLSearchParams(preparePayload);
1055
1541
 
1056
- const response = await this.client.post(`${this.endpoint}/payment_kbankqr.php`, formData.toString(), {
1057
- headers: {
1058
- ...this.defaultHeaders,
1059
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1060
- "x-requested-with": "XMLHttpRequest",
1061
- referer: `${this.endpoint}/orderenc_kbankqr.php`,
1062
- },
1063
- });
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
+ });
1064
1551
 
1065
- const $ = cheerio.load(response.data);
1066
- const script = $("#kbankpost script[data-order-id]");
1552
+ const $ = cheerio.load(response.data);
1553
+ const script = $("#kbankpost script[data-order-id]");
1067
1554
 
1068
- const orderId = script.attr("data-order-id") ?? "";
1069
- const apiKey = script.attr("data-apikey") ?? "";
1555
+ const orderId = script.attr("data-order-id") ?? "";
1556
+ const apiKey = script.attr("data-apikey") ?? "";
1070
1557
 
1071
- if (!orderId || !apiKey) {
1072
- throw new Error("Unable to extract KBank orderId or apiKey from response");
1073
- }
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
+ });
1564
+
1565
+ if (!orderId || !apiKey) {
1566
+ throw new Error("Unable to extract KBank orderId or apiKey from response");
1567
+ }
1074
1568
 
1075
- 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
+ }
1076
1579
  }
1077
1580
 
1078
1581
  /**
1079
1582
  * Step 15: ยิงตรง Kasikorn Bank Gateway เพื่อสร้าง ThaiQR Code
1080
1583
  */
1081
1584
  public async generateThaiQR(orderId: string, apiKey: string, amount: string | number): Promise<any> {
1082
- this.emitProgress("GENERATE_THAI_QR", `Requesting ThaiQR code from Kasikorn Bank`);
1083
-
1585
+ const url = "https://kpaymentgateway-services.kasikornbank.com/qr/v2/qr";
1084
1586
  const payload = {
1085
1587
  amount: amount,
1086
1588
  currency: "THB",
@@ -1089,15 +1591,37 @@ export class ThaiTicketMajor {
1089
1591
  sof: "ThaiQR",
1090
1592
  };
1091
1593
 
1092
- const response = await this.client.post("https://kpaymentgateway-services.kasikornbank.com/qr/v2/qr", payload, {
1093
- headers: {
1094
- ...this.defaultHeaders,
1095
- referer: "https://kpaymentgateway.kasikornbank.com/",
1096
- "x-api-key": apiKey,
1097
- },
1594
+ this.emitProgress("GENERATE_THAI_QR", `Requesting ThaiQR code from Kasikorn Bank`, {
1595
+ url,
1596
+ requestPayload: payload,
1098
1597
  });
1099
1598
 
1100
- 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
+ }
1101
1625
  }
1102
1626
 
1103
1627
  // ==========================================