python-qdairlines-helper 0.4.3__py3-none-any.whl → 0.6.0__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_qdairlines_helper
3
- Version: 0.4.3
3
+ Version: 0.6.0
4
4
  Summary: qdairlines helper python package
5
5
  Author-email: ckf10000 <ckf10000@sina.com>
6
6
  License: Apache License
@@ -210,9 +210,9 @@ Project-URL: Issues, https://github.com/ckf10000/python-qdairlines-helper/issues
210
210
  Requires-Python: >=3.12
211
211
  Description-Content-Type: text/markdown
212
212
  License-File: LICENSE
213
- Requires-Dist: python_playwright_helper>=0.4.0
214
- Requires-Dist: python_http_helper>=0.2.1
215
- Requires-Dist: flight_helper>=0.3.3
213
+ Requires-Dist: python_playwright_helper>=0.4.3
214
+ Requires-Dist: python_http_helper>=0.2.2
215
+ Requires-Dist: flight_helper>=0.3.7
216
216
  Dynamic: license-file
217
217
 
218
218
  # python-qdairlines-helper
@@ -1,19 +1,19 @@
1
- python_qdairlines_helper-0.4.3.dist-info/licenses/LICENSE,sha256=WtjCEwlcVzkh1ziO35P2qfVEkLjr87Flro7xlHz3CEY,11556
1
+ python_qdairlines_helper-0.6.0.dist-info/licenses/LICENSE,sha256=WtjCEwlcVzkh1ziO35P2qfVEkLjr87Flro7xlHz3CEY,11556
2
2
  qdairlines_helper/__init__.py,sha256=bN1FqEK_jvefO_HU_8HFJpjuOzHTsuhmRe5KAzmraR4,479
3
3
  qdairlines_helper/config/__init__.py,sha256=JjIRocePx3gwFNpdauuiwBU6B2Ug565xaHLEMu5Oo0I,479
4
4
  qdairlines_helper/config/url_const.py,sha256=KpvvZEPOsJ7FQdJLp79VfUdUSRuItR4OYYCzl8wfKZI,2194
5
5
  qdairlines_helper/controller/__init__.py,sha256=oyeciEBlDLNpqHblB0R-nXQG3HsI8L5mmlWulQ7Y38Q,482
6
6
  qdairlines_helper/controller/add_passenger.py,sha256=qixajF_MRzmMZ9htAFeGCP3L27iV1Xk-jnfPQEP48s8,5155
7
- qdairlines_helper/controller/book_payment.py,sha256=9QdaakkIyi8qCjZ7TT687JX8Jqt5mp9SSQ307NYZ3tY,6270
8
- qdairlines_helper/controller/book_search.py,sha256=XQQuI1xX6rO-MqdJ15_X5wX94Yeqj9UVat6p55beNu8,8977
7
+ qdairlines_helper/controller/book_payment.py,sha256=yk1A99yHAjVcnXGBSzGanA6Cle4NXwjt6e8Qx4W2Xi0,6184
8
+ qdairlines_helper/controller/book_search.py,sha256=qSsjyn6BgJc5yP-mf2mNIDkEPsG_PTUffH4cEAZFsl8,9267
9
9
  qdairlines_helper/controller/cash_pax_info.py,sha256=AZ09ekt-zaYYs3uUSclul014mbCZEF5BVIghOeIGT5s,2507
10
10
  qdairlines_helper/controller/home.py,sha256=2r75pe7d16kZ6tkJZ3zM5ycV-DE5iy3njhpE0xf1EZw,1390
11
- qdairlines_helper/controller/nhlms_cashdesk.py,sha256=1gWURu9VnjjvvalAu-uF7eXVMSsiaBDWHPwG8EgDRh8,4308
11
+ qdairlines_helper/controller/nhlms_cashdesk.py,sha256=Z5ApNGAzYWgBn43zcTqt42h1cFABLtfEDKWTIWSXsAA,4421
12
12
  qdairlines_helper/controller/order_detail.py,sha256=hhL37PiozS1jALMk_eB0vg0eigW_Rcl_mf0l-QrW21o,4337
13
13
  qdairlines_helper/controller/order_query.py,sha256=7U6S5xlT89UJHFslDbUT5VRgGlAoPQSOi4NRtEKK9xQ,1903
14
- qdairlines_helper/controller/order_verify.py,sha256=3_bc1R47_vaCIEJaPWrxyYrE49v2J1-KsGCmAEEloKc,2269
15
- qdairlines_helper/controller/pay_success.py,sha256=G8Vf5MDDBgvDo3MIg-deEvyv4PJfxdzLhpHU9TGuejc,3295
16
- qdairlines_helper/controller/user_login.py,sha256=ojW6mqZdiURpi5K1gMPdFlqCUieOkBhcXDJELW5Hktw,6210
14
+ qdairlines_helper/controller/order_verify.py,sha256=lx2YpDUoooCkU596IEz4kwyJsev-dHskBLHJ4v3d1Rg,2670
15
+ qdairlines_helper/controller/pay_success.py,sha256=J0yWSarIlce4cVXdqZyURWm-DwujTqg32al5JTe5flA,2837
16
+ qdairlines_helper/controller/user_login.py,sha256=CNuPIpGlkk5uU9ltL3DtTgC2SK6K32yA-K0Q-Kxe3MM,6267
17
17
  qdairlines_helper/http/__init__.py,sha256=96AJPf2zgw_rm3Wv-boIBltWFgZH3Yo-fn5M9SRK280,489
