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.
Files changed (26) hide show
  1. {python_qdairlines_helper-0.0.6.dist-info → python_qdairlines_helper-0.1.4.dist-info}/METADATA +2 -1
  2. python_qdairlines_helper-0.1.4.dist-info/RECORD +35 -0
  3. qdairlines_helper/config/url_const.py +2 -1
  4. qdairlines_helper/controller/add_passenger.py +29 -26
  5. qdairlines_helper/controller/book_payment.py +74 -41
  6. qdairlines_helper/controller/book_search.py +51 -26
  7. qdairlines_helper/controller/cash_pax_info.py +11 -8
  8. qdairlines_helper/controller/home.py +4 -3
  9. qdairlines_helper/controller/nhlms_cashdesk.py +32 -18
  10. qdairlines_helper/controller/order_detail.py +43 -34
  11. qdairlines_helper/controller/order_query.py +9 -6
  12. qdairlines_helper/controller/order_verify.py +9 -4
  13. qdairlines_helper/controller/pay_success.py +66 -0
  14. qdairlines_helper/controller/user_login.py +14 -9
  15. qdairlines_helper/po/add_passenger_page.py +3 -2
  16. qdairlines_helper/po/book_search_page.py +2 -3
  17. qdairlines_helper/po/cash_pax_info_page.py +10 -10
  18. qdairlines_helper/po/nhlms_cash_desk_page.py +2 -1
  19. qdairlines_helper/po/order_verify_page.py +0 -2
  20. qdairlines_helper/po/pay_success_page.py +33 -0
  21. qdairlines_helper/utils/exception_utils.py +91 -3
  22. python_qdairlines_helper-0.0.6.dist-info/RECORD +0 -34
  23. qdairlines_helper/utils/log_utils.py +0 -14
  24. {python_qdairlines_helper-0.0.6.dist-info → python_qdairlines_helper-0.1.4.dist-info}/WHEEL +0 -0
  25. {python_qdairlines_helper-0.0.6.dist-info → python_qdairlines_helper-0.1.4.dist-info}/licenses/LICENSE +0 -0
  26. {python_qdairlines_helper-0.0.6.dist-info → python_qdairlines_helper-0.1.4.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_qdairlines_helper
3
- Version: 0.0.6
3
+ Version: 0.1.4
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.1.4.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=ygIHu6LGnLLihDM4PrpNDr3ZkTEZiQb-taqk4DuvANk,6104
8
+ qdairlines_helper/controller/book_search.py,sha256=Q1KZKYeRmJh26nK1G9d8sWJF7_kCUS-9mJjTyoWCXpE,7598
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=6h6TyS_-yU2AHdFxbQS16TyjKWGWeCu1uVlsz9wANDE,3172
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=UiIn6rMpD4BIsBbbiZH4xuWJd5eeuHp0sb-qmo3MJGg,3153
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=cHcPhHPPE0xuGfi6fnue-lH33COWU_nyLH2tVUQGI7U,9782
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.1.4.dist-info/METADATA,sha256=SIlFOHl1g6nCQGSubFYF6rnaXyF1fq-zg8KmLY0RMvA,14424
33
+ python_qdairlines_helper-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ python_qdairlines_helper-0.1.4.dist-info/top_level.txt,sha256=MRbQkBMdSG4f5mx2RWfu6aXoDSyM-2KwL-YFqNBdbng,18
35
+ python_qdairlines_helper-0.1.4.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, Dict, Any
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 EnvironmentError(ip_access_blocked_msg)
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, passengers: List[Dict[str, Any]], service_mobile: str, timeout: float = 60.0
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, passenger in enumerate(passengers):
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(passenger_type=p_type, timeout=timeout)
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"添加乘客页面,【添加{p_type}】按钮点击完成")
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=p_type, timeout=timeout, position_index=index
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}>乘机人信息---旅客类型【{p_type}】单选框点击完成")
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=p_name)
60
- logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---姓名<{p_name}>输入完成")
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
- gender = passenger.get("gender")
64
- gender_checkbox = await page.get_gender_checkbox(locator=passenger_info_plane, gender=gender, timeout=timeout)
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 = passenger.get("id_type")
70
- if id_type != "身份证":
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=id_no)
77
- logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---证件号码<{id_no}>输入完成")
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=service_mobile)
82
- logger.info(f"添加乘客页面,联系人信息---手机号码<{service_mobile}>输入完成")
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(passengers)}>名乘客信息添加成功")
95
+ logger.info(f"添加乘客页面,【下一步】按钮点击完成,<{len(passengers_dto)}>名乘客信息添加成功")
@@ -9,75 +9,108 @@
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 qdairlines_helper.utils.log_utils import logger
14
- from typing import Dict, Any, List, Optional, Callable
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, select_payment_type
19
- from qdairlines_helper.controller.nhlms_cashdesk import load_nhlms_cash_desk_po, pay_account_payment
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, protocol: str, domain: str, dep_city: str, arr_city: str, dep_date: str, flight_no: str,
24
- cabin: str, login_user: str, login_password: str, passengers: List[Dict[str, Any]], service_mobile: str,
25
- price_std: float, order_id: int, is_pay_completed_callback: Callable, product_type: str = "经济舱",
26
- payment_type: str = "汇付天下", sub_payment_type: str = "付款账户支付", operator_account: Optional[str] = None,
27
- timeout: float = 60.0, pay_password: Optional[str] = None, refresh_attempt: int = 3,
28
- price_increase_threshold: float = 20.0, price_reduction_threshold: float = 10.0, **kwargs: Any
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(page=page, protocol=protocol, domain=domain, timeout=timeout)
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, dep_city=dep_city, arr_city=arr_city, dep_date=dep_date, flight_no=flight_no,
36
- cabin=cabin, passengers=len(passengers), product_type=product_type, price_std=price_std, timeout=timeout,
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"订单<{order_id}>,预订搜索结束")
47
+ logger.info(f"订单<{one_way_booking_dto.order_no}>,预订搜索结束")
40
48
 
