Files
autoPlus/core/checkout.py
2026-01-29 22:41:16 +08:00

285 lines
8.3 KiB
Python
Raw Permalink 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
"""
ChatGPT Plus 支付账单获取模块
功能:
- 使用 access_token 自动获取 Stripe 支付会话
- 返回 checkout_session_id 和 client_secret 用于后续支付
使用方法:
from core.checkout import CheckoutFlow
session = OAISession(proxy=proxy)
await session.login(email, password)
checkout = CheckoutFlow(session)
result = await checkout.create_checkout_session()
"""
from typing import Dict, Any, Optional
from utils.logger import logger
class CheckoutFlow:
"""
ChatGPT Plus 支付结账流程
通过 /backend-api/payments/checkout 接口获取 Stripe 支付会话
"""
# API 端点
CHECKOUT_URL = "https://chatgpt.com/backend-api/payments/checkout"
# 支持的订阅计划
PLAN_PLUS = "chatgptplusplan"
PLAN_PRO = "chatgptproplan"
PLAN_TEAM = "chatgptteamplan"
def __init__(
self,
session,
plan_name: str = "chatgptplusplan",
country: str = "US",
currency: str = "USD"
):
"""
初始化结账流程
参数:
session: OAISession 实例(需已登录)
plan_name: 订阅计划名称
- chatgptplusplan: ChatGPT Plus ($20/月)
- chatgptproplan: ChatGPT Pro ($200/月)
country: 账单国家代码
currency: 货币代码
"""
self.session = session
self.plan_name = plan_name
self.country = country
self.currency = currency
# 验证登录状态
if not session.is_logged_in():
logger.warning("Session not logged in - checkout may fail")
async def create_checkout_session(
self,
checkout_ui_mode: str = "redirect"
) -> Dict[str, Any]:
"""
创建 Stripe 结账会话
参数:
checkout_ui_mode: 结账UI模式
- "redirect": 重定向到 Stripe 托管页面
- "custom": 使用自定义嵌入式表单
返回:
成功时返回:
{
"status": "success",
"checkout_session_id": "cs_live_xxx",
"client_secret": "cs_live_xxx_secret_xxx",
"publishable_key": "pk_live_xxx",
"plan_name": "chatgptplusplan",
"payment_status": "unpaid",
"url": "https://checkout.stripe.com/..." (如果 ui_mode=redirect)
}
失败时返回:
{
"status": "failed",
"error": "错误信息",
"status_code": HTTP状态码
}
"""
logger.info(f"Creating checkout session for plan: {self.plan_name}")
# 构建请求体
payload = {
"plan_name": self.plan_name,
"billing_details": {
"country": self.country,
"currency": self.currency
},
"promo_campaign": {
"promo_campaign_id": "plus-1-month-free",
"is_coupon_from_query_param": False
},
"checkout_ui_mode": checkout_ui_mode
}
# 构建请求头
headers = self._build_headers()
try:
response = self.session.post(
self.CHECKOUT_URL,
json=payload,
headers=headers
)
return self._parse_response(response)
except Exception as e:
logger.error(f"Checkout request failed: {e}")
return {
"status": "failed",
"error": str(e),
"message": f"Request exception: {type(e).__name__}"
}
def _build_headers(self) -> Dict[str, str]:
"""
构建结账请求头
包含必要的 Authorization 和 OpenAI 特定头
"""
headers = {
"Content-Type": "application/json",
"Accept": "*/*",
"Origin": "https://chatgpt.com",
"Referer": "https://chatgpt.com/",
"Oai-Language": "en-US",
"Oai-Device-Id": self.session.oai_did,
}
# 添加 Authorization 头Bearer token
if self.session.access_token:
headers["Authorization"] = f"Bearer {self.session.access_token}"
else:
logger.warning("No access_token available for Authorization header")
return headers
def _parse_response(self, response) -> Dict[str, Any]:
"""
解析结账响应
参数:
response: HTTP 响应对象
返回:
解析后的结果字典
"""
status_code = response.status_code
if status_code == 200:
try:
data = response.json()
result = {
"status": "success",
"tag": data.get("tag"),
"checkout_session_id": data.get("checkout_session_id"),
"client_secret": data.get("client_secret"),
"publishable_key": data.get("publishable_key"),
"processor_entity": data.get("processor_entity"),
"checkout_ui_mode": data.get("checkout_ui_mode"),
"plan_name": data.get("plan_name"),
"payment_status": data.get("payment_status"),
"url": data.get("url"),
"automatic_tax_enabled": data.get("automatic_tax_enabled"),
"requires_manual_approval": data.get("requires_manual_approval"),
}
logger.success(f"Checkout session created: {result['checkout_session_id'][:30]}...")
return result
except Exception as e:
logger.error(f"Failed to parse checkout response: {e}")
return {
"status": "failed",
"error": f"JSON parse error: {e}",
"raw_response": response.text[:500]
}
elif status_code == 401:
logger.error("Unauthorized - invalid or expired token")
return {
"status": "failed",
"error": "Unauthorized - token invalid or expired",
"status_code": 401
}
elif status_code == 403:
logger.error("Forbidden - possible Cloudflare block or permission issue")
return {
"status": "failed",
"error": "Forbidden - access denied",
"status_code": 403,
"raw_response": response.text[:500]
}
elif status_code == 400:
logger.error(f"Bad request: {response.text[:200]}")
return {
"status": "failed",
"error": "Bad request - invalid parameters",
"status_code": 400,
"raw_response": response.text[:500]
}
else:
logger.error(f"Unexpected status {status_code}: {response.text[:200]}")
return {
"status": "failed",
"error": f"HTTP {status_code}",
"status_code": status_code,
"raw_response": response.text[:500]
}
async def get_checkout_for_account(
email: str,
password: str,
proxy: str = None,
plan_name: str = "chatgptplusplan"
) -> Dict[str, Any]:
"""
便捷函数:为指定账号获取支付链接
参数:
email: 账号邮箱
password: 账号密码
proxy: 代理地址(可选)
plan_name: 订阅计划
返回:
包含 checkout 信息的字典
"""
from core.session import OAISession
session = None
try:
session = OAISession(proxy=proxy, impersonate="chrome124")
# 登录
logger.info(f"Logging in as {email}...")
login_result = await session.login(email, password)
if login_result.get("status") != "success":
return {
"status": "failed",
"error": f"Login failed: {login_result.get('error')}",
"email": email
}
# 获取 checkout
checkout = CheckoutFlow(session, plan_name=plan_name)
result = await checkout.create_checkout_session()
result["email"] = email
return result
finally:
if session:
session.close()
# 导出
__all__ = [
"CheckoutFlow",
"get_checkout_for_account",
]