import base64 import hashlib import json import platform import random import re import time import traceback import uuid from urllib import parse from curl_cffi import requests from fake_useragent import UserAgent from tenacity import retry, stop_after_attempt, wait_exponential from apps.jd.schemas.models import QueryCardResponseData from apps.jd.services.jstk import NormalJsTk, TxSMJsTk from apps.jd.services.utils import gen_cipher_ep, get_pay_sign, get_sign from apps.shared.proxy_pool.proxy_pool import ProxyPoolFactory from core.config import ProxyPoolType, settings from core.responses import BusinessCode from observability.logging import LoggerAdapter, get_logger_with_trace # logger: LoggerAdapter = get_logger_with_trace(__name__) class AppStoreSpider: def __init__( self, cookies, order_num, face_price: float = 0, user_client: str = UserAgent().random, ): self.SKU_MA = { "10.00": 10022039398507, "50.00": 11170365589, "100.00": 11183343342, "200.00": 11183368356, "500.00": 11183445154, "1000.00": 10066407336810, "68.00": 10023403480808, "00.00": 10026503885976, } self.__proxy_pool = ProxyPoolFactory.get_proxy_pool(settings.proxy_type) self.order_num = order_num self.cookies = cookies self.face_price = face_price self.__user_client = user_client self.md5_key = "e7c398ffcb2d4824b4d0a703e38eb0bb" self.time_stamp = int(time.time() * 1000) self.submit_order_url = "https://api.m.jd.com/appstore/submitorder" self.ticket_url = "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkcaptcha" self.jd_api = "https://api.m.jd.com/api" self.action_url = "https://api.m.jd.com/client.action" self.check_captcha_url = ( "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkcaptcha" ) self.headers = { "content-type": "application/x-www-form-urlencoded", "cookie": self.cookies, "origin": "https://txsm-m.jd.com", "referer": "https://txsm-m.jd.com/", "user-agent": self.get_user_agent(), } self.current_os = platform.system() self.js = None self.__js_tk = TxSMJsTk() self.__js_tk.generate_token(self.get_user_agent(), cookies) expiring_pool = ProxyPoolFactory.get_proxy_pool( ProxyPoolType.EXPIRING, expire_time=60 ) self.proxy = expiring_pool.get_proxy(order_id=uuid.uuid4().hex) # self.init_js() def h5st(self, pt_pin: str, app_id: str, user_agent: str, data: dict): logger.info(f"h5st: {pt_pin}, {app_id}, {user_agent}, {data}") response = requests.post( "http://152.136.211.112:9001/h5st", json={ "appCode": "Z6eXo8Dl", "pin": pt_pin, "ua": user_agent, "body": data, "appId": app_id, }, ) return response.json().get("body", {}).get("h5st", {}).get("h5st", "") def get_user_agent(self) -> str: if "iPhone" in self.__user_client: return "Mozilla/5.0 (iPhone; CPU iPhone OS 17_7_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1" return f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/{random.randint(120, 140)}.0.0.0" def get_eid_token(self): return self.__js_tk.get_token() def format_number(self, number): if not number: number = 0 number = float(number) formatted_number = "{:.2f}".format(number) return formatted_number def get_s(self): use_bean = 0 buy_num = 1 face_price = self.face_price type = 1 brand_id = 999440 eid = self.__js_tk.get_eid() pay_mode = 0 coupon_ids = "" sku_id = self.SKU_MA.setdefault( self.format_number(self.face_price), 10026503885976 ) total_price = self.format_number(self.face_price) order_source = 2 order_source_type = 2 s = f"{use_bean}{buy_num}{face_price}{type}{brand_id}{eid}{pay_mode}{coupon_ids}{sku_id}{total_price}{order_source}{order_source_type}" return s def get_u(self): u = f"{self.time_stamp}{self.md5_key}" return u def get_enc_str(self): s = self.get_s() u = self.get_u() _str = f"{s}{u}" enc_str = hashlib.md5(_str.encode()).hexdigest() return enc_str def get_body(self, enc_str): item = { "useBean": 0, "buyNum": 1, "facePrice": self.face_price, "type": 1, "brandId": 999440, "eid": self.__js_tk.get_eid(), "payMode": "0", "couponIds": "", "skuId": self.SKU_MA.setdefault( self.format_number(self.face_price), 10026503885976 ), "totalPrice": self.format_number(self.face_price), "orderSource": 2, "orderSourceType": 2, "t": self.time_stamp, "channelSource": "txzs", "encStr": enc_str, "babelChannel": "ttt35", } json_string = json.dumps(item) encrypted_message = ( base64.b64encode(json_string.encode("utf-8")).decode("utf-8").rstrip("=") ) return encrypted_message def submit_order(self, body): data = { "appid": "txsm-m", "client": "iPhone", "functionId": "appstore_order_submit_new", "uuid": uuid.uuid4().hex.replace("-", ""), "osVersion": "16.6", "screen": "1170.000046491623*2532.0001006126404", "t": self.time_stamp, "loginType": "2", "x-api-eid-token": self.__js_tk.get_eid(), "body": body, } proxy = self.__proxy_pool.get_proxy(order_id=self.order_num) if proxy: response = requests.post( url=self.submit_order_url, headers=self.headers, data=data, proxies={"http": proxy, "https": proxy}, ) else: response = requests.post( url=self.submit_order_url, headers=self.headers, data=data ) return response.json() @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=15) ) def get_ticket_res(self): headers = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Language": "zh-CN,zh;q=0.9", "Cache-Control": "no-cache", "Connection": "keep-alive", "Pragma": "no-cache", "Upgrade-Insecure-Requests": "1", "User-Agent": self.get_user_agent(), } url = "http://ticket_slide_server:99/api/TX?aid=2093769752&host=https://t.captcha.qq.com&ip=" proxy = self.__proxy_pool.get_proxy(order_id=self.order_num) if proxy: response = requests.get( url, headers=headers, verify=False, proxies={"http": proxy, "https": proxy}, ) else: response = requests.get(url, headers=headers, verify=False) return response.json() def decrypt_card_info(self, message): key = "2E1ZMAF88CCE5EBE551FR3E9AA6FF322" card_info = self.js.call("decryptDes", message, key) card_info = json.loads(card_info) return card_info[0] def get_card_res(self, order_id: str): """ 获取订单卡号/卡密详情 :param order_id: 订单号 :return: 卡号/卡密详情 """ user_agent = self.get_user_agent() headers = { "accept": "application/json, text/plain, */*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", "content-type": "application/x-www-form-urlencoded", "origin": "https://recharge.m.jd.com", "priority": "u=1, i", "referer": "https://recharge.m.jd.com/", "user-agent": user_agent, "x-referer-page": "https://recharge.m.jd.com/orderDetail", "x-rp-client": "h5_1.0.0", "cookie": self.cookies, } token_service = NormalJsTk() token_service.generate_token(user_agent, self.cookies) data = { "appid": "tsw-m", "functionId": "getGPOrderDetail", "t": self.time_stamp, "body": '{"appKey":"android","source":41,"orderId":"%s","version":"1.10","rechargeversion":"12.8","moduleName":"JDReactVirtualRecharge","apiVersion":"new"}' % (order_id), "uuid": "1736139761886772644158", "screen": "2560.5*1600.5", "x-api-eid-token": token_service.get_token(), } cookie_dict: dict[str, str] = { cookie.split("=")[0].strip(): cookie.split("=")[1].strip() for cookie in self.cookies.split(";") } h5st = self.h5st(cookie_dict.get("pt_pin", ""), "8e94a", user_agent, data) data["h5st"] = h5st response = requests.post(self.jd_api, headers=headers, data=data) return response.json() def get_pay_res(self, order_id): headers = { "accept": "*/*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", "content-type": "application/json;charset=UTF-8", "origin": "https://trade.m.jd.com", "priority": "u=1, i", "referer": "https://trade.m.jd.com/", "sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Windows"', "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-site", "user-agent": self.get_user_agent(), "x-referer-page": "https://trade.m.jd.com/order/orderlist_jdm.shtml", "x-rp-client": "h5_1.0.0", "cookie": self.cookies, } params = { "t": f"{self.time_stamp}", "loginType": "2", "loginWQBiz": "golden-trade", "appid": "m_core", "client": "MacIntel", "clientVersion": "", "build": "", "osVersion": "null", "screen": "1920*1080", "networkType": "4g", "partner": "", "forcebot": "", "d_brand": "", "d_model": "", "lang": "zh-CN", "scope": "", "sdkVersion": "", "openudid": "", "uuid": uuid.uuid4().hex.replace("-", ""), "x-api-eid-token": self.__js_tk.get_token(), "functionId": "pay_info_m", "body": '{"appType":3,"bizType":"2","deviceUUId":"","platform":3,"sceneval":"2","source":"m_inner_myJd.orderFloor_orderlist","systemBaseInfo":"{\\"pixelRatio\\":2,\\"screenWidth\\":1920,\\"screenHeight\\":1080,\\"windowWidth\\":1920,\\"windowHeight\\":959,\\"statusBarHeight\\":null,\\"safeArea\\":{\\"bottom\\":0,\\"height\\":0,\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"width\\":0},\\"bluetoothEnabled\\":false,\\"locationEnabled\\":false,\\"wifiEnabled\\":false,\\"deviceOrientation\\":\\"landscape\\",\\"benchmarkLevel\\":-1,\\"brand\\":\\"\\",\\"model\\":\\"\\",\\"system\\":null,\\"platform\\":\\"MacIntel\\",\\"SDKVersion\\":\\"\\",\\"enableDebug\\":false,\\"language\\":\\"zh-CN\\",\\"version\\":\\"\\",\\"theme\\":\\"light\\",\\"fontSizeSetting\\":null,\\"albumAuthorized\\":false,\\"cameraAuthorized\\":false,\\"locationAuthorized\\":false,\\"microphoneAuthorized\\":false,\\"notificationAuthorized\\":false,\\"notificationAlertAuthorized\\":false,\\"notificationBadgeAuthorized\\":false,\\"notificationSoundAuthorized\\":false,\\"phoneCalendarAuthorized\\":false,\\"locationReducedAccuracy\\":false,\\"environment\\":\\"\\"}","orderId":"%s","origin":10,"tenantCode":"jgm","bizModelCode":"2","bizModeClientType":"M","bizModeFramework":"Taro","externalLoginType":1,"token":"3852b12f8c4d869b7ed3e2b3c68c9436","appId":"m91d27dbf599dff74"}' % order_id, "h5st": "20250513003554680%3Bg9gwwg93pzmtgwp3%3B9b070%3Btk03wac841c0518nh3UsKG1ttEEV8mnlrHkEhynQ-S2x3PbyeW6t_Cn0S29bQlTaYFu10XQnqBTZqpZVg7yWDGL1-Ien%3B57ceff3efad640ff671169173de4393cea87add0aeafeb28fe45d8ed0885f34d%3B4.2%3B1747067754680%3Be2dbf31f18c0566b03779cdfb7daf1889536ac5980ca54061cc5d12b276f6fcb74aa49e97fb21499ccffae2bb79d16ba8b664ce42ae53fce6b12c709789cf1eeb1e039f7a491fa6c0bb41380e593285cdb5cbaca0dd43586ff7937f44c9f6f0a1104467ba19dc3d8a5081e3bf7da385fee7ad469c515d6cd459c9efc82eb331e899e6c338ec36e3cfcf4466efd3d4bb31f752f3730e1343447f0524cfabc0dd22d9dbc853d384e6f6969ac7eb9339b72b5ff17147816fa422a79b375f28edbb490089ee8dd463b06339ba1536ba3e4ad90d82913ad4bd6049ff583666f24d648bb47c6d2f65380c4bda6ac0eea24fc622fa95763f9352247d21980f0b8dcdc54d7a2d18b84dad43660f88fcfb8c8613d86daa87f58addf1a60f3df6e5626753bf497864d03408578ff78bd5007b1bf86f0747b05681c58b32d27ae7b2de07059", } proxy = self.__proxy_pool.get_proxy(order_id=self.order_num) if proxy: response = requests.get( self.action_url, headers=headers, params=params, proxies={"http": proxy, "https": proxy}, ) else: response = requests.get(self.action_url, headers=headers, params=params) return response.json() def plat_pay_channel_res(self, pay_id): headers = { "accept": "application/json, text/plain, */*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", "content-type": "application/x-www-form-urlencoded", "origin": "https://mpay.m.jd.com", "priority": "u=1, i", "referer": "https://mpay.m.jd.com/", "sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Windows"', "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-site", "user-agent": self.get_user_agent(), "x-referer-page": "https://mpay.m.jd.com/mpay.623f9498223cf9b9de9f.html", "x-rp-client": "h5_1.0.0", "cookie": self.cookies, } params = {"functionId": "platPayChannel", "appid": "mcashier", "scval": "mpay"} data = { "body": '{"appId":"m_D1vmUq63","payId":"%s","source":"mcashier","origin":"h5","mcashierTraceId":1729842386189}' % (pay_id), "x-api-eid-token": self.__js_tk.get_token(), "h5st": "20241025154626341;0587023779148689;303a7;tk03w7da11b8b18nOQ6HZGNLj6DdtLQBS695YHMu7RyONolcwWCRc8ihMUs5ITCem6HIGhdYo_DpJ62yYLkdrxIBxE0N;ab8a935407baae929b0d3e267f67693f9c00a5d876ad149c10e904371f87726f;3.1;1729842386341;24c9ee85e67cf80746dd82817ecbeafc7a829b35c7f446a4c7d476cc9faa1d8834a93323ad7bce9bef1bba682b93d2e3694e425ff68d304875c1ae9e2ae398cfd94e4ff03cd3bdd9f0f600a0d75c92d537baaa944d39072a92db7dc20c99e7f80889e289e78a1f8f93c57f8471890c464b78b61e9b3bbffea712e6d6c671ad12", } proxy = self.__proxy_pool.get_proxy(order_id=self.order_num) if proxy: response = requests.post( self.action_url, headers=headers, params=params, data=data, proxies={"http": proxy, "https": proxy}, ) else: response = requests.post( self.action_url, headers=headers, params=params, data=data ) return response.json() def plat_wx_pay_res(self, pay_id): headers = { "accept": "application/json, text/plain, */*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", "content-type": "application/x-www-form-urlencoded", "origin": "https://mpay.m.jd.com", "priority": "u=1, i", "referer": "https://mpay.m.jd.com/", "sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Windows"', "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-site", "user-agent": self.get_user_agent(), "x-referer-page": "https://mpay.m.jd.com/mpay.623f9498223cf9b9de9f.html", "x-rp-client": "h5_1.0.0", "cookie": self.cookies, } params = {"functionId": "platWapWXPay", "appid": "mcashier", "scval": "mpay"} data = { "body": '{"appId":"m_D1vmUq63","payId":"%s","eid":"PBZHV4O4RF5SAA7QGYPZEPRPAYOCCF3WTUQYMWEFASLCJNYX2HWO7C35L5TYQUL66FGXVVMXDWKTBEEE24LW42XEWM","source":"mcashier","origin":"h5","mcashierTraceId":1729837716957}' % pay_id, "x-api-eid-token": "jdd03PBZHV4O4RF5SAA7QGYPZEPRPAYOCCF3WTUQYMWEFASLCJNYX2HWO7C35L5TYQUL66FGXVVMXDWKTBEEE24LW42XEWMAAAAMSYJMLE3IAAAAAC5OTMMGGUM5SYIX", # "h5st": "20241025142845015;0587023779148689;303a7;tk03w7da11b8b18nOQ6HZGNLj6DdtLQBS695YHMu7RyONolcwWCRc8ihMUs5ITCem6HIGhdYo_DpJ62yYLkdrxIBxE0N;48aee025d65fab059f98c546832f7a1d7bca99dba6bc0df9bd6328b9817cf394;3.1;1729837725015;24c9ee85e67cf80746dd82817ecbeafc7a829b35c7f446a4c7d476cc9faa1d8834a93323ad7bce9bef1bba682b93d2e3694e425ff68d304875c1ae9e2ae398cfd94e4ff03cd3bdd9f0f600a0d75c92d537baaa944d39072a92db7dc20c99e7f80889e289e78a1f8f93c57f8471890c464b78b61e9b3bbffea712e6d6c671ad12" } proxy = self.__proxy_pool.get_proxy(order_id=self.order_num) if proxy: response = requests.post( self.action_url, headers=headers, params=params, data=data, proxies={"http": proxy, "https": proxy}, ) else: response = requests.post( self.action_url, headers=headers, params=params, data=data ) return response.json() def get_deep_link_res(self, mweb_url): headers = { "Host": "wx.tenpay.com", "sec-ch-ua-platform": '"Windows"', "User-Agent": self.get_user_agent(), "sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"', "sec-ch-ua-mobile": "?0", "Accept": "*/*", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty", "Referer": mweb_url, "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", } prepay_id = re.search(r"prepay_id=(wx\w+)", mweb_url).group(1) package = re.search(r"package=(\d+)", mweb_url).group(1) ticket_res = self.get_ticket_res() logger.info(f"订单号:{self.order_num},获取ticket返回:{ticket_res}") ticket = ticket_res["ticket"] randstr = ticket_res["randstr"] params = { "ticket": ticket, "randstr": randstr, "prepayid": prepay_id, "package": package, } proxy = self.__proxy_pool.get_proxy(order_id=self.order_num) if proxy: response = requests.get( self.check_captcha_url, headers=headers, params=params, proxies={"http": proxy, "https": proxy}, ) else: response = requests.get( self.check_captcha_url, headers=headers, params=params ) return response.json() def get_card_secret(self, jd_order_num): card_res = self.get_card_res(jd_order_num) logger.info(f"获取卡密信息返回:{card_res}") if not card_res: return 110, card_res if card_res.get("code") != "0": return 110, card_res card_info = card_res.get("result").get("cardInfos") if not card_info: return 110, card_res card_info = self.decrypt_card_info(card_info) return 100, card_info def query_card(self, jd_order_num): """ 查询订单状态和卡密信息 :param jd_order_num: 订单号 :return: code=100成功,包含order_status, card_num, card_pwd字段 """ try: card_res = self.get_card_res(jd_order_num) logger.info(f"获取卡密信息返回:{card_res}") if not card_res: return BusinessCode.JD_ORDER_NORMAL_ERR, QueryCardResponseData( order_status="", card_num="", card_pwd="", remark=str(card_res) ) # 判断没有登录的场景 if card_res.get("code") == "20001": return BusinessCode.JD_ORDER_CK_ERR, QueryCardResponseData( order_status="", card_num="", card_pwd="", remark=str(card_res) ) if card_res.get("code") != "0": return BusinessCode.JD_ORDER_NORMAL_ERR, QueryCardResponseData( order_status="", card_num="", card_pwd="", remark=str(card_res) ) card_info_list = card_res.get("result", {}).get("cardInfos", []) if not card_info_list: return BusinessCode.JD_ORDER_NORMAL_ERR, QueryCardResponseData( order_status="获取失败", card_num="", card_pwd="", remark=str(card_res), ) try: card_info = self.decrypt_card_info(card_info_list) return BusinessCode.SUCCESS, QueryCardResponseData( order_status=card_info.get("status", "成功"), card_num=card_info.get("cardNo", ""), card_pwd=card_info.get("cardPwd", ""), remark=str(card_res), ) except Exception as e: logger.error(f"解密卡密信息失败: {e}") return BusinessCode.JD_ORDER_NORMAL_ERR, QueryCardResponseData( order_status="解密失败", card_num="", card_pwd="" ) except Exception as e: logger.error(f"查询卡密信息异常: {e}") return BusinessCode.JD_ORDER_NORMAL_ERR, QueryCardResponseData( order_status="查询失败", card_num="", card_pwd="" ) def run(self) -> tuple[BusinessCode, dict, str]: # 获取加密参数 try: enc_str = self.get_enc_str() except KeyError as e: return BusinessCode.JD_ORDER_FACE_PRICE_ERR, {}, "充值面值有误" # 获取请求body body = self.get_body(enc_str) # 提交预付款订单 order_res = self.submit_order(body) # 提交预付款订单 logger.info( f"订单号:{self.order_num},app_store提交预付款订单返回:{order_res}" ) if order_res.get("code") == 300: return BusinessCode.JD_ORDER_CK_ERR, {}, str(order_res) if order_res.get("code") != 200: return BusinessCode.JD_ORDER_NORMAL_ERR, {}, str(order_res) # 提交预付款订单 # # 获取支付信息 pay_res = self.get_pay_res(order_res["data"]) logger.info(f"订单号:{self.order_num},app_store获取支付信息返回:{pay_res}") # pay_res = {'body': {'payId': '0d02ea36cf7946a89ed6f51898d2d2a3', 'url': 'https://mpay.m.jd.com/mpay.25f73d1adb414ba7e0e3.html?appId=m_D1vmUq63&payId=0d02ea36cf7946a89ed6f51898d2d2a3&orderId=314518542769&tId=mpay'}, 'code': '0', 'message': 'success', 'timestamp': 1747127287841} if pay_res.get("code") != "0": return BusinessCode.JD_ORDER_NORMAL_ERR, {}, str(pay_res) # 获取支付信息 pay_id = pay_res["body"]["payId"] # 获取微信支付信息 pay_channel_res = self.plat_pay_channel_res(pay_id) logger.info( f"订单号:{self.order_num},app_store请求微信渠道返回:{pay_channel_res}" ) if pay_channel_res.get("code") != "0": return BusinessCode.JD_ORDER_NORMAL_ERR, {}, str(pay_channel_res) if "iPhone" in self.get_user_agent(): code, msg = self.ios_pay(pay_channel_res["orderId"], pay_id) data = {} else: code, data, msg = self.web_pay(pay_id) if code != BusinessCode.SUCCESS: return code, data, msg return ( code, { "deeplink": msg, "order_id": pay_channel_res["orderId"], "pay_id": pay_id, "face_price": self.face_price, }, "请求成功", ) def web_pay(self, pay_id: str) -> tuple[BusinessCode, dict, str]: # 获取微信支付信息 wx_pay_res = self.plat_wx_pay_res(pay_id) logger.info( f"订单号:{self.order_num},app_store获取微信支付信息返回:{wx_pay_res}" ) if wx_pay_res.get("code") != "0": return BusinessCode.JD_ORDER_NORMAL_ERR, {}, str(wx_pay_res) if wx_pay_res.get("errorCode") == "-1": wx_pay_res["order_id"] = self.order_num wx_pay_res["pay_id"] = pay_id wx_pay_res["face_price"] = self.face_price return BusinessCode.JD_ORDER_NORMAL_ERR, {}, str(wx_pay_res) mweb_url = wx_pay_res["payInfo"]["mweb_url"] # 获取支付链接信息 deep_link_res = self.get_deep_link_res(mweb_url) logger.info( f"订单号:{self.order_num},app_store获取支付链接deep_link信息返回:{deep_link_res}" ) if deep_link_res.get("retcode") != 1: return BusinessCode.JD_ORDER_NORMAL_ERR, {}, str(deep_link_res) return BusinessCode.SUCCESS, {}, deep_link_res["deeplink"] def ios_pay(self, order_id: str, pay_id: str) -> tuple[BusinessCode, str]: headers = { "Host": "api.m.jd.com", "charset": "UTF-8", "user-agent": "okhttp/3.12.1;jdmall;android;version/11.1.0;build/98139", "cache-control": "no-cache", "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "cookie": self.cookies, } url = "https://api.m.jd.com/client.action" func = "platPayChannel" version = "11.1.0" uuid_ = uuid.uuid4().hex.replace("-", "") ts = int(time.time() * 1000) ep = gen_cipher_ep(uuid_, ts) pay_sign = get_pay_sign(order_id=order_id, face_price=self.face_price) body = ( '{"appId":"jd_android_app4","client":"android","fk_aid":"%s","fk_appId":"com.jingdong.app.mall","fk_latitude":"NfUaIOrNMes=","fk_longtitude":"NfUaIOrNMes=","fk_terminalType":"02","fk_traceIp":"26.26.26.1","hasCyberMoneyPay":"0","hasHuaweiPay":"0","hasOCPay":"0","hasUPPay":"0","orderId":"%s","orderPrice":"%s","orderType":"37","orderTypeCode":"0","origin":"native","payId":"%s","paySign":"%s","paySourceId":"2","payablePrice":"%s","sdkToken":"jdd016CSEHGQ3IPOGEXVOBUPDLTBSKZZUMESDDOP4PPRC2E2R7CC2K4LXQN63E6A3P3Y76GN4M5TMPIZGOWYQJG4MO6ET75VQFVQL2MIYRFQ01234567","source":"jdapp","style":"normal","supportNFC":"1"}' % ( uuid_, order_id, self.face_price, pay_id, pay_sign, self.face_price, ) ) formatted_params = get_sign(func, body, uuid_, version) params = { "functionId": func, "clientVersion": version, "build": "98139", "client": "android", "partner": "wandoujia", # "eid": "eidAf760812200s2xOZPVA0sThGrvhABq1zrPMTmUOM1tv8FFg1FjE8yRJtYdV/UFhJuIkVZbrk/xl XPeoFQTNgMiYJpXeeyACCVPbt0/3R7R3Gd 8y", "sdkVersion": "28", "lang": "zh_CN", "harmonyOs": "0", "networkType": "wifi", # "uts": "0f31TVRjBSsqndu4/jgUPz6uymy50MQJ8fZr7wet7pLPYx9jEXMpd8VCD64sq/eBbsH5zZBXnKZMkN1vxnjOrpfx7GiQBINsuAELLpjOiZsCHkTDoRW/d9talOxyn2bo1YZLq8uq5Kdx/Fd7diA023Qwr+5V5TzeOnca3cc6QzMFh8p+DS2gR6Bimgz0BiNqbeN1q1NA9rJ/t5QOAIH9EA==", "uemps": "0-0", "ext": '{"prstate":"0","pvcStu":"1"}', "ef": "1", "ep": ep, "st": formatted_params["st"], "sign": formatted_params["sign"], "sv": formatted_params["sv"], } data = {"body": body, "": ""} proxy = self.__proxy_pool.get_proxy(order_id=self.order_num) if proxy: response = requests.post( url=url, params=params, headers=headers, data=data, proxies={"http": proxy, "https": proxy}, ) else: response = requests.post(url=url, params=params, headers=headers, data=data) client_res = response.json() logger.info(f"订单id:{self.order_num},获取支付参数res返回:{client_res}") # 不支持该支付类型 if client_res.get("errorCode") == "-2": return BusinessCode.JD_ORDER_TYPE_NOT_SUPPORTED_ERR, "订单类型不支持" if client_res.get("errorCode") == "3": return BusinessCode.JD_ORDER_CK_ERR, "ck失效" # 订单失效 if client_res.get("errorCode") == "-3": return BusinessCode.JD_ORDER_EXPIRED_ERR, "订单失效" # 订单与下单账号不同 if client_res.get("errorCode") == "-5": return BusinessCode.JD_ORDER_NORMAL_ERR, "订单与下单账号不匹配" # 订单已取消 if client_res.get("errorCode") == "-100": return BusinessCode.JD_ORDER_EXPIRED_ERR, "订单已支付完成或已取消" # 订单已支付完成或已取消 if client_res.get("mcashierConfirmInfo", {}).get("errorCode", "") == "-100": return BusinessCode.JD_ORDER_EXPIRED_ERR, "订单已支付完成或已取消" # 获取微信支付参数 url = "https://api.m.jd.com/client.action" headers = { "Host": "api.m.jd.com", "charset": "UTF-8", "user-agent": self.get_user_agent(), "cache-control": "no-cache", "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "cookie": self.cookies, } uuid_ = uuid.uuid4().hex.replace("-", "") ts = int(time.time() * 1000) ep = gen_cipher_ep(uuid_, ts) pay_sign = get_pay_sign(order_id=order_id, face_price=self.face_price) sdk_token = "jdd01XYE6SS5ZXXG7F74OZC3PKG5LWF7W3GTVLTSUH56A2YAUXOT6NEEX4TRT3I3XDDYO7NQSIV4BSK3XGKWMROTBRKO4ENETDD4IEHFWZHA01234567" body = ( '{"appId":"jd_android_app4","backUrl":"","client":"android","orderId":"%s","orderPrice":"%s","orderType":"37","orderTypeCode":"0","origin":"native","payId":"%s","paySign":"%s","sdkToken":"%s","source":"jdapp"}' % (order_id, self.face_price, pay_id, pay_sign, sdk_token) ) func = "platWXPay" version = "11.0.0" formatted_params = get_sign(func, body, uuid_, version) params = { "functionId": "platWXPay", "clientVersion": "11.0.0", # "build": "97235", "client": "android", "partner": "huawei", # "eid": "eidAd2c08121b3s9eOeub59TR2G1LS3GtRpmFHjnr/5tBkXKUdbZOahaF5ejedAIDQFfwVV2GDYUzftxnEQRjdhD5bpPNR4B0qK9tPJ 5liDjiHAhDR3", "sdkVersion": "28", "lang": "zh_CN", "harmonyOs": "0", "networkType": "wifi", # "uts": "0f31TVRjBSsqndu4/jgUPz6uymy50MQJVJk5AARu8sQVESbyLWRZZNMf5XbN+023PgE2PL4I0aLSvISDbN3u1a1oIB1KTvzrqacX+46wkkSjXxvvYKogR9YCbfnMf2pdC5H/VDcJc2u6uHTxPVwvhLoJ8vX8cN45ZijAbikou9B5o2KTvMTzCfrSYgi3+mls/cA+6k+Ao5sFIIdtimZ6bw==", "uemps": "0-0", "ext": '{"prstate":"0","pvcStu":"1"}', "ef": "1", "ep": ep, "st": formatted_params["st"], "sign": formatted_params["sign"], "sv": formatted_params["sv"], } data = { "body": body, } pay_channel_res = None for i in range(3): try: proxy = self.__proxy_pool.get_proxy(order_id=self.order_num) if proxy: response = requests.post( url=url, params=params, headers=headers, data=data, proxies={"http": proxy, "https": proxy}, timeout=2, ) else: response = requests.post( url=url, params=params, headers=headers, data=data, timeout=2 ) pay_channel_res = response.json() logger.info( f"订单id:{self.order_num},获取微信支付参数返回:{pay_channel_res}" ) break except Exception as e: logger.error(traceback.format_exc()) continue if not pay_channel_res: return BusinessCode.JD_ORDER_NORMAL_ERR, "爬虫异常" # 当前支付方式不可用,建议选择其他支付方式 if pay_channel_res.get("errorCode") == "-1": return BusinessCode.INTERNAL_ERROR, "爬虫异常" # 加密算法失效 if pay_channel_res.get("code") == "600": return BusinessCode.INTERNAL_ERROR, "加密算法异常" # 不支持该支付类型 if pay_channel_res.get("errorCode") == "-2": return BusinessCode.JD_ORDER_TYPE_NOT_SUPPORTED_ERR, "订单类型不支持" # ck失效 if pay_channel_res.get("errorCode") == "3": return BusinessCode.JD_ORDER_CK_ERR, "ck失效" # 订单失效 if pay_channel_res.get("errorCode") == "-3": return BusinessCode.JD_ORDER_EXPIRED_ERR, "订单失效" # 订单与下单账号不同 if pay_channel_res.get("errorCode") == "-5": return BusinessCode.JD_ORDER_NORMAL_ERR, "订单与下单账号不匹配" # 订单已取消 if pay_channel_res.get("errorCode") == "-100": return BusinessCode.JD_ORDER_EXPIRED_ERR, "订单已支付完成或已取消" # 订单已支付完成或已取消 if ( pay_channel_res.get("mcashierConfirmInfo", {}).get("errorCode", "") == "-100" ): return BusinessCode.JD_ORDER_EXPIRED_ERR, "订单已支付完成或已取消" # 获取数据 pay_info = pay_channel_res["payInfo"] # url转码 wx_pay_info: str = ( f"weixin://app/wxe75a2e68877315fb/pay/?nonceStr={pay_info['nonceStr']}&package={parse.quote(pay_info['package'])}&partnerId={pay_info['partnerId']}&prepayId={pay_info['prepayId']}&timeStamp={pay_info['timeStamp']}&sign={pay_info['sign']}×tamp={pay_info['timeStamp']}" ) logger.info(f"获取微信app端支付参数返回:{wx_pay_info}") return BusinessCode.SUCCESS, wx_pay_info