Files
kami_spider_monorepo/apps/jd/services/app_store.py
danial 6c768b6e7b feat(jd): 添加京东相关路由及苹果权益充值功能
- 新增jd模块基础路由,整合app_store和payment子路由
- 实现苹果权益充值接口,支持苹果、携程及沃尔玛多个渠道
- 实现卡号密码查询接口,支持不同类别订单查询
- 新增短信认证相关接口,实现短信验证码发送及短信登录
- 新增商品管理接口,支持SKU详情查询及账号类下单功能
- 新增订单管理接口,实现订单删除功能
- 实现支付相关接口,增加刷新支付参数功能
- 定义完整请求及响应数据模型,确保接口数据规范
- 编写AppStoreSpider类,封装苹果应用内订单处理逻辑
- 引入多种代理池及请求重试机制,增强接口稳定性
- 添加详细日志记录,便于请求追踪与错误排查
2025-11-03 19:35:39 +08:00

774 lines
35 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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']}&timestamp={pay_info['timeStamp']}"
)
logger.info(f"获取微信app端支付参数返回{wx_pay_info}")
return BusinessCode.SUCCESS, wx_pay_info