python-qdairlines-helper 0.0.6__py3-none-any.whl → 0.2.6__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.2.6.dist-info}/METADATA +2 -1
- python_qdairlines_helper-0.2.6.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 +73 -41
- qdairlines_helper/controller/book_search.py +105 -52
- 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 +46 -28
- 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.2.6.dist-info}/WHEEL +0 -0
- {python_qdairlines_helper-0.0.6.dist-info → python_qdairlines_helper-0.2.6.dist-info}/licenses/LICENSE +0 -0
- {python_qdairlines_helper-0.0.6.dist-info → python_qdairlines_helper-0.2.6.dist-info}/top_level.txt +0 -0
{python_qdairlines_helper-0.0.6.dist-info → python_qdairlines_helper-0.2.6.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python_qdairlines_helper
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: qdairlines helper python package
|
|
5
5
|
Author-email: ckf10000 <ckf10000@sina.com>
|
|
6
6
|
License: Apache License
|
|
@@ -212,6 +212,7 @@ Description-Content-Type: text/markdown
|
|
|
212
212
|
License-File: LICENSE
|
|
213
213
|
Requires-Dist: python_playwright_helper>=0.3.7
|
|
214
214
|
Requires-Dist: python_http_helper>=0.2.1
|
|
215
|
+
Requires-Dist: flight_helper>=0.1.3
|
|
215
216
|
Dynamic: license-file
|
|
216
217
|
|
|
217
218
|
# python-qdairlines-helper
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
python_qdairlines_helper-0.2.6.dist-info/licenses/LICENSE,sha256=WtjCEwlcVzkh1ziO35P2qfVEkLjr87Flro7xlHz3CEY,11556
|
|
2
|
+
qdairlines_helper/__init__.py,sha256=bN1FqEK_jvefO_HU_8HFJpjuOzHTsuhmRe5KAzmraR4,479
|
|
3
|
+
qdairlines_helper/config/__init__.py,sha256=JjIRocePx3gwFNpdauuiwBU6B2Ug565xaHLEMu5Oo0I,479
|
|
4
|
+
qdairlines_helper/config/url_const.py,sha256=Ro-8r3dRxuloJC5NIpwUMQ7Pezg2cDyZOqNUfTqCOFQ,2086
|
|
5
|
+
qdairlines_helper/controller/__init__.py,sha256=oyeciEBlDLNpqHblB0R-nXQG3HsI8L5mmlWulQ7Y38Q,482
|
|
6
|
+
qdairlines_helper/controller/add_passenger.py,sha256=GxaoSoLOH_XXTR79bDtSEbnmM_dSpBko6OzlrQtuT7U,5159
|
|
7
|
+
qdairlines_helper/controller/book_payment.py,sha256=x_jrgmZ4REMn-Zii7ZUQpuX3j8YAEFe8omt1Fm1z7YY,6059
|
|
8
|
+
qdairlines_helper/controller/book_search.py,sha256=5Tnuolm92HwbIgwhg1SF7e7bPbV2ZRluVTfVxjna-oI,9153
|
|
9
|
+
qdairlines_helper/controller/cash_pax_info.py,sha256=V5QKlvbWp8eF6gRos818w-9MyuienjgECkKKZkxCys8,2511
|
|
10
|
+
qdairlines_helper/controller/home.py,sha256=KzTyl7vvI3_Blc-XQ8ItueGR1hnV6f9gEdYalfXG9Ws,1394
|
|
11
|
+
qdairlines_helper/controller/nhlms_cashdesk.py,sha256=Fai8zZiqhUJl5-RWdkaGOdB3GTd5UQ7fpXDt4Emv65w,4312
|
|
12
|
+
qdairlines_helper/controller/order_detail.py,sha256=LEYrxcGx3kcYuJ9IZa-crRhCJLYTppdMVUu8hEXGrnI,3154
|
|
13
|
+
qdairlines_helper/controller/order_query.py,sha256=JzNrJj6HRHSJMQhszucWPf6nWQl4Y5B7oE1N9SpD47k,1907
|
|
14
|
+
qdairlines_helper/controller/order_verify.py,sha256=G9CC4a6KZg-VdWH6hzHDN7DkaoW2J9_yc7-mE2fL7Ms,2273
|
|
15
|
+
qdairlines_helper/controller/pay_success.py,sha256=tegkKa2b2R8ZqYTtGI6LqqW7h3SShpvM26cf_syi14E,3180
|
|
16
|
+
qdairlines_helper/controller/user_login.py,sha256=7PBbzP7doeUEy4M_Igb32IhjT4GzY_I-U3YimxekR_Q,4735
|
|
17
|
+
qdairlines_helper/http/__init__.py,sha256=96AJPf2zgw_rm3Wv-boIBltWFgZH3Yo-fn5M9SRK280,489
|
|
18
|
+
qdairlines_helper/http/flight_order.py,sha256=tC25YNk1pDzMmk4Zy6v120PwNuuaDhlHFh5xPBxtKco,3665
|
|
19
|
+
qdairlines_helper/po/__init__.py,sha256=HoouLJGLu_dI6IAKTVjBRraGHMcAUgntayTph-BUBAE,481
|
|
20
|
+
qdairlines_helper/po/add_passenger_page.py,sha256=XpaU6g7YSLhA8h3bHWJ_6CwnwgUmRtmLulPoRhkQ6_I,8000
|
|
21
|
+
qdairlines_helper/po/air_order_page.py,sha256=0Ux0B2hUGKCoFLRpJqGHS2w690o0A2PDnvRf9-UKfJE,930
|
|
22
|
+
qdairlines_helper/po/book_search_page.py,sha256=6EgA-yAqwzyT6FIMQD901-LmVmht5eBFiwLiKCE_MEs,10848
|
|
23
|
+
qdairlines_helper/po/cash_pax_info_page.py,sha256=iJlmIxD8IKnMyTNcylmoz9jV0k4u-B5ALXZEUcRajY4,3401
|
|
24
|
+
qdairlines_helper/po/home_page.py,sha256=KvoOZOYwKjlJjZOT522K_8ic3_2zaYOPWkuklUHVSlM,907
|
|
25
|
+
qdairlines_helper/po/login_page.py,sha256=SdWmkP_47GeosbFRcfvXLeQxxdM7b9UESvmajWq4-nA,2975
|
|
26
|
+
qdairlines_helper/po/nhlms_cash_desk_page.py,sha256=TwqszVbCc4iRSE684uafdlGJB1VC5wS9ZuGWvfeb8Fo,4542
|
|
27
|
+
qdairlines_helper/po/order_verify_page.py,sha256=omgs6CBPTPCGnO8cxRFd2ePFUrJZVD5cD_GBCXsZIes,2917
|
|
28
|
+
qdairlines_helper/po/pay_success_page.py,sha256=XGddo8as8wf1kCCdvemqHIRWOPIPv1JG8iXEmpH4zck,1329
|
|
29
|
+
qdairlines_helper/utils/__init__.py,sha256=sti2S709puM2mQbQisk0KGq_YNjW6T4zz2vyYXonNB0,479
|
|
30
|
+
qdairlines_helper/utils/exception_utils.py,sha256=rXPao6h9sW5uL3lUA36FS3WTlePAm5EZ6MBjWeJJTUs,3934
|
|
31
|
+
qdairlines_helper/utils/po_utils.py,sha256=74ZVyyxPmPk1tcGQoMauXhBE3qsZJej8urni1ch27io,2417
|
|
32
|
+
python_qdairlines_helper-0.2.6.dist-info/METADATA,sha256=aTq22fxpTiqZKIUBftm2IPxgGj2vxmrO9OxSGppduqU,14424
|
|
33
|
+
python_qdairlines_helper-0.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
34
|
+
python_qdairlines_helper-0.2.6.dist-info/top_level.txt,sha256=MRbQkBMdSG4f5mx2RWfu6aXoDSyM-2KwL-YFqNBdbng,18
|
|
35
|
+
python_qdairlines_helper-0.2.6.dist-info/RECORD,,
|
|
@@ -25,7 +25,6 @@ add_passenger_url: str = "/book/verifySecure"
|
|
|
25
25
|
order_verify_url: str = "/book/orderVerify"
|
|
26
26
|
cash_pax_info_url: str = "/book/cashPaxInfoInput"
|
|
27
27
|
# https://nhlms.cloudpnr.com/nobel/WebEntry.do 汇付天下收银台
|
|
28
|
-
nhlms_cashdesk_domain: str = "https://nhlms.cloudpnr.com"
|
|
29
28
|
nhlms_cashdesk_url: str = "/nobel/WebEntry.do"
|
|
30
29
|
# https://excashier.alipay.com/standard/auth.htm?payOrderId=4e3606b1c842414c88f631c35952a88e.85 支付宝收银台
|
|
31
30
|
alipay_url: str = "/standard/auth.htm"
|
|
@@ -35,3 +34,5 @@ wechat_pay_url: str = "/book/payPreCash"
|
|
|
35
34
|
yeepay_cashdesk_url: str = "/bc-cashier/bcnewpc/request/{}/{}"
|
|
36
35
|
# https://www.qdairlines.com/user/airorder/orderdetail?orderNo=OW20260105B1154687&orderType=2
|
|
37
36
|
order_detail_url: str = "/user/airorder/orderdetail"
|
|
37
|
+
# https://www.qdairlines.com/pay/success
|
|
38
|
+
pay_success_url: str = "/pay/success"
|
|
@@ -9,16 +9,19 @@
|
|
|
9
9
|
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
|
-
from typing import List
|
|
12
|
+
from typing import List
|
|
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
|
+
from flight_helper.models.dto.passenger import PassengerDTO
|
|
17
|
+
from flight_helper.models.dto.booking import BookingInputDTO
|
|
18
|
+
from qdairlines_helper.utils.exception_utils import IPBlockError
|
|
16
19
|
from qdairlines_helper.po.add_passenger_page import AddPassengerPage
|
|
17
20
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
async def load_add_passenger_po(
|
|
21
|
-
*, page: Page, protocol: str, domain: str, timeout: float = 60.0
|
|
24
|
+
*, page: Page, logger: Logger, protocol: str, domain: str, timeout: float = 60.0
|
|
22
25
|
) -> AddPassengerPage:
|
|
23
26
|
url_prefix = f"{protocol}://{domain}"
|
|
24
27
|
add_passenger_url = url_prefix + url_const.add_passenger_url
|
|
@@ -27,59 +30,59 @@ async def load_add_passenger_po(
|
|
|
27
30
|
logger.info(f"即将进入青岛航空官网添加乘客页面,页面URL<{add_passenger_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 add_passenger_po
|
|
32
35
|
|
|
33
36
|
|
|
34
37
|
async def add_passenger(
|
|
35
|
-
*, page: AddPassengerPage,
|
|
38
|
+
*, page: AddPassengerPage, logger: Logger, passengers_dto: List[PassengerDTO], book_input_dto: BookingInputDTO,
|
|
39
|
+
timeout: float = 60.0
|
|
36
40
|
) -> None:
|
|
37
41
|
# 遍历方式添加乘客
|
|
38
|
-
for index,
|
|
39
|
-
p_type = passenger.get("p_type")
|
|
42
|
+
for index, passenger_dto in enumerate(passengers_dto):
|
|
40
43
|
if index != 0:
|
|
41
44
|
# 1. 点击【添加成人|添加儿童|添加婴儿】按钮
|
|
42
|
-
add_adult_btn = await page.get_add_passenger_btn(
|
|
45
|
+
add_adult_btn = await page.get_add_passenger_btn(
|
|
46
|
+
passenger_type=passenger_dto.passenger_type, timeout=timeout
|
|
47
|
+
)
|
|
43
48
|
await add_adult_btn.click(button="left")
|
|
44
|
-
logger.info(f"添加乘客页面,【添加{
|
|
49
|
+
logger.info(f"添加乘客页面,【添加{passenger_dto.passenger_type}】按钮点击完成")
|
|
45
50
|
# 2. 获取 乘机人信息plane
|
|
46
51
|
passenger_info_plane = await page.get_add_passenger_plane(timeout=timeout, position_index=index)
|
|
47
52
|
logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---plane获取完成")
|
|
48
53
|
|
|
49
54
|
# 3. 选择旅客类型
|
|
50
55
|
adult_checkbox = await page.get_passenger_type_checkbox(
|
|
51
|
-
passenger_type=
|
|
56
|
+
passenger_type=passenger_dto.passenger_type, timeout=timeout, position_index=index
|
|
52
57
|
)
|
|
53
58
|
await adult_checkbox.click(button="left")
|
|
54
|
-
logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---旅客类型【{
|
|
59
|
+
logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---旅客类型【{passenger_dto.passenger_type}】单选框点击完成")
|
|
55
60
|
|
|
56
61
|
# 4. 输入姓名
|
|
57
|
-
p_name = passenger.get("p_name")
|
|
58
62
|
username_input = await page.get_passenger_username_input(locator=passenger_info_plane, timeout=timeout)
|
|
59
|
-
await username_input.fill(value=
|
|
60
|
-
logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---姓名<{
|
|
63
|
+
await username_input.fill(value=passenger_dto.passenger_name)
|
|
64
|
+
logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---姓名<{passenger_dto.passenger_name}>输入完成")
|
|
61
65
|
|
|
62
66
|
# 5. 选择性别
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
gender_checkbox = await page.get_gender_checkbox(
|
|
68
|
+
locator=passenger_info_plane, gender=passenger_dto.gender, timeout=timeout
|
|
69
|
+
)
|
|
65
70
|
await gender_checkbox.click(button="left")
|
|
66
|
-
logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---性别【{gender}】单选框点击完成")
|
|
71
|
+
logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---性别【{passenger_dto.gender}】单选框点击完成")
|
|
67
72
|
|
|
68
73
|
# 6. 选择证件类型 TODO 暂时只支持身份证类型,待后续完善
|
|
69
|
-
id_type
|
|
70
|
-
|
|
71
|
-
raise EnvironmentError(f"乘客证件类型<{id_type}>暂不支持")
|
|
74
|
+
if passenger_dto.id_type != "身份证":
|
|
75
|
+
raise EnvironmentError(f"乘客证件类型<{passenger_dto.id_type}>暂不支持")
|
|
72
76
|
|
|
73
77
|
# 7. 输入证件号码
|
|
74
|
-
id_no = passenger.get("id_no")
|
|
75
78
|
id_no_input = await page.get_id_no_input(locator=passenger_info_plane, timeout=timeout)
|
|
76
|
-
await id_no_input.fill(value=
|
|
77
|
-
logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---证件号码<{
|
|
79
|
+
await id_no_input.fill(value=passenger_dto.id_number)
|
|
80
|
+
logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---证件号码<{passenger_dto.id_number}>输入完成")
|
|
78
81
|
|
|
79
82
|
# 8. 输入联系人电话
|
|
80
83
|
service_mobile_input = await page.get_service_mobile_input(timeout=timeout)
|
|
81
|
-
await service_mobile_input.fill(value=
|
|
82
|
-
logger.info(f"添加乘客页面,联系人信息---手机号码<{
|
|
84
|
+
await service_mobile_input.fill(value=book_input_dto.specialist_mobile)
|
|
85
|
+
logger.info(f"添加乘客页面,联系人信息---手机号码<{book_input_dto.specialist_mobile}>输入完成")
|
|
83
86
|
|
|
84
87
|
# 9. 勾选同意单选框
|
|
85
88
|
agree_checkbox = await page.get_agree_checkbox(timeout=timeout)
|
|
@@ -89,4 +92,4 @@ async def add_passenger(
|
|
|
89
92
|
# 10. 点击【下一步】
|
|
90
93
|
next_btn = await page.get_next_btn(timeout=timeout)
|
|
91
94
|
await next_btn.click(button="left")
|
|
92
|
-
logger.info(f"添加乘客页面,【下一步】按钮点击完成,<{len(
|
|
95
|
+
logger.info(f"添加乘客页面,【下一步】按钮点击完成,<{len(passengers_dto)}>名乘客信息添加成功")
|
|
@@ -9,75 +9,107 @@
|
|
|
9
9
|
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
|
+
from logging import Logger
|
|
13
|
+
from aiohttp import CookieJar
|
|
12
14
|
from playwright.async_api import Page
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
+
from typing import Any, List, Callable, Optional, Dict
|
|
16
|
+
from flight_helper.models.dto.passenger import PassengerDTO
|
|
17
|
+
from flight_helper.models.dto.itinerary import QueryItineraryRequestDTO
|
|
18
|
+
from qdairlines_helper.controller.pay_success import two_check_pay_success
|
|
19
|
+
from qdairlines_helper.utils.exception_utils import PaymentChannelMissError
|
|
20
|
+
from flight_helper.models.dto.booking import BookingInputDTO, OneWayBookingDTO
|
|
15
21
|
from qdairlines_helper.controller.book_search import open_book_search_page, book_search
|
|
16
22
|
from qdairlines_helper.controller.order_verify import load_order_verify_po, order_verify
|
|
23
|
+
from flight_helper.models.dto.payment import HFPaidAccountPaymentInputDTO, PaymentResultDTO
|
|
17
24
|
from qdairlines_helper.controller.add_passenger import add_passenger, load_add_passenger_po
|
|
18
|
-
from qdairlines_helper.controller.cash_pax_info import load_cash_pax_info_po,
|
|
19
|
-
from qdairlines_helper.controller.nhlms_cashdesk import load_nhlms_cash_desk_po,
|
|
25
|
+
from qdairlines_helper.controller.cash_pax_info import load_cash_pax_info_po, select_payment_channel
|
|
26
|
+
from qdairlines_helper.controller.nhlms_cashdesk import load_nhlms_cash_desk_po, hf_paid_account_payment
|
|
20
27
|
|
|
21
28
|
|
|
22
29
|
async def book_payment_callback(
|
|
23
|
-
*, page: Page,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
) -> Dict[str, Any]:
|
|
30
|
+
*, page: Page, logger: Logger, is_pay_completed_callback: Callable, book_input_dto: BookingInputDTO,
|
|
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,
|
|
34
|
+
hf_paid_account_payment_dto: Optional[HFPaidAccountPaymentInputDTO] = None, **kwargs: Any
|
|
35
|
+
) -> PaymentResultDTO:
|
|
30
36
|
# 1. 打开预订搜索页面
|
|
31
|
-
book_search_page = await open_book_search_page(
|
|
37
|
+
book_search_page = await open_book_search_page(
|
|
38
|
+
page=page, logger=logger, protocol=book_input_dto.book_protocol, domain=book_input_dto.book_domain,
|
|
39
|
+
timeout=timeout
|
|
40
|
+
)
|
|
32
41
|
|
|
33
42
|
# 2. 预订搜索
|
|
34
43
|
await book_search(
|
|
35
|
-
book_search_page=book_search_page,
|
|
36
|
-
|
|
37
|
-
price_increase_threshold=price_increase_threshold, price_reduction_threshold=price_reduction_threshold,
|
|
44
|
+
book_search_page=book_search_page, one_way_booking_dto=one_way_booking_dto, book_input_dto=book_input_dto,
|
|
45
|
+
passengers=len(passengers_dto), logger=logger, timeout=timeout
|
|
38
46
|
)
|
|
39
|
-
logger.info(f"订单<{
|
|
47
|
+
logger.info(f"订单<{one_way_booking_dto.order_no}>,预订搜索结束")
|
|
40
48
|
|
|
41
49
|
# 3. 加载添加乘客页面对象
|
|
42
|
-
add_passenger_po = await load_add_passenger_po(
|
|
50
|
+
add_passenger_po = await load_add_passenger_po(
|
|
51
|
+
page=page, logger=logger, protocol=book_input_dto.book_protocol, domain=book_input_dto.book_domain,
|
|
52
|
+
timeout=timeout
|
|
53
|
+
)
|
|
43
54
|
|
|
44
55
|
# 4. 添加乘客
|
|
45
|
-
await add_passenger(
|
|
46
|
-
|
|
56
|
+
await add_passenger(
|
|
57
|
+
page=add_passenger_po, logger=logger, passengers_dto=passengers_dto, book_input_dto=book_input_dto,
|
|
58
|
+
timeout=timeout
|
|
59
|
+
)
|
|
60
|
+
logger.info(f"订单<{one_way_booking_dto.order_no}>,添加乘客结束")
|
|
47
61
|
|
|
48
62
|
# 5. 加载订单校验页面对象
|
|
49
|
-
order_verify_po = await load_order_verify_po(
|
|
63
|
+
order_verify_po = await load_order_verify_po(
|
|
64
|
+
page=page, logger=logger, protocol=book_input_dto.book_protocol, domain=book_input_dto.book_domain,
|
|
65
|
+
timeout=timeout
|
|
66
|
+
)
|
|
50
67
|
|
|
51
68
|
# 6. 校验订单
|
|
52
|
-
await order_verify(page=order_verify_po, refresh_attempt=refresh_attempt, timeout=timeout)
|
|
53
|
-
logger.info(f"订单<{
|
|
69
|
+
await order_verify(page=order_verify_po, logger=logger, refresh_attempt=refresh_attempt, timeout=timeout)
|
|
70
|
+
logger.info(f"订单<{one_way_booking_dto.order_no}>,校验订单结束")
|
|
54
71
|
|
|
55
72
|
# 7. 加载支付类型页面对象
|
|
56
|
-
cash_pax_info_po = await load_cash_pax_info_po(
|
|
73
|
+
cash_pax_info_po = await load_cash_pax_info_po(
|
|
74
|
+
page=page, logger=logger, protocol=book_input_dto.book_protocol, domain=book_input_dto.book_domain,
|
|
75
|
+
timeout=timeout
|
|
76
|
+
)
|
|
57
77
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
if isinstance(hf_paid_account_payment_dto, HFPaidAccountPaymentInputDTO):
|
|
79
|
+
# 8. 选择支付渠道
|
|
80
|
+
pre_order_no: str = await select_payment_channel(
|
|
81
|
+
page=cash_pax_info_po, logger=logger, channel_name=hf_paid_account_payment_dto.channel_name, timeout=timeout
|
|
82
|
+
)
|
|
83
|
+
logger.info(f"订单<{one_way_booking_dto.order_no}>,选择支付方式结束")
|
|
61
84
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
85
|
+
# 9. 根据支付方式,加载不同的收银台页面对象
|
|
86
|
+
nhlms_cash_desk_po = await load_nhlms_cash_desk_po(
|
|
87
|
+
context=kwargs.get("context"), timeout=timeout, logger=logger,
|
|
88
|
+
domain=hf_paid_account_payment_dto.pay_domain, protocol=hf_paid_account_payment_dto.pay_protocol
|
|
89
|
+
)
|
|
65
90
|
|
|
66
91
|
# 10. 汇付天下操作支付
|
|
67
|
-
if
|
|
92
|
+
if hf_paid_account_payment_dto.payment_type == "付款账户支付":
|
|
68
93
|
# 10. 汇付天下操作支付
|
|
69
|
-
|
|
70
|
-
page=nhlms_cash_desk_po,
|
|
71
|
-
|
|
94
|
+
payment_result_dto = await hf_paid_account_payment(
|
|
95
|
+
page=nhlms_cash_desk_po, logger=logger, order_no=pre_order_no, timeout=timeout,
|
|
96
|
+
is_pay_completed_callback=is_pay_completed_callback,
|
|
97
|
+
hf_paid_account_payment_dto=hf_paid_account_payment_dto
|
|
98
|
+
)
|
|
99
|
+
logger.info(f"订单<{one_way_booking_dto.order_no}>,汇付天下操作支付结束")
|
|
100
|
+
payment_result_dto.pre_order_no = pre_order_no
|
|
101
|
+
|
|
102
|
+
query_dto = QueryItineraryRequestDTO(
|
|
103
|
+
payment_domain=book_input_dto.book_domain, payment_protocol=book_input_dto.book_protocol,
|
|
104
|
+
storage_state=qdair_cookie.get("storage_state"), token=qdair_cookie.get("token"),
|
|
105
|
+
pre_order_no=pre_order_no, user_id=qdair_cookie.get("user_id"), headers=qdair_cookie.get("headers")
|
|
72
106
|
)
|
|
73
|
-
logger.info(f"订单<{order_id}>,汇付天下操作支付结束")
|
|
74
107
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
108
|
+
# 11. 检查是否支付成功
|
|
109
|
+
await two_check_pay_success(
|
|
110
|
+
page=page, logger=logger, timeout=timeout, query_dto=query_dto, retry=retry, enable_log=enable_log,
|
|
111
|
+
callback_get_proxy=callback_get_proxy, cookie_jar=cookie_jar
|
|
79
112
|
)
|
|
80
|
-
|
|
81
|
-
raise EnvironmentError(f"汇付天下暂不支持<{sub_payment_type}>操作支付方式")
|
|
113
|
+
return payment_result_dto
|
|
82
114
|
else:
|
|
83
|
-
raise
|
|
115
|
+
raise PaymentChannelMissError()
|
|
@@ -10,17 +10,23 @@
|
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
12
|
import asyncio
|
|
13
|
-
from
|
|
13
|
+
from logging import Logger
|
|
14
|
+
from typing import Dict, Any, List
|
|
14
15
|
from playwright.async_api import Page, Locator
|
|
15
|
-
from qdairlines_helper.utils.log_utils import logger
|
|
16
16
|
import qdairlines_helper.config.url_const as url_const
|
|
17
|
+
from qdairlines_helper.utils.exception_utils import IPBlockError
|
|
17
18
|
from qdairlines_helper.po.book_search_page import BookSearchPage
|
|
18
19
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
20
|
+
from flight_helper.models.dto.booking import OneWayBookingDTO, BookingInputDTO
|
|
21
|
+
from playwright.async_api import Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError
|
|
22
|
+
from qdairlines_helper.utils.exception_utils import NotEnoughTicketsError, ExcessiveProfitdError, ExcessiveLossesError
|
|
19
23
|
|
|
20
24
|
|
|
21
|
-
async def open_book_search_page(
|
|
25
|
+
async def open_book_search_page(
|
|
26
|
+
*, page: Page, logger: Logger, protocol: str, domain: str, timeout: float = 60.0
|
|
27
|
+
) -> BookSearchPage:
|
|
22
28
|
url_prefix = f"{protocol}://{domain}"
|
|
23
|
-
book_search_url = url_prefix + url_const.
|
|
29
|
+
book_search_url = url_prefix + url_const.book_search_url
|
|
24
30
|
await page.goto(book_search_url)
|
|
25
31
|
|
|
26
32
|
book_search_po = BookSearchPage(page=page, url=book_search_url)
|
|
@@ -28,87 +34,134 @@ async def open_book_search_page(*, page: Page, protocol: str, domain: str, timeo
|
|
|
28
34
|
logger.info(f"即将进入青岛航空官网航班预订查询页面,页面URL<{book_search_url}>")
|
|
29
35
|
ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
|
|
30
36
|
if ip_access_blocked_msg:
|
|
31
|
-
raise
|
|
37
|
+
raise IPBlockError(ip_access_blocked_msg)
|
|
32
38
|
return book_search_po
|
|
33
39
|
|
|
34
40
|
|
|
41
|
+
async def _book_search_dialog_handle(
|
|
42
|
+
*, logger: Logger, page: BookSearchPage, flight_query_btn: Locator, timeout: float = 60.0
|
|
43
|
+
):
|
|
44
|
+
try:
|
|
45
|
+
for _ in range(3):
|
|
46
|
+
# 1. 获取页面的空数据状态, 看看当前默认状态是不是空数据页
|
|
47
|
+
await page.get_empyt_data_page(timeout=timeout)
|
|
48
|
+
logger.warning(f"航班预订查询页面,【查询机票】按钮点击完成后,没有加出来数据,尝试再次点击机票查询")
|
|
49
|
+
# 2. 页面数据没有加载出来,尝试点击 查询按钮,重新再来
|
|
50
|
+
await flight_query_btn.click(button="left")
|
|
51
|
+
await asyncio.sleep(3)
|
|
52
|
+
logger.warning(f"航班预订查询页面,【查询机票】按钮再次点击完成")
|
|
53
|
+
except (PlaywrightError, PlaywrightTimeoutError, RuntimeError, EnvironmentError, Exception,):
|
|
54
|
+
try:
|
|
55
|
+
# 2. 点击继续购票按钮
|
|
56
|
+
continue_book_btn = await page.get_reminder_dialog_continue_book_btn(timeout=timeout)
|
|
57
|
+
await continue_book_btn.click(button="left")
|
|
58
|
+
logger.info("航班预订查询页面,出现温馨提醒弹框,【继续购票】按钮点击完成")
|
|
59
|
+
except (PlaywrightTimeoutError, RuntimeError, EnvironmentError, Exception,):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
35
63
|
async def book_search(
|
|
36
|
-
*, book_search_page: BookSearchPage,
|
|
37
|
-
|
|
38
|
-
price_increase_threshold: float = 20.0, price_reduction_threshold: float = 10.0
|
|
64
|
+
*, book_search_page: BookSearchPage, logger: Logger, passengers: int, book_input_dto: BookingInputDTO,
|
|
65
|
+
one_way_booking_dto: OneWayBookingDTO, timeout: float = 60.0
|
|
39
66
|
) -> None:
|
|
40
|
-
|
|
41
|
-
# 1. 判断是否存在温馨提醒弹框
|
|
42
|
-
continue_book_btn = await book_search_page.get_reminder_dialog_continue_book_btn(timeout=timeout)
|
|
43
|
-
await continue_book_btn.click(button="left")
|
|
44
|
-
logger.info("航班预订查询页面,出现温馨提醒弹框,【继续购票】按钮点击完成")
|
|
45
|
-
except (Exception,):
|
|
46
|
-
pass
|
|
47
|
-
|
|
48
|
-
# 2.搜索栏输入起飞城市
|
|
67
|
+
# 1.搜索栏输入起飞城市
|
|
49
68
|
depart_city_input = await book_search_page.get_depart_city_input(timeout=timeout)
|
|
50
|
-
await depart_city_input.fill(value=
|
|
69
|
+
await depart_city_input.fill(value=one_way_booking_dto.dep_code)
|
|
51
70
|
await asyncio.sleep(delay=1)
|
|
52
71
|
await depart_city_input.press("Enter")
|
|
53
|
-
logger.info(
|
|
72
|
+
logger.info(
|
|
73
|
+
f"航班预订查询页面,搜索栏-起飞城市<{one_way_booking_dto.dep_city} {one_way_booking_dto.dep_code}>输入完成")
|
|
54
74
|
|
|
55
|
-
#
|
|
75
|
+
# 2.搜索栏输入抵达城市
|
|
56
76
|
arrive_city_input = await book_search_page.get_arrive_city_input(timeout=timeout)
|
|
57
|
-
await arrive_city_input.fill(value=
|
|
77
|
+
await arrive_city_input.fill(value=one_way_booking_dto.arr_code)
|
|
58
78
|
await asyncio.sleep(delay=1)
|
|
59
79
|
await arrive_city_input.press("Enter")
|
|
60
|
-
logger.info(
|
|
80
|
+
logger.info(
|
|
81
|
+
f"航班预订查询页面,搜索栏-抵达城市<{one_way_booking_dto.arr_city} {one_way_booking_dto.arr_code}>输入完成")
|
|
61
82
|
|
|
62
|
-
#
|
|
83
|
+
# 3.搜索栏输入起飞时间
|
|
63
84
|
depart_date_input = await book_search_page.get_depart_date_input(timeout=timeout)
|
|
64
|
-
await depart_date_input.fill(value=dep_date)
|
|
85
|
+
await depart_date_input.fill(value=one_way_booking_dto.dep_date)
|
|
65
86
|
await asyncio.sleep(delay=1)
|
|
66
87
|
await depart_date_input.press("Enter")
|
|
67
|
-
logger.info(f"航班预订查询页面,搜索栏-起飞日期<{dep_date}>输入完成")
|
|
88
|
+
logger.info(f"航班预订查询页面,搜索栏-起飞日期<{one_way_booking_dto.dep_date}>输入完成")
|
|
68
89
|
|
|
69
|
-
#
|
|
90
|
+
# 4.点击【查询机票】按钮
|
|
70
91
|
flight_query_btn = await book_search_page.get_flight_query_btn(timeout=timeout)
|
|
71
92
|
await flight_query_btn.click(button="left")
|
|
72
93
|
logger.info(f"航班预订查询页面,【查询机票】按钮点击完成")
|
|
73
94
|
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
# 5.点击查询后,再次处理是否有弹框
|
|
96
|
+
await _book_search_dialog_handle(
|
|
97
|
+
logger=logger, page=book_search_page, flight_query_btn=flight_query_btn, timeout=timeout
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# 6.获取航班基本信息plane locator
|
|
101
|
+
flight_info_plane: Locator = await book_search_page.get_flight_info_plane(
|
|
102
|
+
flight_no=one_way_booking_dto.flight_no, timeout=timeout
|
|
103
|
+
)
|
|
79
104
|
|
|
80
|
-
#
|
|
105
|
+
# 8. 获取产品类型
|
|
81
106
|
flight_product_nav: Locator = await book_search_page.get_flight_product_nav(
|
|
82
|
-
locator=flight_info_plane, product_type=product_type, timeout=timeout
|
|
107
|
+
locator=flight_info_plane, product_type=book_input_dto.product_type, timeout=timeout
|
|
83
108
|
)
|
|
84
109
|
await flight_product_nav.click(button="left")
|
|
85
|
-
logger.info(f"航班预订查询页面,产品类型【{product_type}】选择完成")
|
|
110
|
+
logger.info(f"航班预订查询页面,产品类型【{book_input_dto.product_type}】选择完成")
|
|
111
|
+
|
|
112
|
+
# 9. 点击获取更多产品和价格的按钮
|
|
113
|
+
more_product_btn = await book_search_page.get_more_product_btn(locator=flight_info_plane, timeout=timeout)
|
|
114
|
+
await more_product_btn.click(button="left")
|
|
115
|
+
logger.info(f"航班预订查询页面,航班<{one_way_booking_dto.flight_no}>产品列表,【更多舱位及价格】按钮点击完成")
|
|
86
116
|
|
|
87
|
-
#
|
|
88
|
-
products: Dict[str, Any] = await book_search_page.get_flight_products(
|
|
89
|
-
|
|
90
|
-
|
|
117
|
+
# 9. 获取所有的产品
|
|
118
|
+
products: List[Dict[str, Any]] = await book_search_page.get_flight_products(
|
|
119
|
+
locator=flight_info_plane, flight_no=one_way_booking_dto.flight_no, logger=logger
|
|
120
|
+
)
|
|
121
|
+
cabin_product: Dict[str, Any] = products.get(one_way_booking_dto.cabin)
|
|
122
|
+
# 9.1 判断是否存在该舱位
|
|
91
123
|
if not cabin_product:
|
|
92
|
-
raise RuntimeError(
|
|
93
|
-
|
|
124
|
+
raise RuntimeError(
|
|
125
|
+
f"航班预订查询页面,没有搜索到航班<{one_way_booking_dto.flight_no}>的{one_way_booking_dto.cabin}舱数据")
|
|
126
|
+
# 9.2 判断余座
|
|
94
127
|
seats_status = cabin_product.get("seats_status")
|
|
95
|
-
logger.info(
|
|
128
|
+
logger.info(
|
|
129
|
+
f"航班预订查询页面,航班<{one_way_booking_dto.flight_no}>舱位<{one_way_booking_dto.cabin}>的座位情况:{seats_status}")
|
|
96
130
|
if seats_status < passengers:
|
|
97
|
-
raise
|
|
98
|
-
|
|
131
|
+
raise NotEnoughTicketsError(
|
|
132
|
+
flight_no=one_way_booking_dto.flight_no, seats_status=seats_status, passengers=passengers
|
|
133
|
+
)
|
|
134
|
+
# 9.3. 判断货币符号,是否为 ¥(人民币结算)
|
|
99
135
|
# 目前都为国内航班,暂不考虑货币种类
|
|
100
136
|
|
|
101
|
-
#
|
|
137
|
+
# 9.4 判断销售价格是否满足预订需要
|
|
102
138
|
amounts: Dict[str, Any] = cabin_product.get("amounts")
|
|
103
139
|
amount: float = amounts.get("amount")
|
|
104
|
-
if amount >
|
|
105
|
-
raise
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
140
|
+
if amount > one_way_booking_dto.standard_price + book_input_dto.standard_increase_threshold:
|
|
141
|
+
raise ExcessiveLossesError(
|
|
142
|
+
flight_no=one_way_booking_dto.flight_no, query_price=amount, order_price=one_way_booking_dto.standard_price,
|
|
143
|
+
increase_threshold=book_input_dto.standard_increase_threshold, asset="票面价"
|
|
144
|
+
)
|
|
145
|
+
if amount < one_way_booking_dto.standard_price - book_input_dto.standard_reduction_threshold:
|
|
146
|
+
raise ExcessiveProfitdError(
|
|
147
|
+
flight_no=one_way_booking_dto.flight_no, query_price=amount, order_price=one_way_booking_dto.standard_price,
|
|
148
|
+
reduction_threshold=book_input_dto.standard_reduction_threshold, asset="票面价"
|
|
149
|
+
)
|
|
150
|
+
if book_input_dto.sale_increase_threshold > 0:
|
|
151
|
+
if amount > one_way_booking_dto.sale_price + book_input_dto.sale_increase_threshold:
|
|
152
|
+
raise ExcessiveLossesError(
|
|
153
|
+
flight_no=one_way_booking_dto.flight_no, query_price=amount, order_price=one_way_booking_dto.sale_price,
|
|
154
|
+
increase_threshold=book_input_dto.sale_increase_threshold, asset="销售价"
|
|
155
|
+
)
|
|
156
|
+
if book_input_dto.sale_increase_threshold > 0:
|
|
157
|
+
if amount < one_way_booking_dto.sale_price - book_input_dto.sale_reduction_threshold:
|
|
158
|
+
raise ExcessiveProfitdError(
|
|
159
|
+
flight_no=one_way_booking_dto.flight_no, query_price=amount,
|
|
160
|
+
order_price=one_way_booking_dto.sale_price,
|
|
161
|
+
reduction_threshold=book_input_dto.sale_reduction_threshold, asset="销售价"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# 10. 点击【购票】按钮
|
|
112
165
|
booking_btn: Locator = cabin_product.get("booking_btn")
|
|
113
166
|
await booking_btn.click(button="left")
|
|
114
167
|
logger.info(f"航班预订查询页面,【购票】按钮点击完成")
|
|
@@ -9,15 +9,16 @@
|
|
|
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.cash_pax_info_page import CashPaxInfoPage
|
|
16
17
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
async def load_cash_pax_info_po(
|
|
20
|
-
*, page: Page, protocol: str, domain: str, timeout: float = 60.0
|
|
21
|
+
*, page: Page, logger: Logger, protocol: str, domain: str, timeout: float = 60.0
|
|
21
22
|
) -> CashPaxInfoPage:
|
|
22
23
|
url_prefix = f"{protocol}://{domain}"
|
|
23
24
|
cash_pax_info_url = url_prefix + url_const.cash_pax_info_url
|
|
@@ -26,19 +27,21 @@ async def load_cash_pax_info_po(
|
|
|
26
27
|
logger.info(f"即将进入青岛航空官网支付方式页面,页面URL<{cash_pax_info_url}>")
|
|
27
28
|
ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
|
|
28
29
|
if ip_access_blocked_msg:
|
|
29
|
-
raise
|
|
30
|
+
raise IPBlockError(ip_access_blocked_msg)
|
|
30
31
|
return cash_pax_info_po
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
async def
|
|
34
|
+
async def select_payment_channel(
|
|
35
|
+
*, page: CashPaxInfoPage, logger: Logger, channel_name: str = "汇付天下", timeout: float = 60.0
|
|
36
|
+
) -> str:
|
|
34
37
|
# 1. 获取青岛航空官网订单号
|
|
35
38
|
pre_order_no = await page.get_pre_order_no(timeout=timeout)
|
|
36
39
|
logger.info(f"选择支付方式页面,青岛航空官网订单号<{pre_order_no}>获取完成")
|
|
37
40
|
|
|
38
|
-
# 2.
|
|
39
|
-
|
|
40
|
-
await
|
|
41
|
-
logger.info(f"
|
|
41
|
+
# 2. 选择支付渠道【channel_name】
|
|
42
|
+
payment_channel_checkbox = await page.get_payment_channel_checkbox(channel_name=channel_name, timeout=timeout)
|
|
43
|
+
await payment_channel_checkbox.click(button="left")
|
|
44
|
+
logger.info(f"选择支付方式页面,支付渠道【{channel_name}】单选框点击完成")
|
|
42
45
|
|
|
43
46
|
# 3. 点击【确认支付】
|
|
44
47
|
confirm_payment_btn = await page.get_confirm_payment_btn(timeout=timeout)
|
|
@@ -9,14 +9,15 @@
|
|
|
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
14
|
from qdairlines_helper.po.home_page import HomePage
|
|
14
|
-
from qdairlines_helper.utils.log_utils import logger
|
|
15
15
|
import qdairlines_helper.config.url_const as url_const
|
|
16
|
+
from qdairlines_helper.utils.exception_utils import IPBlockError
|
|
16
17
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
async def load_home_po(*, page: Page, protocol: str, domain: str, timeout: float = 60.0) -> HomePage:
|
|
20
|
+
async def load_home_po(*, page: Page, logger: Logger, protocol: str, domain: str, timeout: float = 60.0) -> HomePage:
|
|
20
21
|
url_prefix = f"{protocol}://{domain}"
|
|
21
22
|
home_url = url_prefix + url_const.home_url
|
|
22
23
|
home_po = HomePage(page=page, url=home_url)
|
|
@@ -24,5 +25,5 @@ async def load_home_po(*, page: Page, protocol: str, domain: str, timeout: float
|
|
|
24
25
|
logger.info(f"即将进入青岛航空官网首页,页面URL<{home_url}>")
|
|
25
26
|
ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
|
|
26
27
|
if ip_access_blocked_msg:
|
|
27
|
-
raise
|
|
28
|
+
raise IPBlockError(ip_access_blocked_msg)
|
|
28
29
|
return home_po
|