41
49
  # 3. 加载添加乘客页面对象
42
- add_passenger_po = await load_add_passenger_po(page=page, protocol=protocol, domain=domain, timeout=timeout)
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(page=add_passenger_po, passengers=passengers, service_mobile=service_mobile, timeout=timeout)
46
- logger.info(f"订单<{order_id}>,添加乘客结束")
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(page=page, protocol=protocol, domain=domain, timeout=timeout)
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"订单<{order_id}>,校验订单结束")
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(page=page, protocol=protocol, domain=domain, timeout=timeout)
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
- # 8. 选择支付方式
59
- pre_order_no: str = await select_payment_type(page=cash_pax_info_po, payment_type=payment_type, timeout=timeout)
60
- logger.info(f"订单<{order_id}>,选择支付方式结束")
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
- # 9. 根据支付方式,加载不同的收银台页面对象
63
- if payment_type == "汇付天下":
64
- nhlms_cash_desk_po = await load_nhlms_cash_desk_po(context=kwargs.get("context"), timeout=timeout)
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 sub_payment_type == "付款账户支付":
92
+ if hf_paid_account_payment_dto.payment_type == "付款账户支付":
68
93
  # 10. 汇付天下操作支付
69
- pay_transaction, actual_payment_amount = await pay_account_payment(
70
- page=nhlms_cash_desk_po, operator_account=operator_account, pay_password=pay_password, timeout=timeout,
71
- payment_type=sub_payment_type, is_pay_completed_callback=is_pay_completed_callback, order_id=order_id
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
- return dict(
76
- actual_payment_amount=actual_payment_amount, pre_order_no=pre_order_no, passengers=passengers,
77
- channel_user_id=login_user, channel_user_password=login_password, order_id=order_id,
78
- pay_transaction=pay_transaction
108
+ # 11. 检查是否支付成功
109
+ is_success = 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
- else:
81
- raise EnvironmentError(f"汇付天下暂不支持<{sub_payment_type}>操作支付方式")
113
+ if is_success:
114
+ return payment_result_dto
82
115
  else:
83
- raise EnvironmentError(f"暂不支持<{payment_type}>支付方式")
116
+ raise PaymentChannelMissError()
@@ -10,15 +10,20 @@
10
10
  # ---------------------------------------------------------------------------------------------------------
11
11
  """
12
12
  import asyncio
13
+ from logging import Logger
13
14
  from typing import Dict, Any
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 qdairlines_helper.utils.exception_utils import NotEnoughTicketsError, ExcessiveProfitdError, ExcessiveLossesError
19
22
 
20
23
 
21
- async def open_book_search_page(*, page: Page, protocol: str, domain: str, timeout: float = 60.0) -> BookSearchPage:
24
+ async def open_book_search_page(
25
+ *, page: Page, logger: Logger, protocol: str, domain: str, timeout: float = 60.0
26
+ ) -> BookSearchPage:
22
27
  url_prefix = f"{protocol}://{domain}"
23
28
  book_search_url = url_prefix + url_const.login_url
24
29
  await page.goto(book_search_url)
@@ -28,14 +33,13 @@ async def open_book_search_page(*, page: Page, protocol: str, domain: str, timeo
28
33
  logger.info(f"即将进入青岛航空官网航班预订查询页面,页面URL<{book_search_url}>")
29
34
  ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
30
35
  if ip_access_blocked_msg:
31
- raise EnvironmentError(ip_access_blocked_msg)
36
+ raise IPBlockError(ip_access_blocked_msg)
32
37
  return book_search_po
33
38
 
34
39
 
35
40
  async def book_search(
36
- *, book_search_page: BookSearchPage, dep_city: str, arr_city: str, dep_date: str, flight_no: str,
37
- cabin: str, passengers: int, product_type: str = "经济舱", price_std: float, timeout: float = 60.0,
38
- price_increase_threshold: float = 20.0, price_reduction_threshold: float = 10.0
41
+ *, book_search_page: BookSearchPage, logger: Logger, passengers: int, book_input_dto: BookingInputDTO,
42
+ one_way_booking_dto: OneWayBookingDTO, timeout: float = 60.0
39
43
  ) -> None:
40
44
  try:
41
45
  # 1. 判断是否存在温馨提醒弹框
@@ -47,24 +51,24 @@ async def book_search(
47
51
 
48
52
  # 2.搜索栏输入起飞城市
49
53
  depart_city_input = await book_search_page.get_depart_city_input(timeout=timeout)
50
- await depart_city_input.fill(value=dep_city)
54
+ await depart_city_input.fill(value=one_way_booking_dto.dep_city)
51
55
  await asyncio.sleep(delay=1)
52
56
  await depart_city_input.press("Enter")
53
- logger.info(f"航班预订查询页面,搜索栏-起飞城市<{dep_city}>输入完成")
57
+ logger.info(f"航班预订查询页面,搜索栏-起飞城市<{one_way_booking_dto.dep_city}>输入完成")
54
58
 
55
59
  # 3.搜索栏输入抵达城市
56
60
  arrive_city_input = await book_search_page.get_arrive_city_input(timeout=timeout)
57
- await arrive_city_input.fill(value=arr_city)
61
+ await arrive_city_input.fill(value=one_way_booking_dto.arr_city)
58
62
  await asyncio.sleep(delay=1)
59
63
  await arrive_city_input.press("Enter")
60
- logger.info(f"航班预订查询页面,搜索栏-抵达城市<{arr_city}>输入完成")
64
+ logger.info(f"航班预订查询页面,搜索栏-抵达城市<{one_way_booking_dto.arr_city}>输入完成")
61
65
 
62
66
  # 4.搜索栏输入起飞时间
63
67
  depart_date_input = await book_search_page.get_depart_date_input(timeout=timeout)
64
- await depart_date_input.fill(value=dep_date)
68
+ await depart_date_input.fill(value=one_way_booking_dto.dep_date)
65
69
  await asyncio.sleep(delay=1)
66
70
  await depart_date_input.press("Enter")
67
- logger.info(f"航班预订查询页面,搜索栏-起飞日期<{dep_date}>输入完成")
71
+ logger.info(f"航班预订查询页面,搜索栏-起飞日期<{one_way_booking_dto.dep_date}>输入完成")
68
72
 
69
73
  # 5.点击【查询机票】按钮
70
74
  flight_query_btn = await book_search_page.get_flight_query_btn(timeout=timeout)
@@ -74,39 +78,60 @@ async def book_search(
74
78
  # 6. 获取航班基本信息plane locator
75
79
  flight_info_plane: Locator = await book_search_page.get_flight_info_plane(timeout=timeout)
76
80
  page_flight_no: str = await book_search_page.get_flight_no(locator=flight_info_plane, timeout=timeout)
77
- if flight_no not in page_flight_no:
78
- raise RuntimeError(f"航班预订查询页面,没有搜索到航班<{flight_no}>数据")
81
+ if one_way_booking_dto.flight_no not in page_flight_no:
82
+ raise RuntimeError(f"航班预订查询页面,没有搜索到航班<{one_way_booking_dto.flight_no}>数据")
79
83
 
80
84
  # 7. 获取产品类型
81
85
  flight_product_nav: Locator = await book_search_page.get_flight_product_nav(
82
- locator=flight_info_plane, product_type=product_type, timeout=timeout
86
+ locator=flight_info_plane, product_type=book_input_dto.product_type, timeout=timeout
83
87
  )
84
88
  await flight_product_nav.click(button="left")
85
- logger.info(f"航班预订查询页面,产品类型【{product_type}】选择完成")
89
+ logger.info(f"航班预订查询页面,产品类型【{book_input_dto.product_type}】选择完成")
86
90
 
87
91
  # 8. 获取所有的产品
88
92
  products: Dict[str, Any] = await book_search_page.get_flight_products(timeout=timeout)
89
- cabin_product: Dict[str, Any] = products.get(cabin)
93
+ cabin_product: Dict[str, Any] = products.get(one_way_booking_dto.cabin)
90
94
  # 8.1 判断是否存在该舱位
91
95
  if not cabin_product:
92
- raise RuntimeError(f"航班预订查询页面,没有搜索到航班<{flight_no}>的{cabin}舱数据")
96
+ raise RuntimeError(
97
+ f"航班预订查询页面,没有搜索到航班<{one_way_booking_dto.flight_no}>的{one_way_booking_dto.cabin}舱数据")
93
98
  # 8.2 判断余座
94
99
  seats_status = cabin_product.get("seats_status")
95
- logger.info(f"航班预订查询页面,航班<{flight_no}>舱位<{cabin}>的座位情况:{seats_status}")
100
+ logger.info(
101
+ f"航班预订查询页面,航班<{one_way_booking_dto.flight_no}>舱位<{one_way_booking_dto.cabin}>的座位情况:{seats_status}")
96
102
  if seats_status < passengers:
97
- raise EnvironmentError(f"航班<{flight_no}>的余票: {seats_status},数量不足")
103
+ raise NotEnoughTicketsError(
104
+ flight_no=one_way_booking_dto.flight_no, seats_status=seats_status, passengers=passengers
105
+ )
98
106
  # 8.3. 判断货币符号,是否为 ¥(人民币结算)
99
107
  # 目前都为国内航班,暂不考虑货币种类
100
108
 
101
109
  # 8.4 判断销售价格是否满足预订需要
102
110
  amounts: Dict[str, Any] = cabin_product.get("amounts")
103
111
  amount: float = amounts.get("amount")
104
- if amount > price_std + price_increase_threshold:
105
- raise EnvironmentError(
106
- f"航班预订查询页面,航班<{flight_no}>官网价:{amount} 高于:订单销售价[{price_std}] + 上浮阈值[{price_increase_threshold}],损失过高")
107
- if amount < price_std - price_reduction_threshold:
108
- raise EnvironmentError(
109
- f"航班预订查询页面,航班<{flight_no}>官网价:{amount} 低于:订单销售价[{price_std}] - 下降阈值[{price_reduction_threshold}],收益过高")
112
+ if amount > one_way_booking_dto.standard_price + book_input_dto.standard_increase_threshold:
113
+ raise ExcessiveLossesError(
114
+ flight_no=one_way_booking_dto.flight_no, query_price=amount, order_price=one_way_booking_dto.standard_price,
115
+ increase_threshold=book_input_dto.standard_increase_threshold, asset="票面价"
116
+ )
117
+ if amount < one_way_booking_dto.standard_price - book_input_dto.standard_reduction_threshold:
118
+ raise ExcessiveProfitdError(
119
+ flight_no=one_way_booking_dto.flight_no, query_price=amount, order_price=one_way_booking_dto.standard_price,
120
+ reduction_threshold=book_input_dto.standard_reduction_threshold, asset="票面价"
121
+ )
122
+ if book_input_dto.sale_increase_threshold > 0:
123
+ if amount > one_way_booking_dto.sale_price + book_input_dto.sale_increase_threshold:
124
+ raise ExcessiveLossesError(
125
+ flight_no=one_way_booking_dto.flight_no, query_price=amount, order_price=one_way_booking_dto.sale_price,
126
+ increase_threshold=book_input_dto.sale_increase_threshold, asset="销售价"
127
+ )
128
+ if book_input_dto.sale_increase_threshold > 0:
129
+ if amount < one_way_booking_dto.sale_price - book_input_dto.sale_reduction_threshold:
130
+ raise ExcessiveProfitdError(
131
+ flight_no=one_way_booking_dto.flight_no, query_price=amount,
132
+ order_price=one_way_booking_dto.sale_price,
133
+ reduction_threshold=book_input_dto.sale_reduction_threshold, asset="销售价"
134
+ )
110
135
 
111
136
  # 9. 点击【购票】按钮
112
137
  booking_btn: Locator = cabin_product.get("booking_btn")
@@ -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 EnvironmentError(ip_access_blocked_msg)
30
+ raise IPBlockError(ip_access_blocked_msg)
30
31
  return cash_pax_info_po
31
32
 
32
33
 
33
- async def select_payment_type(*, page: CashPaxInfoPage, payment_type: str = "汇付天下", timeout: float = 60.0) -> str:
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. 选择支付类型【payment_type
39
- payment_type_checkbox = await page.get_payment_type_checkbox(payment_type=payment_type, timeout=timeout)
40
- await payment_type_checkbox.click(button="left")
41
- logger.info(f"选择支付方式页面,第三方支付【{payment_type}】单选框点击完成")
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 EnvironmentError(ip_access_blocked_msg)
28
+ raise IPBlockError(ip_access_blocked_msg)
28
29
  return home_po