python-qdairlines-helper 0.0.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.
Files changed (34) hide show
  1. python_qdairlines_helper-0.0.6.dist-info/METADATA +247 -0
  2. python_qdairlines_helper-0.0.6.dist-info/RECORD +34 -0
  3. python_qdairlines_helper-0.0.6.dist-info/WHEEL +5 -0
  4. python_qdairlines_helper-0.0.6.dist-info/licenses/LICENSE +201 -0
  5. python_qdairlines_helper-0.0.6.dist-info/top_level.txt +1 -0
  6. qdairlines_helper/__init__.py +11 -0
  7. qdairlines_helper/config/__init__.py +11 -0
  8. qdairlines_helper/config/url_const.py +37 -0
  9. qdairlines_helper/controller/__init__.py +11 -0
  10. qdairlines_helper/controller/add_passenger.py +92 -0
  11. qdairlines_helper/controller/book_payment.py +83 -0
  12. qdairlines_helper/controller/book_search.py +114 -0
  13. qdairlines_helper/controller/cash_pax_info.py +47 -0
  14. qdairlines_helper/controller/home.py +28 -0
  15. qdairlines_helper/controller/nhlms_cashdesk.py +74 -0
  16. qdairlines_helper/controller/order_detail.py +53 -0
  17. qdairlines_helper/controller/order_query.py +41 -0
  18. qdairlines_helper/controller/order_verify.py +43 -0
  19. qdairlines_helper/controller/user_login.py +91 -0
  20. qdairlines_helper/http/__init__.py +11 -0
  21. qdairlines_helper/http/flight_order.py +86 -0
  22. qdairlines_helper/po/__init__.py +11 -0
  23. qdairlines_helper/po/add_passenger_page.py +155 -0
  24. qdairlines_helper/po/air_order_page.py +24 -0
  25. qdairlines_helper/po/book_search_page.py +189 -0
  26. qdairlines_helper/po/cash_pax_info_page.py +74 -0
  27. qdairlines_helper/po/home_page.py +24 -0
  28. qdairlines_helper/po/login_page.py +70 -0
  29. qdairlines_helper/po/nhlms_cash_desk_page.py +94 -0
  30. qdairlines_helper/po/order_verify_page.py +62 -0
  31. qdairlines_helper/utils/__init__.py +11 -0
  32. qdairlines_helper/utils/exception_utils.py +17 -0
  33. qdairlines_helper/utils/log_utils.py +14 -0
  34. qdairlines_helper/utils/po_utils.py +50 -0
