Files
rnpay/logs/paytm_business/paytm_business_api.py
2026-03-11 22:48:39 +08:00

315 lines
12 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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Paytm Business 登录 API
支持OTP 登录、Token 保存/读取、获取 QR 数据、查询流水
"""
import requests
import json
import os
from datetime import datetime, timedelta
TOKEN_FILE = ".paytm_business_token.json"
class PaytmBusinessAPI:
def __init__(self):
self.base_url = "https://accounts.paytm.com"
self.dashboard_url = "https://dashboard.paytm.com"
self.token = "cDRiLW13ZWItcHJvZHVjdGlvbjplTU01VWpXaW9wNnNFVGwzcDBpcGRvZ0hJdENtTXNibA=="
self.client_id = "p4b-mweb-production"
self.session = requests.Session()
self.csrf_token = None
self.state = None
self.session_cookie = None
self.xsrf_token = None
self.qr_data = []
def _get_headers(self):
"""通用请求头"""
return {
"Authorization": f"Basic {self.token}",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (Linux; Android 12; M2004J7AC Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/143.0.7499.192 Mobile Safari/537.36",
"Accept": "*/*",
"Origin": "https://accounts.paytm.com",
"Referer": "https://accounts.paytm.com/oauth-js-sdk/index.html",
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"sec-ch-ua": '"Android WebView";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
"sec-ch-ua-mobile": "?1",
"sec-ch-ua-platform": '"Android"',
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
}
def save_token(self):
"""保存 token 到文件"""
if not self.session_cookie or not self.xsrf_token:
return
data = {
"session_cookie": self.session_cookie,
"xsrf_token": self.xsrf_token,
"qr_data": self.qr_data,
"saved_at": datetime.now().isoformat()
}
with open(TOKEN_FILE, 'w') as f:
json.dump(data, f, indent=2)
print(f"✓ Token 已保存到 {TOKEN_FILE}")
def load_token(self):
"""从文件加载 token"""
if not os.path.exists(TOKEN_FILE):
print(f"Token 文件不存在: {TOKEN_FILE}")
return False
try:
with open(TOKEN_FILE, 'r') as f:
data = json.load(f)
self.session_cookie = data.get("session_cookie")
self.xsrf_token = data.get("xsrf_token")
self.qr_data = data.get("qr_data", [])
print(f"✓ 已加载 Token (保存于 {data.get('saved_at')})")
# 验证 token 是否有效
if self.verify_token():
print("✓ Token 有效")
return True
else:
print("✗ Token 已失效")
return False
except Exception as e:
print(f"✗ 加载 Token 失败: {e}")
return False
def _get_dashboard_headers(self):
"""Dashboard 请求头"""
cookie_header = self.session_cookie
if not cookie_header.startswith("SESSION="):
cookie_header = "SESSION=" + cookie_header
return {
"Cookie": cookie_header,
"X-XSRF-TOKEN": self.xsrf_token,
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36",
"Origin": "https://dashboard.paytm.com",
"Referer": "https://dashboard.paytm.com/next/transactions",
"x-ump-version": "bpay-v2.26.2-12188-g89732e0ebb",
}
def verify_token(self):
"""验证 token 是否有效"""
try:
url = f"{self.dashboard_url}/api/v1/context"
resp = requests.get(url, headers=self._get_dashboard_headers(), timeout=10)
return resp.status_code == 200
except:
return False
def init(self):
"""初始化,获取 csrfToken"""
url = f"{self.base_url}/um/authorize/init"
data = {
"clientId": self.client_id,
"responseType": "code",
"scope": "paytm",
"redirectUri": "https://dashboard.paytm.com/auth"
}
resp = self.session.post(url, headers=self._get_headers(), json=data)
result = resp.json()
if result.get("status") == "SUCCESS":
self.csrf_token = result["data"]["authState"]
print(f"✓ 初始化成功csrfToken: {self.csrf_token}")
return self.csrf_token
else:
raise Exception(f"初始化失败: {result}")
def request_otp(self, mobile, password):
"""请求 OTP"""
if not self.csrf_token:
self.init()
url = f"{self.base_url}/um/authorize/proceed"
data = {
"userName": mobile,
"password": password,
"clientId": self.client_id,
"csrfToken": self.csrf_token
}
resp = self.session.post(url, headers=self._get_headers(), json=data)
result = resp.json()
if result.get("status") == "SUCCESS":
self.state = result.get("stateCode")
print(f"✓ OTP 已发送到 {mobile}")
return result
else:
raise Exception(f"请求 OTP 失败: {result.get('message')}")
def verify_otp(self, otp):
"""验证 OTP并跟进 redirect 获取 SESSION cookie"""
if not self.csrf_token or not self.state:
raise Exception("请先请求 OTP")
url = f"{self.base_url}/login/validate/otp"
data = {
"otp": otp,
"state": self.state,
"csrfToken": self.csrf_token
}
resp = self.session.post(url, headers=self._get_headers(), json=data)
try:
result = resp.json()
except:
raise Exception(f"响应格式错误: {resp.text[:200]}")
redirect_uri = result.get("redirectUri")
if not redirect_uri:
raise Exception(f"OTP 验证失败: {result.get('message')} (code: {result.get('responseCode')})")
print(f"✓ OTP 验证成功,跟进 redirect...")
# 跟进 redirect 到 dashboard获取 SESSION 和 XSRF-TOKEN
dashboard_headers = {
"User-Agent": "Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
}
auth_resp = self.session.get(redirect_uri, headers=dashboard_headers, allow_redirects=True)
print(f"✓ Dashboard 响应: {auth_resp.status_code} ({auth_resp.url})")
for cookie in self.session.cookies:
if cookie.name == "SESSION":
self.session_cookie = cookie.value
elif cookie.name == "XSRF-TOKEN":
self.xsrf_token = cookie.value
if not self.session_cookie or not self.xsrf_token:
raise Exception(f"未能获取 SESSION/XSRF-TOKENcookies: {dict(self.session.cookies)}")
print(f"✓ SESSION: {self.session_cookie[:30]}...")
print(f"✓ XSRF-TOKEN: {self.xsrf_token[:30]}...")
return result
def fetch_qr_data(self):
"""获取 QR 码数据"""
if not self.session_cookie or not self.xsrf_token:
raise Exception("请先登录")
url = f"{self.dashboard_url}/api/v4/qrcode/fetch/?pageNo=1&pageSize=100"
resp = requests.get(url, headers=self._get_dashboard_headers())
if resp.status_code == 401:
raise Exception("Token 已失效,需要重新登录")
result = resp.json()
if resp.status_code == 200 and result.get("response") is not None:
self.qr_data = result.get("response", [])
print(f"✓ 获取到 {len(self.qr_data)} 个 QR 码")
if self.qr_data:
print(f" VPA: {self.qr_data[0].get('vpa')}")
return self.qr_data
else:
raise Exception(f"获取 QR 数据失败: {result}")
def get_transactions(self, start_date=None, end_date=None, page_num=1, page_size=50):
"""获取交易流水"""
if not self.session_cookie or not self.xsrf_token:
raise Exception("请先登录")
if not end_date:
end_date = datetime.now()
if not start_date:
start_date = end_date - timedelta(days=1)
url = f"{self.dashboard_url}/api/v3/order/list"
data = {
"bizTypeList": ["ACQUIRING", "CASHBACK", "SPLIT_PAYMENT"],
"pageSize": page_size,
"pageNum": page_num,
"orderCreatedStartTime": start_date.strftime("%Y-%m-%d") + "T00:00:00+05:30",
"orderCreatedEndTime": end_date.strftime("%Y-%m-%d") + "T23:59:59+05:30",
"orderStatusList": ["SUCCESS"],
"isSort": True
}
resp = requests.post(url, headers=self._get_dashboard_headers(), json=data)
if resp.status_code == 401:
raise Exception("Token 已失效,需要重新登录")
result = resp.json()
result_info = result.get("resultInfo", {})
if result_info.get("resultStatus") == "F":
raise Exception(f"Paytm API error: code={result_info.get('resultCode')}, msg={result_info.get('resultMsg')}")
orders = result.get("orderList", [])
print(f"✓ 获取到 {len(orders)} 笔交易")
return orders
def main():
api = PaytmBusinessAPI()
# 示例账号
mobile = "7528905079"
password = "Kular@500"
try:
# 1. 尝试加载已保存的 token
if api.load_token():
print("\n使用已保存的 Token")
else:
print("\n需要重新登录")
# 2. 初始化
api.init()
# 3. 请求 OTP
api.request_otp(mobile, password)
# 4. 验证 OTP
otp = input("请输入收到的 OTP: ")
api.verify_otp(otp)
# 5. 获取 QR 数据
api.fetch_qr_data()
# 6. 保存 token
api.save_token()
# 7. 获取流水
print("\n查询最近1天的交易流水...")
transactions = api.get_transactions()
if transactions:
print(f"\n最新 5 笔交易:")
for i, txn in enumerate(transactions[:5], 1):
amount = float(txn.get("payMoneyAmount", {}).get("value", 0)) / 100
customer = txn.get("additionalInfo", {}).get("customerName", "Unknown")
upi = txn.get("additionalInfo", {}).get("virtualPaymentAddr", "")
order_id = txn.get("bizOrderId", "")
print(f"{i}. {txn.get('orderCreatedTime')} | ₹{amount:.2f} | {customer} ({upi}) | {order_id}")
txn_file = ".paytm_business_transactions.json"
with open(txn_file, 'w') as f:
json.dump(transactions, f, indent=2, ensure_ascii=False)
print(f"\n{len(transactions)} 笔交易已保存到 {txn_file}")
else:
print("暂无交易记录")
except Exception as e:
print(f"\n✗ 错误: {e}")
if __name__ == "__main__":
main()