18
18
  qdairlines_helper/http/flight_order.py,sha256=7NTUA8xt6gwPBH7gdU3VjUoBa9LpPAvAEzkWguLPrNk,2066
19
19
  qdairlines_helper/http/http_base.py,sha256=zWSgJfyMaekHyvV4lJD-KHiCX9BN3GGb8DperyVpRGQ,2492
@@ -21,16 +21,16 @@ qdairlines_helper/http/user_login.py,sha256=S_thG1RLXv5x5MQJmEG-oo38ReIF3_p17hVw
21
21
  qdairlines_helper/po/__init__.py,sha256=HoouLJGLu_dI6IAKTVjBRraGHMcAUgntayTph-BUBAE,481
22
22
  qdairlines_helper/po/add_passenger_page.py,sha256=no_QC4hwO7xuZXKLzakNlcWj2R06oVZdzKHvFHsBG5Y,7996
23
23
  qdairlines_helper/po/air_order_page.py,sha256=0Ux0B2hUGKCoFLRpJqGHS2w690o0A2PDnvRf9-UKfJE,930
24
- qdairlines_helper/po/book_search_page.py,sha256=T2bxKFuvApY0rMlU7NHso0CwQ9XXOR0xz4bvIgI1x0k,10865
24
+ qdairlines_helper/po/book_search_page.py,sha256=Id4sqpmIQvgxTcESh-3dpCG5ccigkL6VrNLbZPf1gq0,14547
25
25
  qdairlines_helper/po/cash_pax_info_page.py,sha256=yH-ZedYweby1x3Sw3JZX6a57-8PdtQHmNKqym7cn-Xo,3397
26
26
  qdairlines_helper/po/home_page.py,sha256=KvoOZOYwKjlJjZOT522K_8ic3_2zaYOPWkuklUHVSlM,907
27
27
  qdairlines_helper/po/login_page.py,sha256=SdWmkP_47GeosbFRcfvXLeQxxdM7b9UESvmajWq4-nA,2975
28
- qdairlines_helper/po/nhlms_cash_desk_page.py,sha256=K3GCcmfHbMOhZnVB7hHKJflVy0DbiidDJBfJ3IMbMGc,6671
29
- qdairlines_helper/po/order_verify_page.py,sha256=7boJVdzoV9IdIGtbjza3za9C26_4LFBjOmp27-0_ick,2924
28
+ qdairlines_helper/po/nhlms_cash_desk_page.py,sha256=I2FdZvEfEiN9pbD6nZ7VquO1ZMg9zUJjVkStx9eat0M,6670
29
+ qdairlines_helper/po/order_verify_page.py,sha256=PlDZ-WbZBwV7pjjuQjXuMrYXAvys0Uas2FncXitsBLs,4978
30
30
  qdairlines_helper/po/pay_success_page.py,sha256=XGddo8as8wf1kCCdvemqHIRWOPIPv1JG8iXEmpH4zck,1329
31
31
  qdairlines_helper/utils/__init__.py,sha256=sti2S709puM2mQbQisk0KGq_YNjW6T4zz2vyYXonNB0,479
32
32
  qdairlines_helper/utils/po_utils.py,sha256=74ZVyyxPmPk1tcGQoMauXhBE3qsZJej8urni1ch27io,2417
33
- python_qdairlines_helper-0.4.3.dist-info/METADATA,sha256=J8UgJTIFyVdwztik2df4g_eVENNr_WY8wH6Vj7FqcdM,14424
34
- python_qdairlines_helper-0.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
- python_qdairlines_helper-0.4.3.dist-info/top_level.txt,sha256=MRbQkBMdSG4f5mx2RWfu6aXoDSyM-2KwL-YFqNBdbng,18
36
- python_qdairlines_helper-0.4.3.dist-info/RECORD,,
33
+ python_qdairlines_helper-0.6.0.dist-info/METADATA,sha256=WtDdd9E0T432X1X5uzk-HRnEt6U2s5OBVrVcV1nXEeE,14424
34
+ python_qdairlines_helper-0.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
+ python_qdairlines_helper-0.6.0.dist-info/top_level.txt,sha256=MRbQkBMdSG4f5mx2RWfu6aXoDSyM-2KwL-YFqNBdbng,18
36
+ python_qdairlines_helper-0.6.0.dist-info/RECORD,,
@@ -15,6 +15,7 @@ from playwright.async_api import Page
15
15
  from typing import Any, List, Callable, Optional, Dict
16
16
  from flight_helper.models.dto.passenger import PassengerDTO
17
17
  from flight_helper.models.dto.itinerary import QueryItineraryRequestDTO
