playttm 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  export declare const isBrowserEnvironment: () => boolean;
2
+ /**
3
+ * Extract transaction amount from EMVCo / ThaiQR payload (Tag 54)
4
+ */
5
+ export declare const parseThaiQRAmount: (qrCode?: string) => string | null;
2
6
  export interface TTMConfig {
3
7
  cookies?: string;
4
8
  headers?: Record<string, string>;
@@ -81,12 +85,17 @@ export declare class ThaiTicketMajor {
81
85
  inBrowser: boolean;
82
86
  private rawCookies?;
83
87
  constructor(config?: TTMConfig);
88
+ static readonly BOT_PROTECTION_COOKIES: Set<string>;
84
89
  /**
85
90
  * Synchronize a cookie string into Chrome Extension's cookie store (chrome.cookies API).
91
+ * Automatically ignores bot protection cookies (Akamai _abck, bm_*, WAF) to prevent Access Denied (403) bans.
86
92
  */
87
93
  static syncCookiesToChrome(cookieString: string, domainUrl?: string): Promise<void>;
88
94
  /**
89
- * Setup declarativeNetRequest session rules in Chrome Extension to set Referer and Origin headers
95
+ * Setup declarativeNetRequest session rules in Chrome Extension to set Referer header for API requests only.
96
+ */
97
+ /**
98
+ * Setup declarativeNetRequest session rules in Chrome Extension to set Referer headers accurately for each endpoint.
90
99
  */
91
100
  static setupExtensionRules(): Promise<void>;
92
101
  syncCookies(domainUrl?: string): Promise<void>;
package/dist/lib.js CHANGED
@@ -45,7 +45,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
45
45
  return (mod && mod.__esModule) ? mod : { "default": mod };
46
46
  };
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.displayQRCode = exports.ThaiTicketMajor = exports.isBrowserEnvironment = void 0;
48
+ exports.displayQRCode = exports.ThaiTicketMajor = exports.parseThaiQRAmount = exports.isBrowserEnvironment = void 0;
49
49
  const axios_1 = __importDefault(require("axios"));
50
50
  const cheerio = __importStar(require("cheerio"));
51
51
  const axios_cookiejar_support_1 = require("axios-cookiejar-support");
@@ -64,6 +64,23 @@ const isBrowserEnvironment = () => {
64
64
  return false;
65
65
  };
66
66
  exports.isBrowserEnvironment = isBrowserEnvironment;
67
+ /**
68
+ * Extract transaction amount from EMVCo / ThaiQR payload (Tag 54)
69
+ */
70
+ const parseThaiQRAmount = (qrCode) => {
71
+ if (!qrCode)
72
+ return null;
73
+ const match = qrCode.match(/(?:^|[^0-9])54(\d{2})([0-9.]+)/);
74
+ if (match) {
75
+ const len = parseInt(match[1], 10);
76
+ const amountStr = match[2].substring(0, len);
77
+ if (!isNaN(parseFloat(amountStr))) {
78
+ return amountStr;
79
+ }
80
+ }
81
+ return null;
82
+ };
83
+ exports.parseThaiQRAmount = parseThaiQRAmount;
67
84
  // ==========================================
68
85
  // Main Library Class
69
86
  // ==========================================
@@ -109,6 +126,7 @@ class ThaiTicketMajor {
109
126
  }
110
127
  /**
111
128
  * Synchronize a cookie string into Chrome Extension's cookie store (chrome.cookies API).
129
+ * Automatically ignores bot protection cookies (Akamai _abck, bm_*, WAF) to prevent Access Denied (403) bans.
112
130
  */
113
131
  static syncCookiesToChrome(cookieString_1) {
114
132
  return __awaiter(this, arguments, void 0, function* (cookieString, domainUrl = "https://booking.thaiticketmajor.com") {
@@ -128,6 +146,11 @@ class ThaiTicketMajor {
128
146
  const value = trimmed.substring(eqIndex + 1).trim();
129
147
  if (!name)
130
148
  continue;
149
+ // DO NOT overwrite Akamai / WAF bot tokens!
150
+ // Writing stale _abck or bm_* triggers Akamai Bot Protection -> Access Denied.
151
+ if (ThaiTicketMajor.BOT_PROTECTION_COOKIES.has(name.toLowerCase())) {
152
+ continue;
153
+ }
131
154
  try {
132
155
  yield chrome.cookies.set({
133
156
  url: domainUrl,
@@ -152,55 +175,171 @@ class ThaiTicketMajor {
152
175
  });
153
176
  }
154
177
  /**
155
- * Setup declarativeNetRequest session rules in Chrome Extension to set Referer and Origin headers
178
+ * Setup declarativeNetRequest session rules in Chrome Extension to set Referer header for API requests only.
179
+ */
180
+ /**
181
+ * Setup declarativeNetRequest session rules in Chrome Extension to set Referer headers accurately for each endpoint.
156
182
  */
157
183
  static setupExtensionRules() {
158
184
  return __awaiter(this, void 0, void 0, function* () {
159
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
185
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3;
160
186
  if (typeof chrome === "undefined" || !((_a = chrome === null || chrome === void 0 ? void 0 : chrome.declarativeNetRequest) === null || _a === void 0 ? void 0 : _a.updateSessionRules)) {
161
187
  return;
162
188
  }
163
189
  try {
190
+ const ruleIdsToRemove = [1001, 1002, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019];
164
191
  yield chrome.declarativeNetRequest.updateSessionRules({
165
- removeRuleIds: [1001, 1002],
192
+ removeRuleIds: ruleIdsToRemove,
166
193
  addRules: [
167
194
  {
168
- id: 1001,
169
- priority: 1,
195
+ id: 1010,
196
+ priority: 20,
170
197
  action: {
171
198
  type: ((_b = chrome.declarativeNetRequest.RuleActionType) === null || _b === void 0 ? void 0 : _b.MODIFY_HEADERS) || "modifyHeaders",
172
199
  requestHeaders: [
173
200
  {
174
201
  header: "Referer",
175
202
  operation: ((_c = chrome.declarativeNetRequest.HeaderOperation) === null || _c === void 0 ? void 0 : _c.SET) || "set",
176
- value: "https://booking.thaiticketmajor.com",
203
+ value: "https://booking.thaiticketmajor.com/booking/3m/orderenc_kbankqr.php",
177
204
  },
205
+ ],
206
+ },
207
+ condition: {
208
+ urlFilter: "getkbankqr.php",
209
+ resourceTypes: [
210
+ ((_d = chrome.declarativeNetRequest.ResourceType) === null || _d === void 0 ? void 0 : _d.XMLHTTPREQUEST) || "xmlhttprequest",
211
+ ],
212
+ },
213
+ },
214
+ {
215
+ id: 1011,
216
+ priority: 20,
217
+ action: {
218
+ type: ((_e = chrome.declarativeNetRequest.RuleActionType) === null || _e === void 0 ? void 0 : _e.MODIFY_HEADERS) || "modifyHeaders",
219
+ requestHeaders: [
178
220
  {
179
- header: "Origin",
180
- operation: ((_d = chrome.declarativeNetRequest.HeaderOperation) === null || _d === void 0 ? void 0 : _d.SET) || "set",
181
- value: "https://booking.thaiticketmajor.com",
221
+ header: "Referer",
222
+ operation: ((_f = chrome.declarativeNetRequest.HeaderOperation) === null || _f === void 0 ? void 0 : _f.SET) || "set",
223
+ value: "https://booking.thaiticketmajor.com/booking/3m/orderenc_kbankqr.php",
182
224
  },
183
225
  ],
184
226
  },
185
227
  condition: {
186
- urlFilter: "thaiticketmajor.com",
228
+ urlFilter: "payment_kbankqr.php",
229
+ resourceTypes: [
230
+ ((_g = chrome.declarativeNetRequest.ResourceType) === null || _g === void 0 ? void 0 : _g.XMLHTTPREQUEST) || "xmlhttprequest",
231
+ ],
232
+ },
233
+ },
234
+ {
235
+ id: 1012,
236
+ priority: 20,
237
+ action: {
238
+ type: ((_h = chrome.declarativeNetRequest.RuleActionType) === null || _h === void 0 ? void 0 : _h.MODIFY_HEADERS) || "modifyHeaders",
239
+ requestHeaders: [
240
+ {
241
+ header: "Referer",
242
+ operation: ((_j = chrome.declarativeNetRequest.HeaderOperation) === null || _j === void 0 ? void 0 : _j.SET) || "set",
243
+ value: "https://booking.thaiticketmajor.com/booking/3m/paycfmall.php",
244
+ },
245
+ ],
246
+ },
247
+ condition: {
248
+ urlFilter: "orderenc_kbankqr.php",
249
+ resourceTypes: [
250
+ ((_k = chrome.declarativeNetRequest.ResourceType) === null || _k === void 0 ? void 0 : _k.XMLHTTPREQUEST) || "xmlhttprequest",
251
+ ],
252
+ },
253
+ },
254
+ {
255
+ id: 1013,
256
+ priority: 20,
257
+ action: {
258
+ type: ((_l = chrome.declarativeNetRequest.RuleActionType) === null || _l === void 0 ? void 0 : _l.MODIFY_HEADERS) || "modifyHeaders",
259
+ requestHeaders: [
260
+ {
261
+ header: "Referer",
262
+ operation: ((_m = chrome.declarativeNetRequest.HeaderOperation) === null || _m === void 0 ? void 0 : _m.SET) || "set",
263
+ value: "https://booking.thaiticketmajor.com/booking/3m/paymentall.php",
264
+ },
265
+ ],
266
+ },
267
+ condition: {
268
+ urlFilter: "paycfmall.php",
269
+ resourceTypes: [
270
+ ((_o = chrome.declarativeNetRequest.ResourceType) === null || _o === void 0 ? void 0 : _o.XMLHTTPREQUEST) || "xmlhttprequest",
271
+ ],
272
+ },
273
+ },
274
+ {
275
+ id: 1014,
276
+ priority: 20,
277
+ action: {
278
+ type: ((_p = chrome.declarativeNetRequest.RuleActionType) === null || _p === void 0 ? void 0 : _p.MODIFY_HEADERS) || "modifyHeaders",
279
+ requestHeaders: [
280
+ {
281
+ header: "Referer",
282
+ operation: ((_q = chrome.declarativeNetRequest.HeaderOperation) === null || _q === void 0 ? void 0 : _q.SET) || "set",
283
+ value: "https://booking.thaiticketmajor.com/booking/3m/fixed.php",
284
+ },
285
+ ],
286
+ },
287
+ condition: {
288
+ urlFilter: "paymentall.php",
187
289
  resourceTypes: [
188
- ((_e = chrome.declarativeNetRequest.ResourceType) === null || _e === void 0 ? void 0 : _e.XMLHTTPREQUEST) || "xmlhttprequest",
189
- ((_f = chrome.declarativeNetRequest.ResourceType) === null || _f === void 0 ? void 0 : _f.SUB_FRAME) || "sub_frame",
190
- ((_g = chrome.declarativeNetRequest.ResourceType) === null || _g === void 0 ? void 0 : _g.MAIN_FRAME) || "main_frame",
191
- ((_h = chrome.declarativeNetRequest.ResourceType) === null || _h === void 0 ? void 0 : _h.OTHER) || "other",
290
+ ((_r = chrome.declarativeNetRequest.ResourceType) === null || _r === void 0 ? void 0 : _r.XMLHTTPREQUEST) || "xmlhttprequest",
291
+ ],
292
+ },
293
+ },
294
+ {
295
+ id: 1015,
296
+ priority: 20,
297
+ action: {
298
+ type: ((_s = chrome.declarativeNetRequest.RuleActionType) === null || _s === void 0 ? void 0 : _s.MODIFY_HEADERS) || "modifyHeaders",
299
+ requestHeaders: [
300
+ {
301
+ header: "Referer",
302
+ operation: ((_t = chrome.declarativeNetRequest.HeaderOperation) === null || _t === void 0 ? void 0 : _t.SET) || "set",
303
+ value: "https://booking.thaiticketmajor.com/booking/3m/fixed.php",
304
+ },
305
+ ],
306
+ },
307
+ condition: {
308
+ urlFilter: "enroll",
309
+ resourceTypes: [
310
+ ((_u = chrome.declarativeNetRequest.ResourceType) === null || _u === void 0 ? void 0 : _u.XMLHTTPREQUEST) || "xmlhttprequest",
311
+ ],
312
+ },
313
+ },
314
+ {
315
+ id: 1018,
316
+ priority: 20,
317
+ action: {
318
+ type: ((_v = chrome.declarativeNetRequest.RuleActionType) === null || _v === void 0 ? void 0 : _v.MODIFY_HEADERS) || "modifyHeaders",
319
+ requestHeaders: [
320
+ {
321
+ header: "Referer",
322
+ operation: ((_w = chrome.declarativeNetRequest.HeaderOperation) === null || _w === void 0 ? void 0 : _w.SET) || "set",
323
+ value: "https://booking.thaiticketmajor.com/booking/3m/zones.php",
324
+ },
325
+ ],
326
+ },
327
+ condition: {
328
+ urlFilter: "zonesavail.php",
329
+ resourceTypes: [
330
+ ((_x = chrome.declarativeNetRequest.ResourceType) === null || _x === void 0 ? void 0 : _x.XMLHTTPREQUEST) || "xmlhttprequest",
192
331
  ],
193
332
  },
194
333
  },
195
334
  {
196
335
  id: 1002,
197
- priority: 1,
336
+ priority: 20,
198
337
  action: {
199
- type: ((_j = chrome.declarativeNetRequest.RuleActionType) === null || _j === void 0 ? void 0 : _j.MODIFY_HEADERS) || "modifyHeaders",
338
+ type: ((_y = chrome.declarativeNetRequest.RuleActionType) === null || _y === void 0 ? void 0 : _y.MODIFY_HEADERS) || "modifyHeaders",
200
339
  requestHeaders: [
201
340
  {
202
341
  header: "Referer",
203
- operation: ((_k = chrome.declarativeNetRequest.HeaderOperation) === null || _k === void 0 ? void 0 : _k.SET) || "set",
342
+ operation: ((_z = chrome.declarativeNetRequest.HeaderOperation) === null || _z === void 0 ? void 0 : _z.SET) || "set",
204
343
  value: "https://kpaymentgateway.kasikornbank.com/",
205
344
  },
206
345
  ],
@@ -208,8 +347,27 @@ class ThaiTicketMajor {
208
347
  condition: {
209
348
  urlFilter: "kasikornbank.com",
210
349
  resourceTypes: [
211
- ((_l = chrome.declarativeNetRequest.ResourceType) === null || _l === void 0 ? void 0 : _l.XMLHTTPREQUEST) || "xmlhttprequest",
212
- ((_m = chrome.declarativeNetRequest.ResourceType) === null || _m === void 0 ? void 0 : _m.OTHER) || "other",
350
+ ((_0 = chrome.declarativeNetRequest.ResourceType) === null || _0 === void 0 ? void 0 : _0.XMLHTTPREQUEST) || "xmlhttprequest",
351
+ ],
352
+ },
353
+ },
354
+ {
355
+ id: 1001,
356
+ priority: 1,
357
+ action: {
358
+ type: ((_1 = chrome.declarativeNetRequest.RuleActionType) === null || _1 === void 0 ? void 0 : _1.MODIFY_HEADERS) || "modifyHeaders",
359
+ requestHeaders: [
360
+ {
361
+ header: "Referer",
362
+ operation: ((_2 = chrome.declarativeNetRequest.HeaderOperation) === null || _2 === void 0 ? void 0 : _2.SET) || "set",
363
+ value: "https://booking.thaiticketmajor.com/booking/3m/",
364
+ },
365
+ ],
366
+ },
367
+ condition: {
368
+ urlFilter: "thaiticketmajor.com",
369
+ resourceTypes: [
370
+ ((_3 = chrome.declarativeNetRequest.ResourceType) === null || _3 === void 0 ? void 0 : _3.XMLHTTPREQUEST) || "xmlhttprequest",
213
371
  ],
214
372
  },
215
373
  },
@@ -724,6 +882,12 @@ class ThaiTicketMajor {
724
882
  const response = yield this.client.post(`${this.endpoint}/orderenc_kbankqr.php`, formData.toString(), {
725
883
  headers: Object.assign(Object.assign({}, this.defaultHeaders), { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/paycfmall.php?k=${k}` }),
726
884
  });
885
+ this.emitProgress("ORDER_ENC_KBANK_RES", `Received orderenc_kbankqr.php response`, {
886
+ url: `${this.endpoint}/orderenc_kbankqr.php`,
887
+ requestPayload: payCfmData,
888
+ responseStatus: response.status,
889
+ responseData: response.data,
890
+ });
727
891
  const $ = cheerio.load(response.data);
728
892
  const kbankqrData = {};
729
893
  $("#kbankqr input[type='hidden']").each((_, el) => {
@@ -747,6 +911,12 @@ class ThaiTicketMajor {
747
911
  const response = yield this.client.post(`${this.endpoint}/getkbankqr.php`, formData.toString(), {
748
912
  headers: Object.assign(Object.assign({}, this.defaultHeaders), { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/orderenc_kbankqr.php` }),
749
913
  });
914
+ this.emitProgress("GET_KBANK_QR_RES", `Received getkbankqr.php response`, {
915
+ url: `${this.endpoint}/getkbankqr.php`,
916
+ requestPayload: orderEncData,
917
+ responseStatus: response.status,
918
+ responseData: response.data,
919
+ });
750
920
  return response.data;
751
921
  });
752
922
  }
@@ -757,11 +927,17 @@ class ThaiTicketMajor {
757
927
  return __awaiter(this, void 0, void 0, function* () {
758
928
  var _a, _b;
759
929
  this.emitProgress("PAYMENT_KBANK_QR", `Extracting KBank OrderID and API Key`);
760
- const preparePayload = Object.assign(Object.assign({}, orderEncData), { reasoncode: kbankPayload.reasoncode, eventname: kbankPayload.eventname, rounddetail: kbankPayload.rounddetail, totalamount: kbankPayload.totalamount, orderstatus: kbankPayload.orderstatus, kbankqr_txExpiry: kbankPayload.kbankqr_txExpiry });
930
+ const preparePayload = Object.assign(Object.assign({}, orderEncData), { reasoncode: kbankPayload === null || kbankPayload === void 0 ? void 0 : kbankPayload.reasoncode, eventname: kbankPayload === null || kbankPayload === void 0 ? void 0 : kbankPayload.eventname, rounddetail: kbankPayload === null || kbankPayload === void 0 ? void 0 : kbankPayload.rounddetail, totalamount: kbankPayload === null || kbankPayload === void 0 ? void 0 : kbankPayload.totalamount, orderstatus: kbankPayload === null || kbankPayload === void 0 ? void 0 : kbankPayload.orderstatus, kbankqr_txExpiry: kbankPayload === null || kbankPayload === void 0 ? void 0 : kbankPayload.kbankqr_txExpiry });
761
931
  const formData = new URLSearchParams(preparePayload);
762
932
  const response = yield this.client.post(`${this.endpoint}/payment_kbankqr.php`, formData.toString(), {
763
933
  headers: Object.assign(Object.assign({}, this.defaultHeaders), { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-requested-with": "XMLHttpRequest", referer: `${this.endpoint}/orderenc_kbankqr.php` }),
764
934
  });
935
+ this.emitProgress("PAYMENT_KBANK_QR_RES", `Received payment_kbankqr.php response`, {
936
+ url: `${this.endpoint}/payment_kbankqr.php`,
937
+ requestPayload: preparePayload,
938
+ responseStatus: response.status,
939
+ responseData: response.data,
940
+ });
765
941
  const $ = cheerio.load(response.data);
766
942
  const script = $("#kbankpost script[data-order-id]");
767
943
  const orderId = (_a = script.attr("data-order-id")) !== null && _a !== void 0 ? _a : "";
@@ -799,7 +975,7 @@ class ThaiTicketMajor {
799
975
  */
800
976
  quickBook(options) {
801
977
  return __awaiter(this, void 0, void 0, function* () {
802
- var _a, _b, _c, _d;
978
+ var _a, _b, _c, _d, _e;
803
979
  const { eventUrl, roundIndex = 0, roundId, zoneId, ticketCount = 1, seatSelection = "random", attendees = [], deliveryType = "1", payType = "KBQR", paymentDelayMs = 1500, } = options;
804
980
  if (options.onProgress) {
805
981
  this.onProgressCallback = options.onProgress;
@@ -928,7 +1104,14 @@ class ThaiTicketMajor {
928
1104
  const orderEncData = yield this.orderEncKBankQR(zoneData.k, payCfmData);
929
1105
  const kbankInfo = yield this.getKBankQR(orderEncData);
930
1106
  const kbankAuth = yield this.paymentKBankQR(orderEncData, kbankInfo);
931
- const qrResult = yield this.generateThaiQR(kbankAuth.orderId, kbankAuth.apiKey, orderEncData.amount);
1107
+ const targetAmount = orderEncData.transactionamount ||
1108
+ orderEncData.totalamount ||
1109
+ orderEncData.amount ||
1110
+ payCfmData.amount ||
1111
+ kbankInfo.totalamount ||
1112
+ paymentAllData["cal_totalamount"] ||
1113
+ "0";
1114
+ const qrResult = yield this.generateThaiQR(kbankAuth.orderId, kbankAuth.apiKey, targetAmount);
932
1115
  const qrRawCode = (qrResult === null || qrResult === void 0 ? void 0 : qrResult.paint_text) ||
933
1116
  (qrResult === null || qrResult === void 0 ? void 0 : qrResult.raw_qr_code) ||
934
1117
  (qrResult === null || qrResult === void 0 ? void 0 : qrResult.qr_code) ||
@@ -936,8 +1119,14 @@ class ThaiTicketMajor {
936
1119
  ((_c = qrResult === null || qrResult === void 0 ? void 0 : qrResult.data) === null || _c === void 0 ? void 0 : _c.qr_code) ||
937
1120
  ((_d = qrResult === null || qrResult === void 0 ? void 0 : qrResult.data) === null || _d === void 0 ? void 0 : _d.raw_qr_code) ||
938
1121
  (typeof qrResult === "string" ? qrResult : "");
1122
+ const qrAmount = (0, exports.parseThaiQRAmount)(qrRawCode);
1123
+ const finalAmount = qrAmount ||
1124
+ (targetAmount && targetAmount !== "0" ? targetAmount : null) ||
1125
+ (qrResult === null || qrResult === void 0 ? void 0 : qrResult.amount) ||
1126
+ ((_e = qrResult === null || qrResult === void 0 ? void 0 : qrResult.data) === null || _e === void 0 ? void 0 : _e.amount) ||
1127
+ "0";
939
1128
  const displayQR = (customTitle) => {
940
- const title = customTitle || `ThaiQR Payment (KBank) - ยอดชำระ: ${orderEncData.amount || paymentAllData["cal_totalamount"]} บาท`;
1129
+ const title = customTitle || `ThaiQR Payment (KBank) - ยอดชำระ: ${finalAmount} บาท`;
941
1130
  ThaiTicketMajor.displayQRCode(qrRawCode, title);
942
1131
  };
943
1132
  if (options.printQR !== false && qrRawCode) {
@@ -946,7 +1135,7 @@ class ThaiTicketMajor {
946
1135
  return {
947
1136
  success: true,
948
1137
  orderId: kbankAuth.orderId,
949
- amount: orderEncData.amount || paymentAllData["cal_totalamount"],
1138
+ amount: String(finalAmount),
950
1139
  qrRawCode,
951
1140
  qrData: qrResult,
952
1141
  displayQR,
@@ -957,7 +1146,7 @@ class ThaiTicketMajor {
957
1146
  venue: paymentAllData["venue"],
958
1147
  zone: targetZone.value,
959
1148
  ticketCount,
960
- totalAmount: orderEncData.amount || paymentAllData["cal_totalamount"],
1149
+ totalAmount: String(finalAmount),
961
1150
  seats: summarySeats.length > 0 ? summarySeats : undefined,
962
1151
  },
963
1152
  rawPayload: {
@@ -989,5 +1178,24 @@ class ThaiTicketMajor {
989
1178
  }
990
1179
  }
991
1180
  exports.ThaiTicketMajor = ThaiTicketMajor;
1181
+ ThaiTicketMajor.BOT_PROTECTION_COOKIES = new Set([
1182
+ "_abck",
1183
+ "bm_sz",
1184
+ "ak_bmsc",
1185
+ "bm_sv",
1186
+ "bm_mi",
1187
+ "hwwafsesid",
1188
+ "hwwafsestime",
1189
+ "__cf_bm",
1190
+ "cf_clearance",
1191
+ "_ga",
1192
+ "_gid",
1193
+ "_gcl_au",
1194
+ "_fbp",
1195
+ "_clck",
1196
+ "_clsk",
1197
+ "_ttp",
1198
+ "_twpid",
1199
+ ]);
992
1200
  exports.displayQRCode = ThaiTicketMajor.displayQRCode;
993
1201
  exports.default = ThaiTicketMajor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playttm",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Make HTML Metadata to JSON",
5
5
  "main": "dist/lib.js",
6
6
  "types": "dist/lib.d.ts",
package/src/lib.ts CHANGED
@@ -18,6 +18,22 @@ export const isBrowserEnvironment = (): boolean => {
18
18
  return false;
19
19
  };
20
20
 
21
+ /**
22
+ * Extract transaction amount from EMVCo / ThaiQR payload (Tag 54)
23
+ */
24
+ export const parseThaiQRAmount = (qrCode?: string): string | null => {
25
+ if (!qrCode) return null;
26
+ const match = qrCode.match(/(?:^|[^0-9])54(\d{2})([0-9.]+)/);
27
+ if (match) {
28
+ const len = parseInt(match[1], 10);
29
+ const amountStr = match[2].substring(0, len);
30
+ if (!isNaN(parseFloat(amountStr))) {
31
+ return amountStr;
32
+ }
33
+ }
34
+ return null;
35
+ };
36
+
21
37
  // ==========================================
22
38
  // Interfaces & Types
23
39
  // ==========================================
@@ -175,8 +191,29 @@ export class ThaiTicketMajor {
175
191
  }
176
192
  }
177
193
 
194
+ public static readonly BOT_PROTECTION_COOKIES = new Set([
195
+ "_abck",
196
+ "bm_sz",
197
+ "ak_bmsc",
198
+ "bm_sv",
199
+ "bm_mi",
200
+ "hwwafsesid",
201
+ "hwwafsestime",
202
+ "__cf_bm",
203
+ "cf_clearance",
204
+ "_ga",
205
+ "_gid",
206
+ "_gcl_au",
207
+ "_fbp",
208
+ "_clck",
209
+ "_clsk",
210
+ "_ttp",
211
+ "_twpid",
212
+ ]);
213
+
178
214
  /**
179
215
  * Synchronize a cookie string into Chrome Extension's cookie store (chrome.cookies API).
216
+ * Automatically ignores bot protection cookies (Akamai _abck, bm_*, WAF) to prevent Access Denied (403) bans.
180
217
  */
181
218
  public static async syncCookiesToChrome(cookieString: string, domainUrl: string = "https://booking.thaiticketmajor.com"): Promise<void> {
182
219
  if (typeof chrome === "undefined" || !chrome?.cookies?.set) {
@@ -194,6 +231,12 @@ export class ThaiTicketMajor {
194
231
  const value = trimmed.substring(eqIndex + 1).trim();
195
232
  if (!name) continue;
196
233
 
234
+ // DO NOT overwrite Akamai / WAF bot tokens!
235
+ // Writing stale _abck or bm_* triggers Akamai Bot Protection -> Access Denied.
236
+ if (ThaiTicketMajor.BOT_PROTECTION_COOKIES.has(name.toLowerCase())) {
237
+ continue;
238
+ }
239
+
197
240
  try {
198
241
  await chrome.cookies.set({
199
242
  url: domainUrl,
@@ -216,7 +259,10 @@ export class ThaiTicketMajor {
216
259
  }
217
260
 
218
261
  /**
219
- * Setup declarativeNetRequest session rules in Chrome Extension to set Referer and Origin headers
262
+ * Setup declarativeNetRequest session rules in Chrome Extension to set Referer header for API requests only.
263
+ */
264
+ /**
265
+ * Setup declarativeNetRequest session rules in Chrome Extension to set Referer headers accurately for each endpoint.
220
266
  */
221
267
  public static async setupExtensionRules(): Promise<void> {
222
268
  if (typeof chrome === "undefined" || !chrome?.declarativeNetRequest?.updateSessionRules) {
@@ -224,40 +270,153 @@ export class ThaiTicketMajor {
224
270
  }
225
271
 
226
272
  try {
273
+ const ruleIdsToRemove = [1001, 1002, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019];
227
274
  await chrome.declarativeNetRequest.updateSessionRules({
228
- removeRuleIds: [1001, 1002],
275
+ removeRuleIds: ruleIdsToRemove,
229
276
  addRules: [
230
277
  {
231
- id: 1001,
232
- priority: 1,
278
+ id: 1010,
279
+ priority: 20,
233
280
  action: {
234
281
  type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
235
282
  requestHeaders: [
236
283
  {
237
284
  header: "Referer",
238
285
  operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
239
- value: "https://booking.thaiticketmajor.com",
286
+ value: "https://booking.thaiticketmajor.com/booking/3m/orderenc_kbankqr.php",
240
287
  },
288
+ ],
289
+ },
290
+ condition: {
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: [
241
303
  {
242
- header: "Origin",
304
+ header: "Referer",
243
305
  operation: chrome.declarativeNetRequest.HeaderOperation?.SET || "set",
244
- value: "https://booking.thaiticketmajor.com",
306
+ value: "https://booking.thaiticketmajor.com/booking/3m/orderenc_kbankqr.php",
245
307
  },
246
308
  ],
247
309
  },
248
310
  condition: {
249
- urlFilter: "thaiticketmajor.com",
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",
250
412
  resourceTypes: [
251
413
  chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
252
- chrome.declarativeNetRequest.ResourceType?.SUB_FRAME || "sub_frame",
253
- chrome.declarativeNetRequest.ResourceType?.MAIN_FRAME || "main_frame",
254
- chrome.declarativeNetRequest.ResourceType?.OTHER || "other",
255
414
  ],
256
415
  },
257
416
  },
258
417
  {
259
418
  id: 1002,
260
- priority: 1,
419
+ priority: 20,
261
420
  action: {
262
421
  type: chrome.declarativeNetRequest.RuleActionType?.MODIFY_HEADERS || "modifyHeaders",
263
422
  requestHeaders: [
@@ -272,7 +431,26 @@ export class ThaiTicketMajor {
272
431
  urlFilter: "kasikornbank.com",
273
432
  resourceTypes: [
274
433
  chrome.declarativeNetRequest.ResourceType?.XMLHTTPREQUEST || "xmlhttprequest",
275
- chrome.declarativeNetRequest.ResourceType?.OTHER || "other",
434
+ ],
435
+ },
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",
276
454
  ],
277
455
  },
278
456
  },
@@ -964,6 +1142,13 @@ export class ThaiTicketMajor {
964
1142
  },
965
1143
  });
966
1144
 
1145
+ this.emitProgress("ORDER_ENC_KBANK_RES", `Received orderenc_kbankqr.php response`, {
1146
+ url: `${this.endpoint}/orderenc_kbankqr.php`,
1147
+ requestPayload: payCfmData,
1148
+ responseStatus: response.status,
1149
+ responseData: response.data,
1150
+ });
1151
+
967
1152
  const $ = cheerio.load(response.data);
968
1153
  const kbankqrData: Record<string, string> = {};
969
1154
 
@@ -995,6 +1180,13 @@ export class ThaiTicketMajor {
995
1180
  },
996
1181
  });
997
1182
 
1183
+ this.emitProgress("GET_KBANK_QR_RES", `Received getkbankqr.php response`, {
1184
+ url: `${this.endpoint}/getkbankqr.php`,
1185
+ requestPayload: orderEncData,
1186
+ responseStatus: response.status,
1187
+ responseData: response.data,
1188
+ });
1189
+
998
1190
  return response.data;
999
1191
  }
1000
1192
 
@@ -1009,12 +1201,12 @@ export class ThaiTicketMajor {
1009
1201
 
1010
1202
  const preparePayload = {
1011
1203
  ...orderEncData,
1012
- reasoncode: kbankPayload.reasoncode,
1013
- eventname: kbankPayload.eventname,
1014
- rounddetail: kbankPayload.rounddetail,
1015
- totalamount: kbankPayload.totalamount,
1016
- orderstatus: kbankPayload.orderstatus,
1017
- kbankqr_txExpiry: kbankPayload.kbankqr_txExpiry,
1204
+ reasoncode: kbankPayload?.reasoncode,
1205
+ eventname: kbankPayload?.eventname,
1206
+ rounddetail: kbankPayload?.rounddetail,
1207
+ totalamount: kbankPayload?.totalamount,
1208
+ orderstatus: kbankPayload?.orderstatus,
1209
+ kbankqr_txExpiry: kbankPayload?.kbankqr_txExpiry,
1018
1210
  };
1019
1211
 
1020
1212
  const formData = new URLSearchParams(preparePayload);
@@ -1028,6 +1220,13 @@ export class ThaiTicketMajor {
1028
1220
  },
1029
1221
  });
1030
1222
 
1223
+ this.emitProgress("PAYMENT_KBANK_QR_RES", `Received payment_kbankqr.php response`, {
1224
+ url: `${this.endpoint}/payment_kbankqr.php`,
1225
+ requestPayload: preparePayload,
1226
+ responseStatus: response.status,
1227
+ responseData: response.data,
1228
+ });
1229
+
1031
1230
  const $ = cheerio.load(response.data);
1032
1231
  const script = $("#kbankpost script[data-order-id]");
1033
1232
 
@@ -1234,7 +1433,16 @@ export class ThaiTicketMajor {
1234
1433
  const kbankInfo = await this.getKBankQR(orderEncData);
1235
1434
  const kbankAuth = await this.paymentKBankQR(orderEncData, kbankInfo);
1236
1435
 
1237
- const qrResult = await this.generateThaiQR(kbankAuth.orderId, kbankAuth.apiKey, orderEncData.amount);
1436
+ const targetAmount =
1437
+ orderEncData.transactionamount ||
1438
+ orderEncData.totalamount ||
1439
+ orderEncData.amount ||
1440
+ payCfmData.amount ||
1441
+ kbankInfo.totalamount ||
1442
+ paymentAllData["cal_totalamount"] ||
1443
+ "0";
1444
+
1445
+ const qrResult = await this.generateThaiQR(kbankAuth.orderId, kbankAuth.apiKey, targetAmount);
1238
1446
 
1239
1447
  const qrRawCode =
1240
1448
  qrResult?.paint_text ||
@@ -1245,8 +1453,16 @@ export class ThaiTicketMajor {
1245
1453
  qrResult?.data?.raw_qr_code ||
1246
1454
  (typeof qrResult === "string" ? qrResult : "");
1247
1455
 
1456
+ const qrAmount = parseThaiQRAmount(qrRawCode);
1457
+ const finalAmount =
1458
+ qrAmount ||
1459
+ (targetAmount && targetAmount !== "0" ? targetAmount : null) ||
1460
+ qrResult?.amount ||
1461
+ qrResult?.data?.amount ||
1462
+ "0";
1463
+
1248
1464
  const displayQR = (customTitle?: string) => {
1249
- const title = customTitle || `ThaiQR Payment (KBank) - ยอดชำระ: ${orderEncData.amount || paymentAllData["cal_totalamount"]} บาท`;
1465
+ const title = customTitle || `ThaiQR Payment (KBank) - ยอดชำระ: ${finalAmount} บาท`;
1250
1466
  ThaiTicketMajor.displayQRCode(qrRawCode, title);
1251
1467
  };
1252
1468
 
@@ -1257,7 +1473,7 @@ export class ThaiTicketMajor {
1257
1473
  return {
1258
1474
  success: true,
1259
1475
  orderId: kbankAuth.orderId,
1260
- amount: orderEncData.amount || paymentAllData["cal_totalamount"],
1476
+ amount: String(finalAmount),
1261
1477
  qrRawCode,
1262
1478
  qrData: qrResult,
1263
1479
  displayQR,
@@ -1268,7 +1484,7 @@ export class ThaiTicketMajor {
1268
1484
  venue: paymentAllData["venue"],
1269
1485
  zone: targetZone.value,
1270
1486
  ticketCount,
1271
- totalAmount: orderEncData.amount || paymentAllData["cal_totalamount"],
1487
+ totalAmount: String(finalAmount),
1272
1488
  seats: summarySeats.length > 0 ? summarySeats : undefined,
1273
1489
  },
1274
1490
  rawPayload: {