python-qdairlines-helper 0.0.6__py3-none-any.whl → 0.1.4__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.
- {python_qdairlines_helper-0.0.6.dist-info → python_qdairlines_helper-0.1.4.dist-info}/METADATA +2 -1
- python_qdairlines_helper-0.1.4.dist-info/RECORD +35 -0
- qdairlines_helper/config/url_const.py +2 -1
- qdairlines_helper/controller/add_passenger.py +29 -26
- qdairlines_helper/controller/book_payment.py +74 -41
- qdairlines_helper/controller/book_search.py +51 -26
- qdairlines_helper/controller/cash_pax_info.py +11 -8
- qdairlines_helper/controller/home.py +4 -3
- qdairlines_helper/controller/nhlms_cashdesk.py +32 -18
- qdairlines_helper/controller/order_detail.py +43 -34
- qdairlines_helper/controller/order_query.py +9 -6
- qdairlines_helper/controller/order_verify.py +9 -4
- qdairlines_helper/controller/pay_success.py +66 -0
- qdairlines_helper/controller/user_login.py +14 -9
- qdairlines_helper/po/add_passenger_page.py +3 -2
- qdairlines_helper/po/book_search_page.py +2 -3
- qdairlines_helper/po/cash_pax_info_page.py +10 -10
- qdairlines_helper/po/nhlms_cash_desk_page.py +2 -1
- qdairlines_helper/po/order_verify_page.py +0 -2
- qdairlines_helper/po/pay_success_page.py +33 -0
- qdairlines_helper/utils/exception_utils.py +91 -3
- python_qdairlines_helper-0.0.6.dist-info/RECORD +0 -34
- qdairlines_helper/utils/log_utils.py +0 -14
- {python_qdairlines_helper-0.0.6.dist-info → python_qdairlines_helper-0.1.4.dist-info}/WHEEL +0 -0
- {python_qdairlines_helper-0.0.6.dist-info → python_qdairlines_helper-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {python_qdairlines_helper-0.0.6.dist-info → python_qdairlines_helper-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -10,17 +10,21 @@
|
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
12
|
import inspect
|
|
13
|
-
from
|
|
13
|
+
from logging import Logger
|
|
14
|
+
from typing import Callable
|
|
14
15
|
from playwright.async_api import BrowserContext
|
|
15
|
-
from qdairlines_helper.utils.log_utils import logger
|
|
16
16
|
import qdairlines_helper.config.url_const as url_const
|
|
17
17
|
from qdairlines_helper.po.nhlms_cash_desk_page import NhlmsCashDeskPage
|
|
18
18
|
from playwright_helper.utils.browser_utils import switch_for_table_window
|
|
19
19
|
from qdairlines_helper.utils.exception_utils import DuplicatePaymentError
|
|
20
|
+
from flight_helper.models.dto.payment import HFPaidAccountPaymentInputDTO, PaymentResultDTO
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
async def load_nhlms_cash_desk_po(
|
|
23
|
-
|
|
23
|
+
async def load_nhlms_cash_desk_po(
|
|
24
|
+
*, context: BrowserContext, logger: Logger, domain: str, protocol: str, timeout: float = 60.0
|
|
25
|
+
) -> NhlmsCashDeskPage:
|
|
26
|
+
url_prefix = f"{protocol}://{domain}"
|
|
27
|
+
nhlms_cashdesk_url = url_prefix + url_const.nhlms_cashdesk_url
|
|
24
28
|
|
|
25
29
|
current_page = await switch_for_table_window(
|
|
26
30
|
browser=context, url_keyword=url_const.nhlms_cashdesk_url, wait_time=int(timeout)
|
|
@@ -32,10 +36,10 @@ async def load_nhlms_cash_desk_po(*, context: BrowserContext, timeout: float = 6
|
|
|
32
36
|
return nhlms_cashdesk_po
|
|
33
37
|
|
|
34
38
|
|
|
35
|
-
async def
|
|
36
|
-
*, page: NhlmsCashDeskPage,
|
|
37
|
-
|
|
38
|
-
) ->
|
|
39
|
+
async def hf_paid_account_payment(
|
|
40
|
+
*, page: NhlmsCashDeskPage, logger: Logger, order_no: str, is_pay_completed_callback: Callable,
|
|
41
|
+
hf_paid_account_payment_dto: HFPaidAccountPaymentInputDTO, timeout: float = 60.0
|
|
42
|
+
) -> PaymentResultDTO:
|
|
39
43
|
# 1. 获取收银台支付流水
|
|
40
44
|
pay_transaction = await page.get_order_transaction(timeout=timeout)
|
|
41
45
|
logger.info(f"汇付天下收银台页面,支付流水<{pay_transaction}>获取完成")
|
|
@@ -45,30 +49,40 @@ async def pay_account_payment(
|
|
|
45
49
|
logger.info(f"汇付天下收银台页面,支付金额<{actual_payment_amount}>获取完成")
|
|
46
50
|
|
|
47
51
|
# 3. 获取付款方式tab
|
|
48
|
-
payment_type_tab = await page.get_payment_type_tab(
|
|
52
|
+
payment_type_tab = await page.get_payment_type_tab(
|
|
53
|
+
payment_type=hf_paid_account_payment_dto.payment_type, timeout=timeout
|
|
54
|
+
)
|
|
49
55
|
await payment_type_tab.click(button="left")
|
|
50
|
-
logger.info(f"汇付天下收银台页面,【{payment_type}】Tab点击完成")
|
|
56
|
+
logger.info(f"汇付天下收银台页面,【{hf_paid_account_payment_dto.payment_type}】Tab点击完成")
|
|
51
57
|
|
|
52
58
|
# 4. 输入操作员号
|
|
53
59
|
username_input = await page.get_username_input(timeout=timeout)
|
|
54
|
-
await username_input.fill(value=
|
|
55
|
-
logger.info(f"汇付天下收银台页面,操作员号<{
|
|
60
|
+
await username_input.fill(value=hf_paid_account_payment_dto.account)
|
|
61
|
+
logger.info(f"汇付天下收银台页面,操作员号<{hf_paid_account_payment_dto.account}>输入完成")
|
|
56
62
|
|
|
57
63
|
# 5. 输入交易密码
|
|
58
64
|
password_input = await page.get_password_input(timeout=timeout)
|
|
59
|
-
await password_input.fill(value=
|
|
60
|
-
logger.info(f"汇付天下收银台页面,交易密码<{
|
|
65
|
+
await password_input.fill(value=hf_paid_account_payment_dto.password)
|
|
66
|
+
logger.info(f"汇付天下收银台页面,交易密码<{hf_paid_account_payment_dto.password}>输入完成")
|
|
61
67
|
|
|
62
68
|
# 6. 校验订单是否已经被支付
|
|
63
69
|
if inspect.iscoroutinefunction(is_pay_completed_callback):
|
|
64
|
-
is_pay: bool = await is_pay_completed_callback(order_id=
|
|
70
|
+
is_pay: bool = await is_pay_completed_callback(order_id=order_no)
|
|
65
71
|
else:
|
|
66
|
-
is_pay: bool = is_pay_completed_callback(order_id=
|
|
72
|
+
is_pay: bool = is_pay_completed_callback(order_id=order_no)
|
|
67
73
|
if is_pay is True:
|
|
68
|
-
raise DuplicatePaymentError(
|
|
74
|
+
raise DuplicatePaymentError(order_no=order_no)
|
|
69
75
|
|
|
70
76
|
# 6. 点击【确认支付】
|
|
71
77
|
confirm_payment_btn = await page.get_confirm_payment_btn(timeout=timeout)
|
|
72
78
|
await confirm_payment_btn.click(button="left")
|
|
73
79
|
logger.info("汇付天下收银台页面,【确认支付】按钮点击完成")
|
|
74
|
-
return
|
|
80
|
+
return PaymentResultDTO(
|
|
81
|
+
channel_name=hf_paid_account_payment_dto.channel_name,
|
|
82
|
+
payment_type=hf_paid_account_payment_dto.payment_type,
|
|
83
|
+
account=hf_paid_account_payment_dto.account,
|
|
84
|
+
password=hf_paid_account_payment_dto.password,
|
|
85
|
+
order_no=order_no,
|
|
86
|
+
pay_amount=actual_payment_amount,
|
|
87
|
+
pay_transaction=pay_transaction
|
|
88
|
+
)
|
|
@@ -9,45 +9,54 @@
|
|
|
9
9
|
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
|
+
import re
|
|
12
13
|
from aiohttp import CookieJar
|
|
13
14
|
from typing import Dict, Any, Optional, List
|
|
14
|
-
from qdairlines_helper.utils.log_utils import logger
|
|
15
15
|
from qdairlines_helper.http.flight_order import FlightOrder
|
|
16
|
+
from flight_helper.models.dto.itinerary import QueryItineraryResponseDTO, QueryItineraryRequestDTO, ItineraryInfoDTO
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
async def
|
|
19
|
-
*,
|
|
20
|
-
|
|
21
|
-
timeout: Optional[int] = None, retry: Optional[int] = None, enable_log: Optional[bool] = None,
|
|
22
|
-
cookie_jar: Optional[CookieJar] = None, headers: Dict[str, str] = None
|
|
19
|
+
async def get_order_detail(
|
|
20
|
+
*, query_dto: QueryItineraryRequestDTO, timeout: Optional[int] = None, retry: Optional[int] = None,
|
|
21
|
+
cookie_jar: Optional[CookieJar] = None, enable_log: Optional[bool] = None
|
|
23
22
|
) -> Dict[str, Any]:
|
|
24
23
|
flight_order = FlightOrder(
|
|
25
|
-
playwright_state=
|
|
26
|
-
enable_log=enable_log,
|
|
24
|
+
playwright_state=query_dto.storage_state, token=query_dto.token, domain=query_dto.payment_domain,
|
|
25
|
+
protocol=query_dto.payment_protocol, timeout=timeout, retry=retry, enable_log=enable_log,
|
|
26
|
+
cookie_jar=cookie_jar, proxy=query_dto.proxy
|
|
27
|
+
)
|
|
28
|
+
order_info = await flight_order.get_order_details(
|
|
29
|
+
pre_order_no=query_dto.pre_order_no, user_id=query_dto.user_id, is_end=True, proxy=query_dto.proxy,
|
|
30
|
+
headers=query_dto.headers
|
|
31
|
+
)
|
|
32
|
+
if isinstance(order_info, dict) and order_info.get("result") and order_info.get("code") == 1:
|
|
33
|
+
return order_info.get("result")
|
|
34
|
+
else:
|
|
35
|
+
raise RuntimeError(f"订单<{query_dto.pre_order_no}>,获取详情数据异常,返回值:{order_info}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def get_order_itinerary(
|
|
39
|
+
*, query_dto: QueryItineraryRequestDTO, timeout: Optional[int] = None, retry: Optional[int] = None,
|
|
40
|
+
cookie_jar: Optional[CookieJar] = None, enable_log: Optional[bool] = None
|
|
41
|
+
) -> Optional[QueryItineraryResponseDTO]:
|
|
42
|
+
_order_info: Dict[str, Any] = dict(pre_order_no=query_dto.pre_order_no)
|
|
43
|
+
result = await get_order_detail(
|
|
44
|
+
query_dto=query_dto, timeout=timeout, retry=retry, cookie_jar=cookie_jar, enable_log=enable_log
|
|
27
45
|
)
|
|
28
|
-
_order_info
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
passenger=passenger, id_no=id_no, order_itinerary=order_itinerary, pre_order_no=pre_order_no
|
|
46
|
-
))
|
|
47
|
-
if order_itineraries:
|
|
48
|
-
_order_info["order_itineraries"] = order_itineraries
|
|
49
|
-
else:
|
|
50
|
-
raise RuntimeError(f"订单<{pre_order_no}>,获取订单详情数据异常,返回值:{order_info}")
|
|
51
|
-
except Exception as e:
|
|
52
|
-
logger.error(e)
|
|
53
|
-
return _order_info
|
|
46
|
+
_order_info["cash_unit"] = result.get("cashUnit")
|
|
47
|
+
_order_info["order_status"] = result.get("orderStatus")
|
|
48
|
+
_order_info["order_amount"] = result.get("totalPrice")
|
|
49
|
+
passenger_infos: List[Dict[str, Any]] = result.get("passengerInfoVoList")
|
|
50
|
+
order_itineraries = list()
|
|
51
|
+
for passenger_info in passenger_infos:
|
|
52
|
+
id_no = passenger_info.get("idNo")
|
|
53
|
+
order_itinerary = passenger_info.get("tktNo")
|
|
54
|
+
passenger = passenger_info.get("passName")
|
|
55
|
+
if bool(re.fullmatch(r'\d+-\d+', order_itinerary)):
|
|
56
|
+
order_itineraries.append(dict(
|
|
57
|
+
passenger_name=passenger, id_no=id_no, order_itinerary=order_itinerary,
|
|
58
|
+
pre_order_no=query_dto.pre_order_no
|
|
59
|
+
))
|
|
60
|
+
if order_itineraries:
|
|
61
|
+
_order_info["itinerary_info"] = order_itineraries
|
|
62
|
+
return QueryItineraryResponseDTO(**_order_info)
|
|
@@ -10,14 +10,17 @@
|
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
12
|
from typing import Any
|
|
13
|
+
from logging import Logger
|
|
13
14
|
from playwright.async_api import Page
|
|
14
|
-
from qdairlines_helper.utils.log_utils import logger
|
|
15
15
|
import qdairlines_helper.config.url_const as url_const
|
|
16
16
|
from qdairlines_helper.po.air_order_page import AirOrderPage
|
|
17
|
+
from qdairlines_helper.utils.exception_utils import IPBlockError
|
|
17
18
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
async def
|
|
21
|
+
async def open_air_order_page(
|
|
22
|
+
*, page: Page, logger: Logger, protocol: str, domain: str, timeout: float = 60.0
|
|
23
|
+
) -> AirOrderPage:
|
|
21
24
|
url_prefix = f"{protocol}://{domain}"
|
|
22
25
|
air_order_url = url_prefix + url_const.air_order_url
|
|
23
26
|
await page.goto(air_order_url)
|
|
@@ -27,15 +30,15 @@ async def open_air_page(*, page: Page, protocol: str, domain: str, timeout: floa
|
|
|
27
30
|
logger.info(f"即将进入青岛航空官网航班查询页面,页面URL<{air_order_url}>")
|
|
28
31
|
ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
|
|
29
32
|
if ip_access_blocked_msg:
|
|
30
|
-
raise
|
|
33
|
+
raise IPBlockError(ip_access_blocked_msg)
|
|
31
34
|
return air_order_po
|
|
32
35
|
|
|
33
36
|
|
|
34
|
-
async def
|
|
35
|
-
*, page: Page, protocol: str, domain: str, timeout: float = 60.0, **kwargs: Any
|
|
37
|
+
async def is_open_air_order_page_callback(
|
|
38
|
+
*, page: Page, protocol: str, logger: Logger, domain: str, timeout: float = 60.0, **kwargs: Any
|
|
36
39
|
) -> bool:
|
|
37
40
|
try:
|
|
38
|
-
await
|
|
41
|
+
await open_air_order_page(page=page, logger=logger, protocol=protocol, domain=domain, timeout=timeout)
|
|
39
42
|
return True
|
|
40
43
|
except (Exception,):
|
|
41
44
|
return False
|
|
@@ -9,14 +9,17 @@
|
|
|
9
9
|
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
|
+
from logging import Logger
|
|
12
13
|
from playwright.async_api import Page
|
|
13
|
-
from qdairlines_helper.utils.log_utils import logger
|
|
14
14
|
import qdairlines_helper.config.url_const as url_const
|
|
15
|
+
from qdairlines_helper.utils.exception_utils import IPBlockError
|
|
15
16
|
from qdairlines_helper.po.order_verify_page import OrderVerifyPage
|
|
16
17
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
async def load_order_verify_po(
|
|
20
|
+
async def load_order_verify_po(
|
|
21
|
+
*, page: Page, logger: Logger, protocol: str, domain: str, timeout: float = 60.0
|
|
22
|
+
) -> OrderVerifyPage:
|
|
20
23
|
url_prefix = f"{protocol}://{domain}"
|
|
21
24
|
order_verify_url = url_prefix + url_const.order_verify_url
|
|
22
25
|
order_verify_po = OrderVerifyPage(page=page, url=order_verify_url)
|
|
@@ -24,11 +27,13 @@ async def load_order_verify_po(*, page: Page, protocol: str, domain: str, timeou
|
|
|
24
27
|
logger.info(f"即将进入青岛航空官网订单校验页面,页面URL<{order_verify_url}>")
|
|
25
28
|
ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
|
|
26
29
|
if ip_access_blocked_msg:
|
|
27
|
-
raise
|
|
30
|
+
raise IPBlockError(ip_access_blocked_msg)
|
|
28
31
|
return order_verify_po
|
|
29
32
|
|
|
30
33
|
|
|
31
|
-
async def order_verify(
|
|
34
|
+
async def order_verify(
|
|
35
|
+
*, page: OrderVerifyPage, logger: Logger, refresh_attempt: int = 3, timeout: float = 60.0
|
|
36
|
+
) -> None:
|
|
32
37
|
# 1. 先确认订单数据是否加载出来
|
|
33
38
|
await page.get_order_info_plane(timeout=timeout, refresh_attempt=refresh_attempt)
|
|
34
39
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
4
|
+
# ProjectName: python-qdairlines-helper
|
|
5
|
+
# FileName: pay_success.py
|
|
6
|
+
# Description: 支付成功控制器
|
|
7
|
+
# Author: ASUS
|
|
8
|
+
# CreateDate: 2026/01/09
|
|
9
|
+
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
11
|
+
"""
|
|
12
|
+
import inspect
|
|
13
|
+
from logging import Logger
|
|
14
|
+
from aiohttp import CookieJar
|
|
15
|
+
from playwright.async_api import Page
|
|
16
|
+
from typing import Dict, Any, Optional, Callable
|
|
17
|
+
import qdairlines_helper.config.url_const as url_const
|
|
18
|
+
from qdairlines_helper.po.pay_success_page import PaySuccessPage
|
|
19
|
+
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
20
|
+
from qdairlines_helper.controller.order_detail import get_order_detail
|
|
21
|
+
from flight_helper.models.dto.itinerary import QueryItineraryRequestDTO
|
|
22
|
+
from qdairlines_helper.utils.exception_utils import IPBlockError, PaymentFailedError
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def load_pay_success_page(
|
|
26
|
+
*, page: Page, logger: Logger, protocol: str, domain: str, timeout: float = 60.0
|
|
27
|
+
) -> PaySuccessPage:
|
|
28
|
+
url_prefix = f"{protocol}://{domain}"
|
|
29
|
+
pay_success_url = url_prefix + url_const.pay_success_url
|
|
30
|
+
pay_success_po = PaySuccessPage(page=page, url=pay_success_url)
|
|
31
|
+
await pay_success_po.url_wait_for(url=pay_success_url, timeout=timeout)
|
|
32
|
+
logger.info(f"即将进入青岛航空官网支付成功页面,页面URL<{pay_success_url}>")
|
|
33
|
+
ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
|
|
34
|
+
if ip_access_blocked_msg:
|
|
35
|
+
raise IPBlockError(ip_access_blocked_msg)
|
|
36
|
+
return pay_success_po
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def two_check_pay_success(
|
|
40
|
+
*, page: Page, logger: Logger, query_dto: QueryItineraryRequestDTO, callback_get_proxy: Callable,
|
|
41
|
+
cookie_jar: Optional[CookieJar] = None, timeout: float = 60.0, retry: int = 0, enable_log: bool = True
|
|
42
|
+
) -> bool:
|
|
43
|
+
try:
|
|
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
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logger.error(e)
|
|
54
|
+
|
|
55
|
+
if inspect.iscoroutinefunction(callback_get_proxy):
|
|
56
|
+
query_dto.proxy = await callback_get_proxy(logger=logger)
|
|
57
|
+
else:
|
|
58
|
+
query_dto.proxy = callback_get_proxy(logger=logger)
|
|
59
|
+
|
|
60
|
+
# 2. 尝试取查询票号,看订单状态,是 BOOKED,还是 TICKETED
|
|
61
|
+
order_detail: Dict[str, Any] = await get_order_detail(
|
|
62
|
+
query_dto=query_dto, timeout=int(timeout), retry=retry, enable_log=enable_log, cookie_jar=cookie_jar,
|
|
63
|
+
)
|
|
64
|
+
order_status = (order_detail.get("orderStatus", "")).upper()
|
|
65
|
+
if order_status not in ("TICKED",):
|
|
66
|
+
raise PaymentFailedError(pre_order_no=query_dto.pre_order_no)
|
|
@@ -9,17 +9,20 @@
|
|
|
9
9
|
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
|
+
from logging import Logger
|
|
12
13
|
from typing import Dict, Any
|
|
13
|
-
from qdairlines_helper.utils.log_utils import logger
|
|
14
14
|
from playwright.async_api import Page, BrowserContext
|
|
15
15
|
from qdairlines_helper.po.login_page import LoginPage
|
|
16
16
|
import qdairlines_helper.config.url_const as url_const
|
|
17
17
|
from qdairlines_helper.controller.home import load_home_po
|
|
18
|
+
from qdairlines_helper.utils.exception_utils import IPBlockError
|
|
18
19
|
from playwright.async_api import Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError
|
|
19
20
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg, parse_user_info_from_page_response
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
async def open_login_page(
|
|
23
|
+
async def open_login_page(
|
|
24
|
+
*, page: Page, logger: Logger, protocol: str, domain: str, timeout: float = 60.0
|
|
25
|
+
) -> LoginPage:
|
|
23
26
|
url_prefix = f"{protocol}://{domain}"
|
|
24
27
|
login_url = url_prefix + url_const.login_url
|
|
25
28
|
await page.goto(login_url)
|
|
@@ -29,12 +32,13 @@ async def open_login_page(*, page: Page, protocol: str, domain: str, timeout: fl
|
|
|
29
32
|
logger.info(f"即将进入青岛航空官网登录页面,页面URL<{login_url}>")
|
|
30
33
|
ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
|
|
31
34
|
if ip_access_blocked_msg:
|
|
32
|
-
raise
|
|
35
|
+
raise IPBlockError(ip_access_blocked_msg)
|
|
33
36
|
return login_po
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
async def _user_login(
|
|
37
|
-
*, page: LoginPage, protocol: str, domain: str, username: str, password: str,
|
|
40
|
+
*, page: LoginPage, logger: Logger, protocol: str, domain: str, username: str, password: str,
|
|
41
|
+
timeout: float = 60.0
|
|
38
42
|
) -> None:
|
|
39
43
|
# 1. 输入用户名
|
|
40
44
|
username_input = await page.get_login_username_input(timeout=timeout)
|
|
@@ -62,18 +66,18 @@ async def _user_login(
|
|
|
62
66
|
|
|
63
67
|
# 5. 加载首页页面对象,加载成功说明进入了首页,登录成功
|
|
64
68
|
try:
|
|
65
|
-
await load_home_po(page=page.get_page(), protocol=protocol, domain=domain, timeout=timeout)
|
|
69
|
+
await load_home_po(page=page.get_page(), logger=logger, protocol=protocol, domain=domain, timeout=timeout)
|
|
66
70
|
logger.info(f"青岛航空官网登录页面,用户:{username}, 密码:{password}登录成功")
|
|
67
71
|
except (PlaywrightError, PlaywrightTimeoutError, EnvironmentError, RuntimeError, Exception) as e:
|
|
68
72
|
raise RuntimeError(f"青岛航空官网登录页面,用户:{username}, 密码:{password}登录失败,原因:{e}")
|
|
69
73
|
|
|
70
74
|
|
|
71
75
|
async def user_login_callback(
|
|
72
|
-
*, page: Page, context: BrowserContext,
|
|
73
|
-
timeout: float = 60.0, **kwargs: Any
|
|
76
|
+
*, page: Page, context: BrowserContext, logger: Logger, protocol: str, domain: str, username: str,
|
|
77
|
+
password: str, timeout: float = 60.0, **kwargs: Any
|
|
74
78
|
) -> Dict[str, Any]:
|
|
75
79
|
# 1. 打开登录页面
|
|
76
|
-
login_po = await open_login_page(page=page, protocol=protocol, domain=domain, timeout=timeout)
|
|
80
|
+
login_po = await open_login_page(page=page, logger=logger, protocol=protocol, domain=domain, timeout=timeout)
|
|
77
81
|
|
|
78
82
|
# 2. 开启网络监听
|
|
79
83
|
keywords = [url_const.auth_form_api_url, url_const.login_after_api_url]
|
|
@@ -84,7 +88,8 @@ async def user_login_callback(
|
|
|
84
88
|
|
|
85
89
|
# 2. 执行登录操作
|
|
86
90
|
await _user_login(
|
|
87
|
-
page=login_po, protocol=protocol, domain=domain, username=username, password=password,
|
|
91
|
+
page=login_po, protocol=protocol, logger=logger, domain=domain, username=username, password=password,
|
|
92
|
+
timeout=timeout
|
|
88
93
|
)
|
|
89
94
|
user_info = parse_user_info_from_page_response(network_logs=network_logs)
|
|
90
95
|
user_info.update(dict(storage_state=await context.storage_state()))
|
|
@@ -13,6 +13,7 @@ from typing import Optional
|
|
|
13
13
|
from playwright.async_api import Page, Locator
|
|
14
14
|
from playwright_helper.libs.base_po import BasePo
|
|
15
15
|
import qdairlines_helper.config.url_const as url_const
|
|
16
|
+
from qdairlines_helper.utils.exception_utils import PassengerTypeError
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class AddPassengerPage(BasePo):
|
|
@@ -37,7 +38,7 @@ class AddPassengerPage(BasePo):
|
|
|
37
38
|
elif passenger_type == "婴儿":
|
|
38
39
|
btn_text = "添加婴儿"
|
|
39
40
|
else:
|
|
40
|
-
raise
|
|
41
|
+
raise PassengerTypeError(passenger_type=passenger_type)
|
|
41
42
|
selector: str = f'//div[@class="el-row"]//button[@class="search_btn" and contains(text(), "{btn_text}")]'
|
|
42
43
|
return await self.get_locator(selector=selector, timeout=timeout)
|
|
43
44
|
|
|
@@ -68,7 +69,7 @@ class AddPassengerPage(BasePo):
|
|
|
68
69
|
elif passenger_type == "婴儿":
|
|
69
70
|
passenger_class = f"passengerBaby{position_index}"
|
|
70
71
|
else:
|
|
71
|
-
raise
|
|
72
|
+
raise PassengerTypeError(passenger_type=passenger_type)
|
|
72
73
|
selector: str = f'//label[@for="{passenger_class}"]/span[@class="checkbox"]'
|
|
73
74
|
return await self.get_locator(selector=selector, timeout=timeout)
|
|
74
75
|
|
|
@@ -13,8 +13,8 @@ import re
|
|
|
13
13
|
from typing import Optional, Dict, Any, List
|
|
14
14
|
from playwright.async_api import Page, Locator
|
|
15
15
|
from playwright_helper.libs.base_po import BasePo
|
|
16
|
-
from qdairlines_helper.utils.log_utils import logger
|
|
17
16
|
import qdairlines_helper.config.url_const as url_const
|
|
17
|
+
from qdairlines_helper.utils.exception_utils import ProductTypeError
|
|
18
18
|
from playwright_helper.utils.type_utils import convert_order_amount_text, safe_convert_advanced
|
|
19
19
|
from playwright.async_api import Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError
|
|
20
20
|
|
|
@@ -107,7 +107,7 @@ class BookSearchPage(BasePo):
|
|
|
107
107
|
elif product_type == "公务舱":
|
|
108
108
|
index = 3
|
|
109
109
|
else:
|
|
110
|
-
raise
|
|
110
|
+
raise ProductTypeError(product_type=product_type)
|
|
111
111
|
selector: str = f'xpath=(.//div[contains(@class, "nav_item el-col el-col-24")])[{index}]'
|
|
112
112
|
return await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
113
113
|
|
|
@@ -131,7 +131,6 @@ class BookSearchPage(BasePo):
|
|
|
131
131
|
amounts=amounts, cabin=cabin, seats_status=seats_status, booking_btn=booking_btn
|
|
132
132
|
)
|
|
133
133
|
except (PlaywrightError, PlaywrightTimeoutError, EnvironmentError, RuntimeError, Exception) as e:
|
|
134
|
-
logger.warning(e)
|
|
135
134
|
continue
|
|
136
135
|
return flight_products
|
|
137
136
|
|
|
@@ -13,8 +13,8 @@ from typing import Optional
|
|
|
13
13
|
from urllib.parse import urlparse, parse_qs
|
|
14
14
|
from playwright.async_api import Page, Locator
|
|
15
15
|
from playwright_helper.libs.base_po import BasePo
|
|
16
|
-
from qdairlines_helper.utils.log_utils import logger
|
|
17
16
|
import qdairlines_helper.config.url_const as url_const
|
|
17
|
+
from qdairlines_helper.utils.exception_utils import PaymentChannelError
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class CashPaxInfoPage(BasePo):
|
|
@@ -25,23 +25,23 @@ class CashPaxInfoPage(BasePo):
|
|
|
25
25
|
super().__init__(page, url or url_const.cash_pax_info_url)
|
|
26
26
|
self.__page = page
|
|
27
27
|
|
|
28
|
-
async def
|
|
28
|
+
async def get_payment_channel_checkbox(self, channel_name: str, timeout: float = 5.0) -> Locator:
|
|
29
29
|
"""
|
|
30
|
-
|
|
31
|
-
:param
|
|
30
|
+
获取支付方式页面中的支付渠道【支付宝|微信|汇付天下|易宝支付】单选框
|
|
31
|
+
:param channel_name: 支付渠道,支付宝|微信|汇付天下|易宝支付
|
|
32
32
|
:param timeout: 超时时间(秒)
|
|
33
33
|
:return: (是否存在, 错误信息|元素对象)
|
|
34
34
|
"""
|
|
35
|
-
if
|
|
35
|
+
if channel_name == '支付宝':
|
|
36
36
|
for_class = 0
|
|
37
|
-
elif
|
|
37
|
+
elif channel_name == "微信":
|
|
38
38
|
for_class = 1
|
|
39
|
-
elif
|
|
39
|
+
elif channel_name == "汇付天下":
|
|
40
40
|
for_class = 2
|
|
41
|
-
elif
|
|
41
|
+
elif channel_name == "易宝支付":
|
|
42
42
|
for_class = 3
|
|
43
43
|
else:
|
|
44
|
-
raise
|
|
44
|
+
raise PaymentChannelError(channel_name=channel_name)
|
|
45
45
|
selector: str = f'xpath=//label[@for="passengerAdult{for_class}"]//span[@class="checkbox"]'
|
|
46
46
|
return await self.get_locator(selector=selector, timeout=timeout)
|
|
47
47
|
|
|
@@ -68,7 +68,7 @@ class CashPaxInfoPage(BasePo):
|
|
|
68
68
|
if pre_order_no and len(pre_order_no) == 18 and pre_order_no.startswith("OW") is True:
|
|
69
69
|
return pre_order_no
|
|
70
70
|
except (Exception,):
|
|
71
|
-
|
|
71
|
+
pass
|
|
72
72
|
selector: str = '//div[@class="order-form"]//span[contains(text(), "订单编号")]/../span[@class="font_red"]'
|
|
73
73
|
locator = await self.get_locator(selector=selector, timeout=timeout)
|
|
74
74
|
return (await locator.inner_text()).strip()
|
|
@@ -14,6 +14,7 @@ from playwright.async_api import Page, Locator
|
|
|
14
14
|
from playwright_helper.libs.base_po import BasePo
|
|
15
15
|
import qdairlines_helper.config.url_const as url_const
|
|
16
16
|
from playwright_helper.utils.type_utils import safe_convert_advanced
|
|
17
|
+
from qdairlines_helper.utils.exception_utils import HFPaymentTypeError
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class NhlmsCashDeskPage(BasePo):
|
|
@@ -62,7 +63,7 @@ class NhlmsCashDeskPage(BasePo):
|
|
|
62
63
|
elif payment_type == "快捷支付":
|
|
63
64
|
value = "fp"
|
|
64
65
|
else:
|
|
65
|
-
raise
|
|
66
|
+
raise HFPaymentTypeError(payment_type=payment_type)
|
|
66
67
|
selector: str = f'//div[@class="content-bottom network-recharge"]//li[@value="{value}"]'
|
|
67
68
|
return await self.get_locator(selector=selector, timeout=timeout)
|
|
68
69
|
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
from typing import Optional
|
|
13
13
|
from playwright.async_api import Page, Locator
|
|
14
14
|
from playwright_helper.libs.base_po import BasePo
|
|
15
|
-
from qdairlines_helper.utils.log_utils import logger
|
|
16
15
|
import qdairlines_helper.config.url_const as url_const
|
|
17
16
|
from playwright.async_api import Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError
|
|
18
17
|
|
|
@@ -40,7 +39,6 @@ class OrderVerifyPage(BasePo):
|
|
|
40
39
|
return await self.get_locator(selector=selector, timeout=timeout)
|
|
41
40
|
except (PlaywrightError, PlaywrightTimeoutError, EnvironmentError, RuntimeError, Exception) as e:
|
|
42
41
|
attempt += 1
|
|
43
|
-
logger.error(f"订单校验页面,第<{attempt}>次尝试刷新加载订单数据失败,原因:{e}")
|
|
44
42
|
raise RuntimeError("订单校验页面中的订单信息加载出现异常,可能是网络拥塞或者用户被风控")
|
|
45
43
|
|
|
46
44
|
async def get_agree_checkbox(self, timeout: float = 5.0) -> Locator:
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
4
|
+
# ProjectName: python-qdairlines-helper
|
|
5
|
+
# FileName: pay_success_page.py
|
|
6
|
+
# Description: 支付成功页面对象
|
|
7
|
+
# Author: ASUS
|
|
8
|
+
# CreateDate: 2026/01/09
|
|
9
|
+
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
11
|
+
"""
|
|
12
|
+
from typing import Optional
|
|
13
|
+
from playwright.async_api import Page, Locator
|
|
14
|
+
from playwright_helper.libs.base_po import BasePo
|
|
15
|
+
import qdairlines_helper.config.url_const as url_const
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PaySuccessPage(BasePo):
|
|
19
|
+
url: str = url_const.pay_success_url
|
|
20
|
+
__page: Page
|
|
21
|
+
|
|
22
|
+
def __init__(self, page: Page, url: Optional[str] = None) -> None:
|
|
23
|
+
super().__init__(page, url or url_const.pay_success_url)
|
|
24
|
+
self.__page = page
|
|
25
|
+
|
|
26
|
+
async def get_pay_success_image(self, timeout: float = 5.0) -> Locator:
|
|
27
|
+
"""
|
|
28
|
+
获取支付成功image图标
|
|
29
|
+
:param timeout: 超时时间(秒)
|
|
30
|
+
:return: (是否存在, 错误信息|元素对象)
|
|
31
|
+
"""
|
|
32
|
+
selector: str = '//img[@alt="支付成功"]'
|
|
33
|
+
return await self.get_locator(selector=selector, timeout=timeout)
|