@@ -0,0 +1,92 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ # ---------------------------------------------------------------------------------------------------------
4
+ # ProjectName: python-qdairlines-helper
5
+ # FileName: add_passenger.py
6
+ # Description: 添加乘客控制器
7
+ # Author: ASUS
8
+ # CreateDate: 2026/01/04
9
+ # Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
10
+ # ---------------------------------------------------------------------------------------------------------
11
+ """
12
+ from typing import List, Dict, Any
13
+ from playwright.async_api import Page
14
+ from qdairlines_helper.utils.log_utils import logger
15
+ import qdairlines_helper.config.url_const as url_const
16
+ from qdairlines_helper.po.add_passenger_page import AddPassengerPage
17
+ from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
18
+
19
+
20
+ async def load_add_passenger_po(
21
+ *, page: Page, protocol: str, domain: str, timeout: float = 60.0
22
+ ) -> AddPassengerPage:
23
+ url_prefix = f"{protocol}://{domain}"
24
+ add_passenger_url = url_prefix + url_const.add_passenger_url
25
+ add_passenger_po = AddPassengerPage(page=page, url=add_passenger_url)
26
+ await add_passenger_po.url_wait_for(url=add_passenger_url, timeout=timeout)
27
+ logger.info(f"即将进入青岛航空官网添加乘客页面,页面URL<{add_passenger_url}>")
28
+ ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
29
+ if ip_access_blocked_msg:
30
+ raise EnvironmentError(ip_access_blocked_msg)
31
+ return add_passenger_po
32
+
33
+
34
+ async def add_passenger(
35
+ *, page: AddPassengerPage, passengers: List[Dict[str, Any]], service_mobile: str, timeout: float = 60.0
36
+ ) -> None:
37
+ # 遍历方式添加乘客
38
+ for index, passenger in enumerate(passengers):
39
+ p_type = passenger.get("p_type")
40
+ if index != 0:
41
+ # 1. 点击【添加成人|添加儿童|添加婴儿】按钮
42
+ add_adult_btn = await page.get_add_passenger_btn(passenger_type=p_type, timeout=timeout)
43
+ await add_adult_btn.click(button="left")
44
+ logger.info(f"添加乘客页面,【添加{p_type}】按钮点击完成")
45
+ # 2. 获取 乘机人信息plane
46
+ passenger_info_plane = await page.get_add_passenger_plane(timeout=timeout, position_index=index)
47
+ logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---plane获取完成")
48
+
49
+ # 3. 选择旅客类型
50
+ adult_checkbox = await page.get_passenger_type_checkbox(
51
+ passenger_type=p_type, timeout=timeout, position_index=index
52
+ )
53
+ await adult_checkbox.click(button="left")
54
+ logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---旅客类型【{p_type}】单选框点击完成")
55
+
56
+ # 4. 输入姓名
57
+ p_name = passenger.get("p_name")
58
+ 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}>输入完成")
61
+
62
+ # 5. 选择性别
63
+ gender = passenger.get("gender")
64
+ gender_checkbox = await page.get_gender_checkbox(locator=passenger_info_plane, gender=gender, timeout=timeout)
65
+ await gender_checkbox.click(button="left")
66
+ logger.info(f"添加乘客页面,第<{index + 1}>乘机人信息---性别【{gender}】单选框点击完成")
67
+
68
+ # 6. 选择证件类型 TODO 暂时只支持身份证类型,待后续完善
69
+ id_type = passenger.get("id_type")
70
+ if id_type != "身份证":
71
+ raise EnvironmentError(f"乘客证件类型<{id_type}>暂不支持")
72
+
73
+ # 7. 输入证件号码
74
+ id_no = passenger.get("id_no")
75
+ 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}>输入完成")
78
+
79
+ # 8. 输入联系人电话
80
+ 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}>输入完成")
83
+
84
+ # 9. 勾选同意单选框
85
+ agree_checkbox = await page.get_agree_checkbox(timeout=timeout)
86
+ await agree_checkbox.click(button="left")
87
+ logger.info(f"添加乘客页面,购票须知---【已阅读并同意】单选框点击完成")
88
+
89
+ # 10. 点击【下一步】
90
+ next_btn = await page.get_next_btn(timeout=timeout)
91
+ await next_btn.click(button="left")
92
+ logger.info(f"添加乘客页面,【下一步】按钮点击完成,<{len(passengers)}>名乘客信息添加成功")
@@ -0,0 +1,83 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ # ---------------------------------------------------------------------------------------------------------
4
+ # ProjectName: python-qdairlines-helper
5
+ # FileName: book_payment.py
6
+ # Description: 预订支付控制器
7
+ # Author: ASUS
8
+ # CreateDate: 2026/01/04
9
+ # Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
10
+ # ---------------------------------------------------------------------------------------------------------
11
+ """
12
+ 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 qdairlines_helper.controller.book_search import open_book_search_page, book_search
16
+ from qdairlines_helper.controller.order_verify import load_order_verify_po, order_verify
17
+ 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
20
+
21
+
22
+ 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
+ # 1. 打开预订搜索页面
31
+ book_search_page = await open_book_search_page(page=page, protocol=protocol, domain=domain, timeout=timeout)
32
+
33
+ # 2. 预订搜索
34
+ 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,
38
+ )
39
+ logger.info(f"订单<{order_id}>,预订搜索结束")
40
+
41
+ # 3. 加载添加乘客页面对象
42
+ add_passenger_po = await load_add_passenger_po(page=page, protocol=protocol, domain=domain, timeout=timeout)
43
+
44
+ # 4. 添加乘客
45
+ await add_passenger(page=add_passenger_po, passengers=passengers, service_mobile=service_mobile, timeout=timeout)
46
+ logger.info(f"订单<{order_id}>,添加乘客结束")
47
+
48
+ # 5. 加载订单校验页面对象
49
+ order_verify_po = await load_order_verify_po(page=page, protocol=protocol, domain=domain, timeout=timeout)
50
+
51
+ # 6. 校验订单
52
+ await order_verify(page=order_verify_po, refresh_attempt=refresh_attempt, timeout=timeout)
53
+ logger.info(f"订单<{order_id}>,校验订单结束")
54
+
55
+ # 7. 加载支付类型页面对象
56
+ cash_pax_info_po = await load_cash_pax_info_po(page=page, protocol=protocol, domain=domain, timeout=timeout)
57
+
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}>,选择支付方式结束")
61
+
62
+ # 9. 根据支付方式,加载不同的收银台页面对象
63
+ if payment_type == "汇付天下":
64
+ nhlms_cash_desk_po = await load_nhlms_cash_desk_po(context=kwargs.get("context"), timeout=timeout)
65
+
66
+ # 10. 汇付天下操作支付
67
+ if sub_payment_type == "付款账户支付":
68
+ # 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
72
+ )
73
+ logger.info(f"订单<{order_id}>,汇付天下操作支付结束")
74
+
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
79
+ )
80
+ else:
81
+ raise EnvironmentError(f"汇付天下暂不支持<{sub_payment_type}>操作支付方式")
82
+ else:
83
+ raise EnvironmentError(f"暂不支持<{payment_type}>支付方式")
@@ -0,0 +1,114 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ # ---------------------------------------------------------------------------------------------------------
4
+ # ProjectName: python-qdairlines-helper
5
+ # FileName: book_search.py
6
+ # Description: 航班搜索控制器
7
+ # Author: ASUS
8
+ # CreateDate: 2026/01/04
9
+ # Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
10
+ # ---------------------------------------------------------------------------------------------------------
11
+ """
12
+ import asyncio
13
+ from typing import Dict, Any
14
+ from playwright.async_api import Page, Locator
15
+ from qdairlines_helper.utils.log_utils import logger
16
+ import qdairlines_helper.config.url_const as url_const
17
+ from qdairlines_helper.po.book_search_page import BookSearchPage
18
+ from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
19
+
20
+
21
+ async def open_book_search_page(*, page: Page, protocol: str, domain: str, timeout: float = 60.0) -> BookSearchPage:
22
+ url_prefix = f"{protocol}://{domain}"
23
+ book_search_url = url_prefix + url_const.login_url
24
+ await page.goto(book_search_url)
25
+
26
+ book_search_po = BookSearchPage(page=page, url=book_search_url)
27
+ await book_search_po.url_wait_for(url=book_search_url, timeout=timeout)
28
+ logger.info(f"即将进入青岛航空官网航班预订查询页面,页面URL<{book_search_url}>")
29
+ ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
30
+ if ip_access_blocked_msg:
31
+ raise EnvironmentError(ip_access_blocked_msg)
32
+ return book_search_po
33
+
34
+
35
+ 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
39
+ ) -> None:
40
+ try:
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.搜索栏输入起飞城市
49
+ depart_city_input = await book_search_page.get_depart_city_input(timeout=timeout)
50
+ await depart_city_input.fill(value=dep_city)
51
+ await asyncio.sleep(delay=1)
52
+ await depart_city_input.press("Enter")
53
+ logger.info(f"航班预订查询页面,搜索栏-起飞城市<{dep_city}>输入完成")
54
+
55
+ # 3.搜索栏输入抵达城市
56
+ arrive_city_input = await book_search_page.get_arrive_city_input(timeout=timeout)
57
+ await arrive_city_input.fill(value=arr_city)
58
+ await asyncio.sleep(delay=1)
59
+ await arrive_city_input.press("Enter")
60
+ logger.info(f"航班预订查询页面,搜索栏-抵达城市<{arr_city}>输入完成")
61
+
62
+ # 4.搜索栏输入起飞时间
63
+ depart_date_input = await book_search_page.get_depart_date_input(timeout=timeout)
64
+ await depart_date_input.fill(value=dep_date)
65
+ await asyncio.sleep(delay=1)
66
+ await depart_date_input.press("Enter")
67
+ logger.info(f"航班预订查询页面,搜索栏-起飞日期<{dep_date}>输入完成")
68
+
69
+ # 5.点击【查询机票】按钮
70
+ flight_query_btn = await book_search_page.get_flight_query_btn(timeout=timeout)
71
+ await flight_query_btn.click(button="left")
72
+ logger.info(f"航班预订查询页面,【查询机票】按钮点击完成")
73
+
74
+ # 6. 获取航班基本信息plane locator
75
+ flight_info_plane: Locator = await book_search_page.get_flight_info_plane(timeout=timeout)
76
+ 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}>数据")
79
+
80
+ # 7. 获取产品类型
81
+ flight_product_nav: Locator = await book_search_page.get_flight_product_nav(
82
+ locator=flight_info_plane, product_type=product_type, timeout=timeout
83
+ )
84
+ await flight_product_nav.click(button="left")
85
+ logger.info(f"航班预订查询页面,产品类型【{product_type}】选择完成")
86
+
87
+ # 8. 获取所有的产品
88
+ products: Dict[str, Any] = await book_search_page.get_flight_products(timeout=timeout)
89
+ cabin_product: Dict[str, Any] = products.get(cabin)
90
+ # 8.1 判断是否存在该舱位
91
+ if not cabin_product:
92
+ raise RuntimeError(f"航班预订查询页面,没有搜索到航班<{flight_no}>的{cabin}舱数据")
93
+ # 8.2 判断余座
94
+ seats_status = cabin_product.get("seats_status")
95
+ logger.info(f"航班预订查询页面,航班<{flight_no}>舱位<{cabin}>的座位情况:{seats_status}")
96
+ if seats_status < passengers:
97
+ raise EnvironmentError(f"航班<{flight_no}>的余票: {seats_status},数量不足")
98
+ # 8.3. 判断货币符号,是否为 ¥(人民币结算)
99
+ # 目前都为国内航班,暂不考虑货币种类
100
+
101
+ # 8.4 判断销售价格是否满足预订需要
102
+ amounts: Dict[str, Any] = cabin_product.get("amounts")
103
+ 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}],收益过高")
110
+
111
+ # 9. 点击【购票】按钮
112
+ booking_btn: Locator = cabin_product.get("booking_btn")
113
+ await booking_btn.click(button="left")
114
+ logger.info(f"航班预订查询页面,【购票】按钮点击完成")
@@ -0,0 +1,47 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ # ---------------------------------------------------------------------------------------------------------
4
+ # ProjectName: python-qdairlines-helper
5
+ # FileName: cash_pax_info.py
6
+ # Description: 支付类型控制器
7
+ # Author: ASUS
8
+ # CreateDate: 2026/01/05
9
+ # Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
10
+ # ---------------------------------------------------------------------------------------------------------
11
+ """
12
+ from playwright.async_api import Page
13
+ from qdairlines_helper.utils.log_utils import logger
14
+ import qdairlines_helper.config.url_const as url_const
15
+ from qdairlines_helper.po.cash_pax_info_page import CashPaxInfoPage
16
+ from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
17
+
18
+
19
+ async def load_cash_pax_info_po(
20
+ *, page: Page, protocol: str, domain: str, timeout: float = 60.0
21
+ ) -> CashPaxInfoPage:
22
+ url_prefix = f"{protocol}://{domain}"
23
+ cash_pax_info_url = url_prefix + url_const.cash_pax_info_url
24
+ cash_pax_info_po = CashPaxInfoPage(page=page, url=cash_pax_info_url)
25
+ await cash_pax_info_po.url_wait_for(url=cash_pax_info_url, timeout=timeout)
26
+ logger.info(f"即将进入青岛航空官网支付方式页面,页面URL<{cash_pax_info_url}>")
27
+ ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
28
+ if ip_access_blocked_msg:
29
+ raise EnvironmentError(ip_access_blocked_msg)
30
+ return cash_pax_info_po
31
+
32
+
33
+ async def select_payment_type(*, page: CashPaxInfoPage, payment_type: str = "汇付天下", timeout: float = 60.0) -> str:
34
+ # 1. 获取青岛航空官网订单号
35
+ pre_order_no = await page.get_pre_order_no(timeout=timeout)
36
+ logger.info(f"选择支付方式页面,青岛航空官网订单号<{pre_order_no}>获取完成")
37
+
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}】单选框点击完成")
42
+
43
+ # 3. 点击【确认支付】
44
+ confirm_payment_btn = await page.get_confirm_payment_btn(timeout=timeout)
45
+ await confirm_payment_btn.click(button="left")
46
+ logger.info("选择支付方式页面,【确认支付】按钮点击完成")
47
+ return pre_order_no
@@ -0,0 +1,28 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ # ---------------------------------------------------------------------------------------------------------
4
+ # ProjectName: python-qdairlines-helper
5
+ # FileName: home.py
6
+ # Description: 主页控制器模块
7
+ # Author: ASUS
8
+ # CreateDate: 2026/01/04
9
+ # Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
10
+ # ---------------------------------------------------------------------------------------------------------
11
+ """
12
+ from playwright.async_api import Page
13
+ from qdairlines_helper.po.home_page import HomePage
14
+ from qdairlines_helper.utils.log_utils import logger
15
+ import qdairlines_helper.config.url_const as url_const
16
+ from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
17
+
18
+
19
+ async def load_home_po(*, page: Page, protocol: str, domain: str, timeout: float = 60.0) -> HomePage:
20
+ url_prefix = f"{protocol}://{domain}"
21
+ home_url = url_prefix + url_const.home_url
22
+ home_po = HomePage(page=page, url=home_url)
23
+ await home_po.url_wait_for(url=home_url, timeout=timeout)
24
+ logger.info(f"即将进入青岛航空官网首页,页面URL<{home_url}>")
25
+ ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
26
+ if ip_access_blocked_msg:
27
+ raise EnvironmentError(ip_access_blocked_msg)
28
+ return home_po
@@ -0,0 +1,74 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ # ---------------------------------------------------------------------------------------------------------
4
+ # ProjectName: python-qdairlines-helper
5
+ # FileName: nhlms_cashdesk.py
6
+ # Description: 汇付天下收银台控制器
7
+ # Author: ASUS
8
+ # CreateDate: 2026/01/05
9
+ # Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
10
+ # ---------------------------------------------------------------------------------------------------------
11
+ """
12
+ import inspect
13
+ from typing import Tuple, Callable
14
+ from playwright.async_api import BrowserContext
15
+ from qdairlines_helper.utils.log_utils import logger
16
+ import qdairlines_helper.config.url_const as url_const
17
+ from qdairlines_helper.po.nhlms_cash_desk_page import NhlmsCashDeskPage
18
+ from playwright_helper.utils.browser_utils import switch_for_table_window
19
+ from qdairlines_helper.utils.exception_utils import DuplicatePaymentError
20
+
21
+
22
+ async def load_nhlms_cash_desk_po(*, context: BrowserContext, timeout: float = 60.0) -> NhlmsCashDeskPage:
23
+ nhlms_cashdesk_url = url_const.nhlms_cashdesk_domain + url_const.nhlms_cashdesk_url
24
+
25
+ current_page = await switch_for_table_window(
26
+ browser=context, url_keyword=url_const.nhlms_cashdesk_url, wait_time=int(timeout)
27
+ )
28
+
29
+ nhlms_cashdesk_po = NhlmsCashDeskPage(page=current_page, url=nhlms_cashdesk_url)
30
+ await nhlms_cashdesk_po.url_wait_for(url=nhlms_cashdesk_url, timeout=timeout)
31
+ logger.info(f"即将进入汇付天下收银台页面,页面URL<{nhlms_cashdesk_url}>")
32
+ return nhlms_cashdesk_po
33
+
34
+
35
+ async def pay_account_payment(
36
+ *, page: NhlmsCashDeskPage, operator_account: str, pay_password: str, order_id: int,
37
+ is_pay_completed_callback: Callable, payment_type: str = "付款账户支付", timeout: float = 60.0
38
+ ) -> Tuple[str, float]:
39
+ # 1. 获取收银台支付流水
40
+ pay_transaction = await page.get_order_transaction(timeout=timeout)
41
+ logger.info(f"汇付天下收银台页面,支付流水<{pay_transaction}>获取完成")
42
+
43
+ # 2. 获取订单支付金额
44
+ actual_payment_amount = await page.get_order_amount(timeout=timeout)
45
+ logger.info(f"汇付天下收银台页面,支付金额<{actual_payment_amount}>获取完成")
46
+
47
+ # 3. 获取付款方式tab
48
+ payment_type_tab = await page.get_payment_type_tab(payment_type=payment_type, timeout=timeout)
49
+ await payment_type_tab.click(button="left")
50
+ logger.info(f"汇付天下收银台页面,【{payment_type}】Tab点击完成")
51
+
52
+ # 4. 输入操作员号
53
+ username_input = await page.get_username_input(timeout=timeout)
54
+ await username_input.fill(value=operator_account)
55
+ logger.info(f"汇付天下收银台页面,操作员号<{operator_account}>输入完成")
56
+
57
+ # 5. 输入交易密码
58
+ password_input = await page.get_password_input(timeout=timeout)
59
+ await password_input.fill(value=pay_password)
60
+ logger.info(f"汇付天下收银台页面,交易密码<{pay_password}>输入完成")
61
+
62
+ # 6. 校验订单是否已经被支付
63
+ if inspect.iscoroutinefunction(is_pay_completed_callback):
64
+ is_pay: bool = await is_pay_completed_callback(order_id=order_id)
65
+ else:
66
+ is_pay: bool = is_pay_completed_callback(order_id=order_id)
67
+ if is_pay is True:
68
+ raise DuplicatePaymentError(order_id=order_id)
69
+
70
+ # 6. 点击【确认支付】
71
+ confirm_payment_btn = await page.get_confirm_payment_btn(timeout=timeout)
72
+ await confirm_payment_btn.click(button="left")
73
+ logger.info("汇付天下收银台页面,【确认支付】按钮点击完成")
74
+ return pay_transaction, actual_payment_amount
@@ -0,0 +1,53 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ # ---------------------------------------------------------------------------------------------------------
4
+ # ProjectName: python-qdairlines-helper
5
+ # FileName: order_detail.py
6
+ # Description: 订单详情控制器
7
+ # Author: ASUS
8
+ # CreateDate: 2026/01/06
9
+ # Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
10
+ # ---------------------------------------------------------------------------------------------------------
11
+ """
12
+ from aiohttp import CookieJar
13
+ from typing import Dict, Any, Optional, List
14
+ from qdairlines_helper.utils.log_utils import logger
15
+ from qdairlines_helper.http.flight_order import FlightOrder
16
+
17
+
18
+ async def get_order_itinerary(
19
+ *, playwright_state: Dict[str, Any], token: Optional[str], pre_order_no: str, user_id: str,
20
+ proxy: Dict[str, Any], domain: Optional[str] = None, protocol: Optional[str] = None,
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
23
+ ) -> Dict[str, Any]:
24
+ flight_order = FlightOrder(
25
+ playwright_state=playwright_state, token=token, domain=domain, protocol=protocol, timeout=timeout, retry=retry,
26
+ enable_log=enable_log, cookie_jar=cookie_jar
27
+ )
28
+ _order_info: Dict[str, Any] = dict(pre_order_no=pre_order_no)
29
+ try:
30
+ order_info = await flight_order.get_order_details(
31
+ pre_order_no=pre_order_no, user_id=user_id, is_end=True, proxy=proxy, headers=headers
32
+ )
33
+ if isinstance(order_info, dict) and order_info.get("result"):
34
+ result = order_info.get("result") or dict()
35
+ _order_info["cash_unit"] = result.get("cashUnit")
36
+ _order_info["order_status"] = result.get("orderStatus")
37
+ _order_info["actual_payment_amount"] = result.get("totalPrice")
38
+ passenger_infos: List[Dict[str, Any]] = result.get("passengerInfoVoList")
39
+ order_itineraries = list()
40
+ for passenger_info in passenger_infos:
41
+ id_no = passenger_info.get("idNo")
42
+ order_itinerary = passenger_info.get("tktNo")
43
+ passenger = passenger_info.get("passName")
44
+ order_itineraries.append(dict(
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
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ # ---------------------------------------------------------------------------------------------------------
4
+ # ProjectName: python-qdairlines-helper
5
+ # FileName: order_query.py
6
+ # Description: 订单查询控制器
7
+ # Author: ASUS
8
+ # CreateDate: 2026/01/04
9
+ # Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
10
+ # ---------------------------------------------------------------------------------------------------------
11
+ """
12
+ from typing import Any
13
+ from playwright.async_api import Page
14
+ from qdairlines_helper.utils.log_utils import logger
15
+ import qdairlines_helper.config.url_const as url_const
16
+ from qdairlines_helper.po.air_order_page import AirOrderPage
17
+ from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
18
+
19
+
20
+ async def open_air_page(*, page: Page, protocol: str, domain: str, timeout: float = 60.0) -> AirOrderPage:
21
+ url_prefix = f"{protocol}://{domain}"
22
+ air_order_url = url_prefix + url_const.air_order_url
23
+ await page.goto(air_order_url)
24
+
25
+ air_order_po = AirOrderPage(page=page, url=air_order_url)
26
+ await air_order_po.url_wait_for(url=air_order_url, timeout=timeout)
27
+ logger.info(f"即将进入青岛航空官网航班查询页面,页面URL<{air_order_url}>")
28
+ ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
29
+ if ip_access_blocked_msg:
30
+ raise EnvironmentError(ip_access_blocked_msg)
31
+ return air_order_po
32
+
33
+
34
+ async def is_open_air_page_callback(
35
+ *, page: Page, protocol: str, domain: str, timeout: float = 60.0, **kwargs: Any
36
+ ) -> bool:
37
+ try:
38
+ await open_air_page(page=page, protocol=protocol, domain=domain, timeout=timeout)
39
+ return True
40
+ except (Exception,):
41
+ return False
@@ -0,0 +1,43 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ # ---------------------------------------------------------------------------------------------------------
4
+ # ProjectName: python-qdairlines-helper
5
+ # FileName: order_verify.py
6
+ # Description: 订单校验控制器
7
+ # Author: ASUS
8
+ # CreateDate: 2026/01/05
9
+ # Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
10
+ # ---------------------------------------------------------------------------------------------------------
11
+ """
12
+ from playwright.async_api import Page
13
+ from qdairlines_helper.utils.log_utils import logger
14
+ import qdairlines_helper.config.url_const as url_const
15
+ from qdairlines_helper.po.order_verify_page import OrderVerifyPage
16
+ from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
17
+
18
+
19
+ async def load_order_verify_po(*, page: Page, protocol: str, domain: str, timeout: float = 60.0) -> OrderVerifyPage:
20
+ url_prefix = f"{protocol}://{domain}"
21
+ order_verify_url = url_prefix + url_const.order_verify_url
22
+ order_verify_po = OrderVerifyPage(page=page, url=order_verify_url)
23
+ await order_verify_po.url_wait_for(url=order_verify_url, timeout=timeout)
24
+ logger.info(f"即将进入青岛航空官网订单校验页面,页面URL<{order_verify_url}>")
25
+ ip_access_blocked_msg = await get_ip_access_blocked_msg(page=page, timeout=3)
26
+ if ip_access_blocked_msg:
27
+ raise EnvironmentError(ip_access_blocked_msg)
28
+ return order_verify_po
29
+
30
+
31
+ async def order_verify(*, page: OrderVerifyPage, refresh_attempt: int = 3, timeout: float = 60.0) -> None:
32
+ # 1. 先确认订单数据是否加载出来
33
+ await page.get_order_info_plane(timeout=timeout, refresh_attempt=refresh_attempt)
34
+
35
+ # 2. 勾选【同意条款】单选框
36
+ agree_checkbox = await page.get_agree_checkbox(timeout=timeout)
37
+ await agree_checkbox.click(button="left")
38
+ logger.info(f"订单校验页面,规定及条款---【我已核对】单选框点击完成")
39
+
40
+ # 3. 点击【下一步】
41
+ next_btn = await page.get_next_btn(timeout=timeout)
42
+ await next_btn.click(button="left")
43
+ logger.info("订单校验页面,【下一步】按钮点击完成")