// background/steps/get-plus-link.js — Step 6: Get ChatGPT Plus Stripe checkout link (function attachBackgroundStep11(root, factory) { root.MultiPageBackgroundStep11 = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep11Module() { const PAYURL_CHECKOUT_ENDPOINT = 'https://payurl.ark2.cn/api/checkout'; const PAYURL_CHECKOUT_MAX_ATTEMPTS = 5; const PAYURL_CHECKOUT_RETRY_DELAY_MS = 1200; function createStep11Executor(deps = {}) { const { addLog, chrome, completeStepFromBackground, getTabId, reuseOrCreateTab, waitForTabStableComplete = null, } = deps; async function executeStep11() { await addLog('步骤 6:正在获取 ChatGPT Plus 订阅链接...'); const tabId = await getTabId('signup-page'); if (!tabId) { throw new Error('未找到 ChatGPT 标签页,请先打开 chatgpt.com'); } await waitForChatGptLoginPageStable(tabId, { addLog, waitForTabStableComplete, }); const accessToken = await getChatGptAccessToken(tabId, chrome, addLog); if (!accessToken) { throw new Error('未获取到 accessToken,请确认已登录 ChatGPT'); } await addLog('已获取 ChatGPT Session Token,正在请求 Plus 长链...'); const result = await requestPayurlCheckoutWithRetry(accessToken, addLog); if (!result.hostedUrl) { throw new Error('未从 payurl.ark2.cn 响应中解析到 Plus 长链'); } await addLog(`已获取 Plus 链接,正在跳转到 Stripe 结账页面...`); await addLog(`Session ID: ${result.checkoutSessionId || '未知'}`); // Navigate to the Stripe checkout URL await reuseOrCreateTab('signup-page', result.hostedUrl, { reloadIfSameUrl: false, }); await completeStepFromBackground(6, { checkoutUrl: result.hostedUrl, checkoutSessionId: result.checkoutSessionId, chatgptCheckoutUrl: result.chatgptCheckoutUrl, openaiPayUrl: result.openaiPayUrl, }); } return { executeStep11 }; } async function waitForChatGptLoginPageStable(tabId, deps = {}) { const { addLog, waitForTabStableComplete, } = deps; if (typeof waitForTabStableComplete !== 'function') { return null; } await addLog('步骤 6:正在等待步骤 5 登录后的 ChatGPT 页面稳定,再读取 Session...', 'info'); const startedAt = Date.now(); let lastTab = null; while (Date.now() - startedAt < 45000) { lastTab = await waitForTabStableComplete(tabId, { timeoutMs: 6000, retryDelayMs: 300, stableMs: 1500, initialDelayMs: 600, }); if (!lastTab) { throw new Error('步骤 6:ChatGPT 标签页已关闭,无法获取 Session。'); } const url = String(lastTab.url || ''); if (isChatGptWebUrl(url)) { await addLog('步骤 6:ChatGPT 页面已稳定,开始读取 Session。', 'info'); return lastTab; } await sleep(500); } const lastUrl = String(lastTab?.url || ''); await addLog( `步骤 6:等待 ChatGPT 页面稳定超时,最后页面为 ${lastUrl || 'unknown'},将继续尝试读取 Session。`, 'warn' ); return lastTab; } function isChatGptWebUrl(rawUrl = '') { try { const parsed = new URL(String(rawUrl || '')); return /(?:^|\.)chatgpt\.com$/i.test(parsed.hostname); } catch { return false; } } async function getChatGptAccessToken(tabId, chrome, addLog) { try { return await fetchAccessTokenFromSessionEndpoint(); } catch (error) { await addLog(`后台获取 ChatGPT Session Token 失败,尝试在页面上下文重试:${error.message}`, 'warn'); } const results = await chrome.scripting.executeScript({ target: { tabId }, func: fetchAccessTokenInPage, }); const tokenResult = results?.[0]?.result; if (!tokenResult || tokenResult.error) { throw new Error(tokenResult?.error || '获取 ChatGPT Session Token 失败,请确认已登录 ChatGPT'); } return String(tokenResult.accessToken || '').trim(); } async function fetchAccessTokenFromSessionEndpoint() { const sessionResp = await fetch('https://chatgpt.com/api/auth/session', { credentials: 'include', headers: { 'Accept': 'application/json', }, }); const text = await sessionResp.text(); let session = null; try { session = text ? JSON.parse(text) : null; } catch (error) { throw new Error(`Session 响应不是有效 JSON:${text.slice(0, 300)}`); } if (!sessionResp.ok) { throw new Error(`Session 请求失败,HTTP ${sessionResp.status}: ${JSON.stringify(session)}`); } const accessToken = String(session?.accessToken || '').trim(); if (!accessToken) { throw new Error('Session 响应中没有 accessToken'); } return accessToken; } /** * This function is executed in the page context via chrome.scripting.executeScript. * It has access to the page's cookies and can make same-origin requests. */ async function fetchAccessTokenInPage() { try { const sessionResp = await fetch('https://chatgpt.com/api/auth/session', { credentials: 'include', }); const session = await sessionResp.json(); const accessToken = session?.accessToken; if (!accessToken) { return { error: '未获取到 accessToken,请确认已登录 ChatGPT' }; } return { accessToken }; } catch (e) { return { error: e.message }; } } async function requestPayurlCheckoutWithRetry(accessToken, addLog) { let lastError = null; for (let attempt = 1; attempt <= PAYURL_CHECKOUT_MAX_ATTEMPTS; attempt += 1) { try { if (attempt > 1) { await addLog(`Plus 长链获取失败,正在重试 ${attempt}/${PAYURL_CHECKOUT_MAX_ATTEMPTS}...`, 'warn'); await sleep(PAYURL_CHECKOUT_RETRY_DELAY_MS); } return await requestPayurlCheckout(accessToken); } catch (error) { lastError = error; await addLog(`Plus 长链获取失败(${attempt}/${PAYURL_CHECKOUT_MAX_ATTEMPTS}):${error.message}`, 'warn'); } } throw new Error(`Plus 长链获取连续失败 ${PAYURL_CHECKOUT_MAX_ATTEMPTS} 次:${lastError?.message || '未知错误'}`); } async function requestPayurlCheckout(accessToken) { const payload = { token: accessToken, plan: 'plus', checkout_ui_mode: 'hosted', ui_language: 'en', country: 'US', currency: 'USD', proxy: '', use_promo: true, promo_code: 'STRIPEATLASGPT4BIZ050126', workspace_name: 'linux-do', seat_quantity: 2, }; const response = await fetch(PAYURL_CHECKOUT_ENDPOINT, { method: 'POST', headers: { 'Accept': '*/*', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Content-Type': 'application/json', 'DNT': '1', 'Origin': 'https://payurl.ark2.cn', 'Referer': 'https://payurl.ark2.cn/', }, body: JSON.stringify(payload), }); const responseText = await response.text(); let data = null; try { data = responseText ? JSON.parse(responseText) : null; } catch (error) { throw new Error(`payurl 响应不是有效 JSON:${responseText.slice(0, 300)}`); } if (!response.ok) { throw new Error(`payurl 请求失败,HTTP ${response.status}: ${JSON.stringify(data)}`); } const hostedUrl = data?.url || data?.openai_payurl || data?.chatgpt_checkout_url || ''; if (!hostedUrl) { throw new Error(`payurl 响应中未找到 url/openai_payurl/chatgpt_checkout_url:${JSON.stringify(data)}`); } return { hostedUrl, checkoutSessionId: data.checkout_session_id || '', chatgptCheckoutUrl: data.chatgpt_checkout_url || '', openaiPayUrl: data.openai_payurl || data.url || '', }; } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } return { createStep11Executor }; });