- 新增jd模块基础路由,整合app_store和payment子路由 - 实现苹果权益充值接口,支持苹果、携程及沃尔玛多个渠道 - 实现卡号密码查询接口,支持不同类别订单查询 - 新增短信认证相关接口,实现短信验证码发送及短信登录 - 新增商品管理接口,支持SKU详情查询及账号类下单功能 - 新增订单管理接口,实现订单删除功能 - 实现支付相关接口,增加刷新支付参数功能 - 定义完整请求及响应数据模型,确保接口数据规范 - 编写AppStoreSpider类,封装苹果应用内订单处理逻辑 - 引入多种代理池及请求重试机制,增强接口稳定性 - 添加详细日志记录,便于请求追踪与错误排查
774 lines
35 KiB
Python
774 lines
35 KiB
Python
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
|