18
+ from qdairlines_helper.controller.pay_success import two_check_pay_success
18
19
  from flight_helper.models.dto.booking import BookingInputDTO, OneWayBookingDTO
19
20
  from qdairlines_helper.controller.book_search import open_book_search_page, book_search
20
21
  from qdairlines_helper.controller.order_verify import load_order_verify_po, order_verify
@@ -23,14 +24,13 @@ from flight_helper.models.dto.payment import HFPaidAccountPaymentInputDTO, Payme
23
24
  from qdairlines_helper.controller.add_passenger import add_passenger, load_add_passenger_po
24
25
  from qdairlines_helper.controller.cash_pax_info import load_cash_pax_info_po, select_payment_channel
25
26
  from qdairlines_helper.controller.nhlms_cashdesk import load_nhlms_cash_desk_po, hf_paid_account_payment
26
- from qdairlines_helper.controller.pay_success import two_check_pay_success, check_hf_payment_is_success
27
27
 
28
28
 
29
29
  async def book_payment_callback(
30
- *, page: Page, logger: Logger, is_pay_completed_callback: Callable, book_input_dto: BookingInputDTO,
30
+ *, page: Page, logger: Logger, pre_check_of_payment: Callable, book_input_dto: BookingInputDTO,
31
31
  one_way_booking_dto: OneWayBookingDTO, passengers_dto: List[PassengerDTO], qdair_cookie: Dict[str, Any],
32
- callback_get_proxy: Callable, cookie_jar: Optional[CookieJar] = None, timeout: float = 60.0,
33
- refresh_attempt: int = 3, retry: int = 0, enable_log: bool = True,
32
+ callback_get_proxy: Callable, qlv_cookie: Dict[str, Any], cookie_jar: Optional[CookieJar] = None,
33
+ timeout: float = 60.0, refresh_attempt: int = 3, retry: int = 0, enable_log: bool = True,
34
34
  hf_paid_account_payment_dto: Optional[HFPaidAccountPaymentInputDTO] = None, **kwargs: Any
35
35
  ) -> PaymentResultDTO:
36
36
  # 1. 打开预订搜索页面
@@ -66,7 +66,10 @@ async def book_payment_callback(
66
66
  )
67
67
 
68
68
  # 6. 校验订单
69
- await order_verify(page=order_verify_po, logger=logger, refresh_attempt=refresh_attempt, timeout=timeout)
69
+ await order_verify(
70
+ page=order_verify_po, logger=logger, refresh_attempt=refresh_attempt, timeout=timeout,
71
+ qdair_user_id=book_input_dto.book_login_user
72
+ )
70
73
  logger.info(f"订单<{one_way_booking_dto.order_no}>,校验订单结束")
71
74
 
72
75
  # 7. 加载支付类型页面对象
@@ -94,8 +97,8 @@ async def book_payment_callback(
94
97
  # 10. 汇付天下操作支付
95
98
  payment_result_dto = await hf_paid_account_payment(
96
99
  page=nhlms_cash_desk_po, logger=logger, order_no=one_way_booking_dto.order_no, timeout=timeout,
97
- is_pay_completed_callback=is_pay_completed_callback,
98
- hf_paid_account_payment_dto=hf_paid_account_payment_dto
100
+ hf_paid_account_payment_dto=hf_paid_account_payment_dto, pre_check_of_payment=pre_check_of_payment,
101
+ order_id=one_way_booking_dto.order_no, qlv_cookie=qlv_cookie
99
102
  )
100
103
  logger.info(f"订单<{one_way_booking_dto.order_no}>,汇付天下操作支付结束")
101
104
  payment_result_dto.pre_order_no = pre_order_no
@@ -105,14 +108,10 @@ async def book_payment_callback(
105
108
  storage_state=qdair_cookie.get("storage_state"), token=qdair_cookie.get("token"),
106
109
  pre_order_no=pre_order_no, user_id=qdair_cookie.get("user_id"), headers=qdair_cookie.get("headers")
107
110
  )
108
-
109
- # 11. 检查是否支付成功
110
- if kwargs.get("暂时不走这个逻辑,改用轻逻辑验证"):
111
- await two_check_pay_success(
112
- page=page, logger=logger, timeout=timeout, query_dto=query_dto, retry=retry, enable_log=enable_log,
113
- callback_get_proxy=callback_get_proxy, cookie_jar=cookie_jar
114
- )
115
- await check_hf_payment_is_success(page=nhlms_cash_desk_po, logger=logger)
111
+ await two_check_pay_success(
112
+ page=nhlms_cash_desk_po, logger=logger, timeout=timeout, query_dto=query_dto, retry=retry,
113
+ enable_log=enable_log, callback_get_proxy=callback_get_proxy, cookie_jar=cookie_jar
114
+ )
116
115
  return payment_result_dto
117
116
  else:
118
117
  raise PaymentChannelMissError()
@@ -9,15 +9,14 @@
9
9
  # Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
10
10
  # ---------------------------------------------------------------------------------------------------------
11
11
  """
12
- import time
13
12
  import asyncio
13
+ from time import time
14
14
  from logging import Logger
15
- from typing import Dict, Any, List, Optional
15
+ from typing import Dict, Any, List
16
16
  from playwright.async_api import Page, Locator
17
17
  import qdairlines_helper.config.url_const as url_const
18
18
  from qdairlines_helper.po.book_search_page import BookSearchPage
19
19
  from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
20
- from playwright.async_api import TimeoutError as PlaywrightTimeoutError
21
20
  from flight_helper.models.dto.booking import OneWayBookingDTO, BookingInputDTO
22
21
  from flight_helper.utils.exception_utils import NotEnoughTicketsError, ExcessiveProfitdError, ExcessiveLossesError, \
23
22
  IPBlockError
@@ -39,26 +38,18 @@ async def open_book_search_page(
39
38
  return book_search_po
40
39
 
41
40
 
42
- async def _book_search_dialog_handle(
43
- *, logger: Logger, page: BookSearchPage, flight_no: str, timeout: float = 60.0
44
- ) -> Locator:
45
- end_time = time.time() + timeout
46
- last_exception: Optional[Exception] = None
47
- while time.time() < end_time:
41
+ async def _book_search_dialog_handle(*, logger: Logger, page: BookSearchPage, timeout: float = 20.0) -> None:
42
+ end_time = time() + timeout
43
+ while time() < end_time:
44
+ await page.handle_yidun_icon(logger=logger, timeout=1)
48
45
  try:
49
- # 2. 点击继续购票按钮
50
46
  continue_book_btn = await page.get_reminder_dialog_continue_book_btn(timeout=1)
51
47
  await continue_book_btn.click(button="left")
52
48
  logger.info("航班预订查询页面,出现温馨提醒弹框,【继续购票】按钮点击完成")
49
+ return
53
50
  except (Exception,):
54
51
  pass
55
- try:
56
- # 1.获取航班基本信息plane locator
57
- flight_info_plane: Locator = await page.get_flight_info_plane(flight_no=flight_no, timeout=1)
58
- return flight_info_plane
59
- except (Exception,) as e:
60
- last_exception = e
61
- raise last_exception
52
+ await asyncio.sleep(0.1) # 快速轮询
62
53
 
63
54
 
64
55
  async def book_search(
@@ -67,17 +58,31 @@ async def book_search(
67
58
  ) -> None:
68
59
  # 1.搜索栏输入起飞城市
69
60
  depart_city_input = await book_search_page.get_depart_city_input(timeout=timeout)
70
- await depart_city_input.fill(value=one_way_booking_dto.dep_code)
71
- await asyncio.sleep(delay=2)
72
- await depart_city_input.press("Enter")
61
+ # await depart_city_input.fill(value=one_way_booking_dto.dep_code)
62
+ await book_search_page.simulation_input_element(
63
+ locator=depart_city_input, text=one_way_booking_dto.dep_code, delay=200
64
+ )
65
+ await asyncio.sleep(delay=1)
66
+ # await depart_city_input.press("Enter")
67
+ depart_city_result = await book_search_page.get_depart_city_search_result(
68
+ depart_city=one_way_booking_dto.dep_city, timeout=timeout
69
+ )
70
+ await depart_city_result.click(button="left")
73
71
  logger.info(
74
72
  f"航班预订查询页面,搜索栏-起飞城市<{one_way_booking_dto.dep_city} {one_way_booking_dto.dep_code}>输入完成")
75
73
 
76
74
  # 2.搜索栏输入抵达城市
77
75
  arrive_city_input = await book_search_page.get_arrive_city_input(timeout=timeout)
78
- await arrive_city_input.fill(value=one_way_booking_dto.arr_code)
79
- await asyncio.sleep(delay=2)
80
- await arrive_city_input.press("Enter")
76
+ # await arrive_city_input.fill(value=one_way_booking_dto.arr_code)
77
+ await book_search_page.simulation_input_element(
78
+ locator=arrive_city_input, text=one_way_booking_dto.arr_code, delay=200
79
+ )
80
+ await asyncio.sleep(delay=1)
81
+ # await arrive_city_input.press("Enter")
82
+ arrive_city_result = await book_search_page.get_arrive_city_search_result(
83
+ arrive_city=one_way_booking_dto.arr_city, timeout=timeout
84
+ )
85
+ await arrive_city_result.click(button="left")
81
86
  logger.info(
82
87
  f"航班预订查询页面,搜索栏-抵达城市<{one_way_booking_dto.arr_city} {one_way_booking_dto.arr_code}>输入完成")
83
88
 
@@ -94,14 +99,12 @@ async def book_search(
94
99
  logger.info(f"航班预订查询页面,【查询机票】按钮点击完成")
95
100
 
96
101
  # 5.点击查询后,再次处理是否有弹框,并航班基本信息plane locator
97
- flight_info_plane: Locator = await _book_search_dialog_handle(
98
- logger=logger, page=book_search_page, timeout=timeout, flight_no=one_way_booking_dto.flight_no
99
- )
102
+ await _book_search_dialog_handle(logger=logger, page=book_search_page, timeout=timeout)
100
103
 
101
104
  # 6.获取航班基本信息plane locator
102
- # flight_info_plane: Locator = await book_search_page.get_flight_info_plane(
103
- # flight_no=one_way_booking_dto.flight_no, timeout=timeout
104
- # )
105
+ flight_info_plane: Locator = await book_search_page.get_flight_info_plane(
106
+ flight_no=one_way_booking_dto.flight_no, timeout=timeout
107
+ )
105
108
 
106
109
  # 8. 获取产品类型
107
110
  flight_product_nav: Locator = await book_search_page.get_flight_product_nav(
@@ -120,7 +123,7 @@ async def book_search(
120
123
  locator=flight_info_plane, flight_no=one_way_booking_dto.flight_no, logger=logger
121
124
  )
122
125
  # 取出需要预订的舱位产品
123
- products = [x for x in products if x.get("cabin")]
126
+ products = [x for x in products if x.get("cabin") == one_way_booking_dto.cabin and not x.get("ticket_info")]
124
127
  # 根据价格升序排序(默认)
125
128
  products = sorted(products, key=lambda x: x["amounts"]["amount"])
126
129
  # 9.1 判断是否存在该舱位
@@ -11,10 +11,9 @@
11
11
  """
12
12
  import inspect
13
13
  from logging import Logger
14
- from typing import Callable
14
+ from typing import Callable, Dict, Any
15
15
  from playwright.async_api import BrowserContext
16
16
  import qdairlines_helper.config.url_const as url_const
17
- from flight_helper.utils.exception_utils import DuplicatePaymentError
18
17
  from qdairlines_helper.po.nhlms_cash_desk_page import NhlmsCashDeskPage
19
18
  from playwright_helper.utils.browser_utils import switch_for_table_window
20
19
  from flight_helper.models.dto.payment import HFPaidAccountPaymentInputDTO, PaymentResultDTO
@@ -37,8 +36,8 @@ async def load_nhlms_cash_desk_po(
37
36
 
38
37
 
39
38
  async def hf_paid_account_payment(
40
- *, page: NhlmsCashDeskPage, logger: Logger, order_no: int, is_pay_completed_callback: Callable,
41
- hf_paid_account_payment_dto: HFPaidAccountPaymentInputDTO, timeout: float = 60.0
39
+ *, page: NhlmsCashDeskPage, logger: Logger, order_no: int, order_id: int, pre_check_of_payment: Callable,
40
+ qlv_cookie: Dict[str, Any], hf_paid_account_payment_dto: HFPaidAccountPaymentInputDTO, timeout: float = 60.0
42
41
  ) -> PaymentResultDTO:
43
42
  # 1. 获取收银台支付流水
44
43
  pay_transaction = await page.get_order_transaction(timeout=timeout)
@@ -65,13 +64,17 @@ async def hf_paid_account_payment(
65
64
  await password_input.fill(value=hf_paid_account_payment_dto.password)
66
65
  logger.info(f"汇付天下收银台页面,交易密码<{hf_paid_account_payment_dto.password}>输入完成")
67
66
 
68
- # 6. 校验订单是否已经被支付
69
- if inspect.iscoroutinefunction(is_pay_completed_callback):
70
- is_pay: bool = await is_pay_completed_callback(order_id=order_no)
67
+ # 5.1. 前置检查,为何放在此处
68
+ if inspect.iscoroutinefunction(pre_check_of_payment):
69
+ await pre_check_of_payment(
70
+ order_id=order_id, logger=logger, cookie=qlv_cookie,
71
+ payment_channel=hf_paid_account_payment_dto.payment_type
72
+ )
71
73
  else:
72
- is_pay: bool = is_pay_completed_callback(order_id=order_no)
73
- if is_pay is True:
74
- raise DuplicatePaymentError(order_no=order_no)
74
+ pre_check_of_payment(
75
+ order_id=order_id, logger=logger, cookie=qlv_cookie,
76
+ payment_channel=hf_paid_account_payment_dto.payment_type
77
+ )
75
78
 
76
79
  # 6. 点击【确认支付】
77
80
  confirm_payment_btn = await page.get_confirm_payment_btn(timeout=timeout)
@@ -13,6 +13,7 @@ from logging import Logger
13
13
  from playwright.async_api import Page
14
14
  import qdairlines_helper.config.url_const as url_const
15
15
  from flight_helper.utils.exception_utils import IPBlockError
16
+ from flight_helper.utils.exception_utils import OrderLimitError
16
17
  from qdairlines_helper.po.order_verify_page import OrderVerifyPage
17
18
  from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
18
19
 
@@ -32,7 +33,7 @@ async def load_order_verify_po(
32
33
 
33
34
 
34
35
  async def order_verify(
35
- *, page: OrderVerifyPage, logger: Logger, refresh_attempt: int = 3, timeout: float = 60.0
36
+ *, page: OrderVerifyPage, logger: Logger, qdair_user_id: str, refresh_attempt: int = 3, timeout: float = 60.0
36
37
  ) -> None:
37
38
  # 1. 先确认订单数据是否加载出来
38
39
  await page.get_order_info_plane(timeout=timeout, refresh_attempt=refresh_attempt)
@@ -46,3 +47,11 @@ async def order_verify(
46
47
  next_btn = await page.get_next_btn(timeout=timeout)
47
48
  await next_btn.click(button="left")
48
49
  logger.info("订单校验页面,【下一步】按钮点击完成")
50
+
51
+ # 4. 判断是否存在消息提示弹框
52
+ message: str = await page.get_message_box(logger=logger, timeout=5)
53
+ if message:
54
+ raise OrderLimitError(user_id=qdair_user_id, message=message)
55
+
56
+ # 5. 看看是否出现易盾校验码
57
+ await page.handle_yidun_icon(logger=logger, timeout=5)
@@ -37,18 +37,13 @@ async def load_pay_success_page(
37
37
 
38
38
 
39
39
  async def two_check_pay_success(
40
- *, page: Page, logger: Logger, query_dto: QueryItineraryRequestDTO, callback_get_proxy: Callable,
40
+ *, page: NhlmsCashDeskPage, logger: Logger, query_dto: QueryItineraryRequestDTO, callback_get_proxy: Callable,
41
41
  cookie_jar: Optional[CookieJar] = None, timeout: float = 60.0, retry: int = 0, enable_log: bool = True
42
- ) -> bool:
42
+ ) -> None:
43
43
  try:
44
44
  # 1. 看支付成功页面是否加载完成
45
- pay_success_po = await load_pay_success_page(
46
- page=page, logger=logger, protocol=query_dto.payment_protocol, domain=query_dto.payment_domain,
47
- timeout=timeout
48
- )
49
- # 2. 检测页面的支付成功image是否存在
50
- await pay_success_po.get_pay_success_image(timeout=timeout)
51
- return True
45
+ await page.check_is_pay_success(logger=logger)
46
+ return
52
47
  except Exception as e:
53
48
  logger.error(e)
54
49
 
@@ -58,10 +53,5 @@ async def two_check_pay_success(
58
53
  callback_get_proxy=callback_get_proxy, logger=logger
59
54
  )
60
55
  order_status = (order_detail.get("orderStatus", "")).upper()
61
- if order_status not in ("TICKED",):
56
+ if order_status not in ("TICKED", "PAYED"):
62
57
  raise PaymentFailedError(pre_order_no=query_dto.pre_order_no, order_status=order_status)
63
-
64
-
65
- async def check_hf_payment_is_success(*, page: NhlmsCashDeskPage, logger: Logger) -> None:
66
- # 1. 看支付成功页面是否加载完成
67
- await page.check_is_pay_success(logger=logger)
@@ -106,9 +106,10 @@ async def get_user_info(
106
106
  ) -> Dict[str, Any]:
107
107
  user_login = UserLogin(http_request_dto=http_request_dto, cookie_jar=cookie_jar)
108
108
  last_user_info: Dict[str, Any] = dict()
109
- for index in range(5):
109
+ count = 2
110
+ for index in range(count):
110
111
  proxy = None
111
- is_end: bool = True if index + 1 == 5 else False
112
+ is_end: bool = True if index + 1 == count else False
112
113
  if index != 0:
113
114
  if inspect.iscoroutinefunction(callback_get_proxy):
114
115
  proxy = await callback_get_proxy(logger=logger)
@@ -121,7 +122,8 @@ async def get_user_info(
121
122
  await user_login.http_client.close()
122
123
  logger.info("获取青岛航空官网的用户详情数据成功")
123
124
  break
124
- except (Exception,):
125
+ except (Exception,) as e:
126
+ logger.error(e)
125
127
  pass
126
128
  if isinstance(last_user_info, dict) and last_user_info.get("result") and last_user_info.get("code") == 1:
127
129
  return last_user_info.get("result")
@@ -11,9 +11,9 @@
11
11
  """
12
12
  import re
13
13
  from logging import Logger
14
- from typing import Optional, Dict, Any, List
15
14
  from playwright.async_api import Page, Locator
16
15
  from playwright_helper.libs.base_po import BasePo
16
+ from typing import Optional, Dict, Any, List, Literal
17
17
  import qdairlines_helper.config.url_const as url_const
18
18
  from flight_helper.utils.exception_utils import ProductTypeError
19
19
  from playwright_helper.utils.type_utils import convert_order_amount_text, safe_convert_advanced
@@ -37,14 +37,21 @@ class BookSearchPage(BasePo):
37
37
  selector: str = '//strong[contains(text(), "没有在飞航班")]'
38
38
  return await self.get_locator(selector=selector, timeout=timeout)
39
39
 
40
+ @staticmethod
41
+ def _get_reminder_dialog_selector() -> str:
42
+ return '//div[@class="el-dialog__body"]//button[contains(@class, "search_btn")]'
43
+
44
+ async def is_exist_reminder_dialog(self) -> bool:
45
+ # 使用 count() 快速判断是否存在(不会因找不到而抛异常)
46
+ return await self.__page.locator(selector=self._get_reminder_dialog_selector()).count() > 0
47
+
40
48
  async def get_reminder_dialog_continue_book_btn(self, timeout: float = 5.0) -> Locator:
41
49
  """
42
50
  获取预订搜索页的温馨提醒弹框中的【继续购票】按钮
43
51
  :param timeout: 超时时间(秒)
44
52
  :return: (是否存在, 错误信息|元素对象)
45
53
  """
46
- selector: str = '//div[@class="el-dialog__body"]//button[contains(@class, "search_btn")]'
47
- return await self.get_locator(selector=selector, timeout=timeout)
54
+ return await self.get_locator(selector=self._get_reminder_dialog_selector(), timeout=timeout)
48
55
 
49
56
  async def get_depart_city_input(self, timeout: float = 5.0) -> Locator:
50
57
  """
@@ -55,6 +62,22 @@ class BookSearchPage(BasePo):
55
62
  selector: str = '//div[contains(@class, "flight_search_form")]//input[@id="orig"]'
56
63
  return await self.get_locator(selector=selector, timeout=timeout)
57
64
 
65
+ @staticmethod
66
+ def _get_city_search_result_selector(city_name: str, endpoint: Literal["orig", "dest"]) -> str:
67
+ return f'xpath=//div[@name="{endpoint}" and @class="vcp-select"]//span[contains(text(), "{city_name}")]/..'
68
+
69
+ async def get_depart_city_search_result(self, depart_city: str, timeout: float = 5.0) -> Locator:
70
+ """
71
+ 获取预订搜索页搜索栏的起飞城市搜索结果
72
+ :param depart_city: 起飞城市
73
+ :param timeout: 超时时间(秒)
74
+ :return: (是否存在, 错误信息|元素对象)
75
+ """
76
+ if depart_city == "晋江":
77
+ depart_city = "泉州"
78
+ selector = self._get_city_search_result_selector(city_name=depart_city, endpoint="orig")
79
+ return await self.get_locator(selector=selector, timeout=timeout, strict=False)
80
+
58
81
  async def get_arrive_city_input(self, timeout: float = 5.0) -> Locator:
59
82
  """
60
83
  获取预订搜索页搜索栏的抵达城市输入框
@@ -64,6 +87,18 @@ class BookSearchPage(BasePo):
64
87
  selector: str = '//div[contains(@class, "flight_search_form")]//input[@id="dest"]'
65
88
  return await self.get_locator(selector=selector, timeout=timeout)
66
89
 
90
+ async def get_arrive_city_search_result(self, arrive_city: str, timeout: float = 5.0) -> Locator:
91
+ """
92
+ 获取预订搜索页搜索栏的抵达城市搜索结果
93
+ :param arrive_city: 抵达城市
94
+ :param timeout: 超时时间(秒)
95
+ :return: (是否存在, 错误信息|元素对象)
96
+ """
97
+ if arrive_city == "晋江":
98
+ arrive_city = "泉州"
99
+ selector = self._get_city_search_result_selector(city_name=arrive_city, endpoint="dest")
100
+ return await self.get_locator(selector=selector, timeout=timeout, strict=False)
101
+
67
102
  async def get_depart_date_input(self, timeout: float = 5.0) -> Locator:
68
103
  """
69
104
  获取预订搜索页搜索栏的起飞日期输入框
@@ -144,8 +179,10 @@ class BookSearchPage(BasePo):
144
179
  amounts: Dict[str, Any] = await self._get_flight_product_price(locator=locator, timeout=timeout)
145
180
  seats_status: int = await self._get_flight_product_seats_status(locator=locator, timeout=timeout)
146
181
  cabin = await self._get_flight_product_cabin(locator=locator, timeout=timeout)
182
+ ticket_info: str = await self._get_flight_product_ticket_info(locator=locator, timeout=timeout)
147
183
  flight_products.append(dict(
148
- amounts=amounts, cabin=cabin, seats_status=seats_status, booking_btn=booking_btn
184
+ amounts=amounts, cabin=cabin, seats_status=seats_status, booking_btn=booking_btn,
185
+ ticket_info=ticket_info
149
186
  ))
150
187
  except (PlaywrightError, PlaywrightTimeoutError, EnvironmentError, RuntimeError, Exception) as e:
151
188
  logger.error(e)
@@ -189,6 +226,20 @@ class BookSearchPage(BasePo):
189
226
  selector: str = 'xpath=(.//td[@class="ticket_num"]/div/span)[1]'
190
227
  return await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
191
228
 
229
+ async def _get_flight_product_ticket_info(self, locator: Locator, timeout: float = 5.0) -> Optional[str]:
230
+ """
231
+ 获取预订搜索页航班内容栏航班产品下的单据信息
232
+ :param timeout: 超时时间(秒)
233
+ :param locator: flight_product Locator 对象
234
+ :return: (是否存在, 错误信息|元素对象)
235
+ """
236
+ try:
237
+ selector: str = 'xpath=.//td[@class="ticket_info"]//span[contains(@class, "text-left")]'
238
+ locator = await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
239
+ return (await locator.inner_text()).strip()
240
+ except (Exception,):
241
+ return
242
+
192
243
  async def _get_flight_product_seats_status(self, locator: Locator, timeout: float = 5.0) -> int:
193
244
  """
194
245
  获取预订搜索页航班内容栏航班产品下的余票信息
@@ -205,3 +256,23 @@ class BookSearchPage(BasePo):
205
256
  return safe_convert_advanced(value=match.group().strip())
206
257
  else:
207
258
  return 999999
259
+
260
+ async def handle_yidun_icon(self, logger: Logger, timeout: float = 20.0) -> None:
261
+ """
262
+ 获取搜索页面中的易盾校验弹框
263
+ :param logger: 日志对象
264
+ :param timeout: 超时时间(秒)
265
+ :return: (是否存在, 错误信息|元素对象)
266
+ """
267
+ selector: str = 'xpath=//div[@class="yidun_intelli-tips"]//div[@class="yidun_intelli-icon"]'
268
+ try:
269
+ icon: Locator = await self.get_locator(selector=selector, timeout=timeout, strict=False)
270
+ await icon.click(button="left")
271
+ logger.info("预订搜索页面,易盾校验框【易盾icon】点击完成")
272
+ btn: Locator = await self.get_locator(
273
+ selector='//div[@class="el-dialog__body"]//button[@type="button"]', timeout=3, strict=False
274
+ )
275
+ await btn.click(button="left")
276
+ logger.info("预订搜索页面,易盾校验框【确认】点击完成")
277
+ except (Exception,):
278
+ pass
@@ -111,7 +111,7 @@ class NhlmsCashDeskPage(BasePo):
111
111
  '//h3[contains(text(), "您的IP")]'
112
112
  )
113
113
  ])
114
- timeout = 120
114
+ timeout = 60
115
115
  end_time = datetime.now() + timedelta(seconds=timeout)
116
116
 
117
117
  while datetime.now() < end_time:
@@ -9,7 +9,8 @@
9
9
  # Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
10
10
  # ---------------------------------------------------------------------------------------------------------
11
11
  """
12
- from typing import Optional
12
+ from logging import Logger
13
+ from typing import Optional, List
13
14
  from playwright.async_api import Page, Locator
14
15
  from playwright_helper.libs.base_po import BasePo
15
16
  import qdairlines_helper.config.url_const as url_const
@@ -24,6 +25,44 @@ class OrderVerifyPage(BasePo):
24
25
  super().__init__(page, url or url_const.order_verify_url)
25
26
  self.__page = page
26
27
 
28
+ async def handle_yidun_icon(self, logger: Logger, timeout: float = 20.0) -> None:
29
+ """
30
+ 获取订单校验页中的易盾校验弹框
31
+ :param logger: 日志对象
32
+ :param timeout: 超时时间(秒)
33
+ :return: (是否存在, 错误信息|元素对象)
34
+ """
35
+ selector: str = 'xpath=//div[@class="yidun_intelli-tips"]//div[@class="yidun_intelli-icon"]'
36
+ try:
37
+ locator: Locator = await self.get_locator(selector=selector, timeout=timeout, strict=False)
38
+ await locator.click(button="left")
39
+ logger.info("订单校验页面,易盾校验框【易盾icon】点击完成")
40
+ except (Exception,):
41
+ pass
42
+
43
+ async def get_message_box(self, logger: Logger, timeout: float = 20.0) -> Optional[str]:
44
+ """
45
+ 获取订单校验页中的提示消息弹框内容
46
+ 内容可能是:1. 您的账户订单数已达最大上限,请前往青岛航空官方微信小程序进行预订。
47
+ 2. 请前往青岛航空官方微信小程序进行预订。
48
+ 3. 抱歉,证件号码为:450511197305180615已经创建订单,订单号OW20260117B1280813,请勿重复占座
49
+ :param logger: 日志对象
50
+ :param timeout: 超时时间(秒)
51
+ :return: (是否存在, 错误信息|元素对象)
52
+ """
53
+ selectors: List[str] = [
54
+ 'xpath=//div[@class="el-message-box"]//div[@class="el-message-box__message"]/p',
55
+ 'xpath=//div[@class="layui-layer-content"]'
56
+ ]
57
+ for selector in selectors:
58
+ try:
59
+ locator: Locator = await self.get_locator(selector=selector, timeout=timeout, strict=False)
60
+ text: str = (await locator.inner_text()).strip()
61
+ logger.info(f"订单校验页面,提示框消息内容【{text}】获取完成")
62
+ return text
63
+ except (Exception,) as e:
64
+ logger.error(e)
65
+
27
66
  async def get_order_info_plane(self, timeout: float = 5.0, refresh_attempt: int = 3) -> Locator:
28
67
  """
29
68
  获取订单校验页中的订单信息版面,注意这里有个小坑,经常出现已经进入了该页面,但是订单信息没有加载出来,需要不断尝试刷新页面