// background/steps/fill-stripe-checkout.js — Step 7: Fill Stripe checkout form (function attachBackgroundStep12(root, factory) { root.MultiPageBackgroundStep12 = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep12Module() { const CHECKOUT_STRIPE_SOURCE = 'checkout-stripe'; const CHECKOUT_STRIPE_INJECT_FILES = ['content/activation-utils.js', 'content/utils.js', 'content/checkout-stripe.js']; const CHECKOUT_URL_PATTERNS = [ 'https://chatgpt.com/checkout/*', 'https://pay.openai.com/*', 'https://checkout.stripe.com/*', ]; const SUBMIT_MAX_ATTEMPTS = 3; const PAYPAL_REDIRECT_TIMEOUT_MS = 20000; function createStep12Executor(deps = {}) { const { addLog, chrome, completeStepFromBackground, fetchCheckoutAddress, getTabId, sendToContentScriptResilient, } = deps; async function executeStep12(state) { await addLog('步骤 7:正在填写 Stripe 结账表单...'); // Fetch a random US address const addr = await fetchCheckoutAddress(); await addLog(`已获取美国地址: ${addr.street}, ${addr.city}, ${addr.state} ${addr.zip}`); if (!chrome?.tabs?.sendMessage || !chrome?.webNavigation?.getAllFrames || !chrome?.scripting?.executeScript) { await addLog('步骤 7:当前环境不支持 checkout iframe 编排,回退到旧版主 frame 填写方式。', 'warn'); await sendToContentScriptResilient(CHECKOUT_STRIPE_SOURCE, { type: 'EXECUTE_STEP', step: 7, source: 'background', payload: { address: addr }, }); return; } const tabId = await resolveCheckoutTabId(); if (!tabId) { throw new Error('步骤 7:未找到 Stripe/Plus Checkout 标签页,请先完成步骤 6。'); } await addLog('步骤 7:正在等待 Checkout 页面加载完成...'); await waitForTabComplete(tabId); await sleep(800); const readyFrames = await getReadyCheckoutFrames(tabId); const paymentFrame = await resolvePaymentFrame(tabId, readyFrames); if (paymentFrame.frameId === null) { throw new Error(`步骤 7:未在主页面或 iframe 中发现 PayPal 付款方式。frame 摘要:${buildFrameSummary(paymentFrame.inspections)}`); } if (paymentFrame.frameId !== 0) { await addLog(`步骤 7:PayPal 位于 checkout iframe(frameId=${paymentFrame.frameId}),将改为在该 frame 内操作。`, 'info'); } await addLog('步骤 7:正在切换 PayPal 付款方式...'); const paymentResult = await sendFrameMessage(tabId, paymentFrame.frameId, { type: 'CHECKOUT_STRIPE_SELECT_PAYPAL', source: 'background', payload: { relaxedActivation: true }, }); if (paymentResult?.error) { throw new Error(paymentResult.error); } const billingFrame = await waitForBillingOrHostedSubmitFrameAfterPaymentSelection(tabId, paymentFrame); let billingResult = { countryText: '', structuredAddress: null, }; if (billingFrame.frameId !== paymentFrame.frameId) { await addLog(`步骤 7:账单地址位于 checkout iframe(frameId=${billingFrame.frameId}),将改为在该 frame 内填写。`, 'info'); } await addLog( billingFrame.autoJsDirectSelectors ? '步骤 7:未发现独立账单地址 iframe,参考 auto.js 在当前 checkout frame 内直接填写固定账单字段...' : '步骤 7:正在填写账单地址...' ); billingResult = await sendFrameMessage(tabId, billingFrame.frameId, { type: 'CHECKOUT_STRIPE_FILL_BILLING_ADDRESS', source: 'background', payload: { address: addr, autoJsDirectSelectors: Boolean(billingFrame.autoJsDirectSelectors), }, }); if (billingResult?.error) { throw new Error(billingResult.error); } let redirectedToPayPal = false; let lastSubmitError = ''; for (let attempt = 1; attempt <= SUBMIT_MAX_ATTEMPTS; attempt += 1) { await addLog( attempt === 1 ? (billingFrame.autoJsDirectSelectors ? '步骤 7:账单地址已按 auto.js 方式处理,等待 1.5 秒后提交...' : '步骤 7:账单地址已填写完成,等待 3 秒让 checkout 完成校验...') : `步骤 7:准备第 ${attempt}/${SUBMIT_MAX_ATTEMPTS} 次重新提交...`, attempt === 1 ? 'info' : 'warn' ); await sleep(billingFrame.autoJsDirectSelectors ? 1500 : 3000); const submitFrame = await waitForSubmitFrame(tabId, [ { frameId: 0, url: '' }, { frameId: paymentFrame.frameId, url: paymentFrame.frameUrl || '' }, { frameId: billingFrame.frameId, url: billingFrame.frameUrl || '' }, ]); const submitResult = await sendFrameMessage(tabId, submitFrame.frameId, { type: 'CHECKOUT_STRIPE_CLICK_SUBMIT', source: 'background', payload: { beforeClickDelayMs: attempt === 1 ? 700 : 1200, }, }); if (submitResult?.error) { lastSubmitError = submitResult.error; await addLog(`步骤 7:点击订阅失败(${attempt}/${SUBMIT_MAX_ATTEMPTS}):${lastSubmitError}`, 'warn'); continue; } await addLog(`步骤 7:已提交 checkout,正在等待跳转到 PayPal(${attempt}/${SUBMIT_MAX_ATTEMPTS})...`, 'info'); redirectedToPayPal = await waitForPayPalRedirectAfterSubmit(tabId); if (redirectedToPayPal) break; lastSubmitError = `提交后 ${Math.round(PAYPAL_REDIRECT_TIMEOUT_MS / 1000)} 秒内未跳转到 PayPal`; await addLog(`步骤 7:${lastSubmitError},将重试提交。`, 'warn'); } if (!redirectedToPayPal) { throw new Error(`步骤 7:多次提交 checkout 后仍未跳转到 PayPal。${lastSubmitError}`); } if (typeof completeStepFromBackground === 'function') { await completeStepFromBackground(7, { plusBillingCountryText: billingResult?.countryText || '', plusBillingAddress: billingResult?.structuredAddress || null, }); } } async function resolveCheckoutTabId() { const registered = await getTabId(CHECKOUT_STRIPE_SOURCE); if (registered && await isCheckoutTab(registered)) { return registered; } const signupTab = await getTabId('signup-page'); if (signupTab && await isCheckoutTab(signupTab)) { return signupTab; } const tabs = await chrome.tabs.query({ url: CHECKOUT_URL_PATTERNS }).catch(() => []); const tab = tabs.find((item) => Number.isInteger(item?.id) && isCheckoutUrl(item.url)); return tab?.id || null; } async function isCheckoutTab(tabId) { const tab = await chrome.tabs.get(tabId).catch(() => null); return Boolean(tab && isCheckoutUrl(tab.url)); } function isCheckoutUrl(url = '') { return /^https:\/\/(?:chatgpt\.com\/checkout|pay\.openai\.com|checkout\.stripe\.com)(?:\/|$)/i.test(String(url || '')); } async function waitForTabComplete(tabId, timeoutMs = 30000) { const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { const tab = await chrome.tabs.get(tabId).catch(() => null); if (!tab) throw new Error('步骤 7:Checkout 标签页已关闭。'); if (tab.status === 'complete') return tab; await sleep(300); } return chrome.tabs.get(tabId).catch(() => null); } async function getCheckoutFrames(tabId) { const frames = await chrome.webNavigation.getAllFrames({ tabId }).catch(() => null); if (!Array.isArray(frames) || !frames.length) { return [{ frameId: 0, url: '' }]; } return frames .filter((frame) => Number.isInteger(frame?.frameId)) .sort((left, right) => Number(left.frameId) - Number(right.frameId)); } async function pingCheckoutFrame(tabId, frameId) { try { const pong = await chrome.tabs.sendMessage(tabId, { type: 'PING', source: 'background', payload: {}, }, { frameId: Number.isInteger(frameId) ? frameId : 0, }); return Boolean(pong?.ok && (!pong.source || pong.source === CHECKOUT_STRIPE_SOURCE)); } catch { return false; } } async function ensureCheckoutFrameReady(tabId, frameId) { if (await pingCheckoutFrame(tabId, frameId)) { return true; } try { await chrome.scripting.executeScript({ target: { tabId, frameIds: [frameId] }, func: (injectedSource) => { window.__MULTIPAGE_SOURCE = injectedSource; }, args: [CHECKOUT_STRIPE_SOURCE], }); await chrome.scripting.executeScript({ target: { tabId, frameIds: [frameId] }, files: CHECKOUT_STRIPE_INJECT_FILES, }); } catch { // Some Stripe helper frames are intentionally not scriptable. They are skipped below. } await sleep(500); return pingCheckoutFrame(tabId, frameId); } async function getReadyCheckoutFrames(tabId) { const frames = await getCheckoutFrames(tabId); const readyFrames = []; for (const frame of frames) { const ready = await ensureCheckoutFrameReady(tabId, frame.frameId); readyFrames.push({ ...frame, ready }); } return readyFrames; } async function sendFrameMessage(tabId, frameId, message) { return chrome.tabs.sendMessage(tabId, message, { frameId: Number.isInteger(frameId) ? frameId : 0, }); } async function inspectCheckoutFrame(tabId, frame) { if (frame.ready === false) { return { frame, error: 'content-script-not-ready' }; } try { const result = await sendFrameMessage(tabId, frame.frameId, { type: 'CHECKOUT_STRIPE_GET_STATE', source: 'background', payload: {}, }); return { frame, result: result || {} }; } catch (error) { return { frame, error: error?.message || String(error || '') }; } } async function inspectCheckoutFrames(tabId, frames) { const inspections = []; for (const frame of frames) { inspections.push(await inspectCheckoutFrame(tabId, frame)); } return inspections; } async function resolvePaymentFrame(tabId, frames) { const inspections = await inspectCheckoutFrames(tabId, frames); const picked = pickPaymentFrame(inspections); if (!picked) { return { frameId: null, frameUrl: '', inspections }; } return { frameId: picked.frame.frameId, frameUrl: picked.frame.url || '', inspections, }; } function isPaymentFrameUrl(url = '') { return /elements-inner-payment|componentName=payment/i.test(String(url || '')); } function isAddressFrameUrl(url = '') { return /elements-inner-address|componentName=address/i.test(String(url || '')); } function pickPaymentFrame(inspections = []) { return inspections.find((item) => item.result?.hasPayPal || item.result?.paypalCandidates?.length) || inspections.find((item) => isPaymentFrameUrl(item.frame.url)) || null; } function pickBillingFrame(inspections = []) { return inspections.find((item) => item.result?.billingFieldsVisible) || inspections.find((item) => isAddressFrameUrl(item.frame.url)) || null; } function pickHostedPayPalSubmitFrame(inspections = []) { return inspections.find((item) => item.result?.hasPayPal && item.result?.hasSubmitButton) || null; } async function getHostedPayPalSubmitFrame(tabId) { const frames = await getReadyCheckoutFrames(tabId); const inspections = await inspectCheckoutFrames(tabId, frames); const picked = pickHostedPayPalSubmitFrame(inspections); if (!picked) { return null; } return { frameId: picked.frame.frameId, frameUrl: picked.frame.url || '', inspections, skipBillingAddress: true, }; } async function waitForBillingOrHostedSubmitFrameAfterPaymentSelection(tabId, paymentFrame) { try { return await waitForBillingFrame(tabId, 6500); } catch (firstError) { const hostedSubmitFrame = await getHostedPayPalSubmitFrame(tabId); if (hostedSubmitFrame) { await addLog(`步骤 7:未等到账单地址 iframe,但当前 frame 已有 PayPal 与提交按钮,将参考 auto.js 直接填写当前页面字段。${firstError.message}`, 'warn'); return hostedSubmitFrame; } await addLog(`步骤 7:首次等待账单地址未出现,准备按 auto.js 方式重新点击 PayPal。${firstError.message}`, 'warn'); } const latestFrames = await getReadyCheckoutFrames(tabId); const latestPaymentFrame = await resolvePaymentFrame(tabId, latestFrames); const retryFrameId = latestPaymentFrame.frameId ?? paymentFrame.frameId; if (Number.isInteger(retryFrameId)) { const retryResult = await sendFrameMessage(tabId, retryFrameId, { type: 'CHECKOUT_STRIPE_SELECT_PAYPAL', source: 'background', payload: { relaxedActivation: true }, }); if (retryResult?.error) { await addLog(`步骤 7:再次点击 PayPal 失败:${retryResult.error}`, 'warn'); } else { await addLog('步骤 7:已再次点击 PayPal,继续检测账单地址或 hosted checkout 提交区...', 'info'); } } try { return await waitForBillingFrame(tabId, 6500); } catch (secondError) { const hostedSubmitFrame = await getHostedPayPalSubmitFrame(tabId); if (hostedSubmitFrame) { await addLog(`步骤 7:二次等待账单地址仍未出现,将参考 auto.js 在当前页面直接填固定账单字段后提交。${secondError.message}`, 'warn'); return hostedSubmitFrame; } throw secondError; } } async function waitForBillingFrame(tabId, timeoutMs = 30000) { const startedAt = Date.now(); let lastInspections = []; while (Date.now() - startedAt < timeoutMs) { const frames = await getReadyCheckoutFrames(tabId); const inspections = await inspectCheckoutFrames(tabId, frames); lastInspections = inspections; const picked = pickBillingFrame(inspections); if (picked) { return { frameId: picked.frame.frameId, frameUrl: picked.frame.url || '', inspections, }; } await sleep(250); } throw new Error(`步骤 7:等待账单地址 iframe 超时。frame 摘要:${buildFrameSummary(lastInspections) || '无可检测 frame'}`); } async function waitForSubmitFrame(tabId, candidateFrames = [], timeoutMs = 15000) { const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { const frameMap = new Map(); for (const item of candidateFrames) { if (Number.isInteger(item?.frameId)) frameMap.set(item.frameId, item); } const readyFrames = await getReadyCheckoutFrames(tabId); for (const frame of readyFrames) { if (!frameMap.has(frame.frameId)) frameMap.set(frame.frameId, frame); } const inspections = await inspectCheckoutFrames(tabId, Array.from(frameMap.values())); const picked = inspections.find((item) => item.result?.hasSubmitButton); if (picked) { return picked.frame; } await sleep(250); } throw new Error('步骤 7:等待订阅按钮超时。'); } async function waitForPayPalRedirectAfterSubmit(tabId) { const startedAt = Date.now(); while (Date.now() - startedAt < PAYPAL_REDIRECT_TIMEOUT_MS) { const tab = await chrome.tabs.get(tabId).catch(() => null); if (!tab) throw new Error('步骤 7:Checkout 标签页已关闭,无法继续等待 PayPal 跳转。'); const url = String(tab.url || ''); if (/paypal\./i.test(url) && !isCheckoutUrl(url)) { await waitForTabComplete(tabId, 15000); await sleep(1000); return true; } if (url && !isCheckoutUrl(url)) { await addLog(`步骤 7:点击订阅后页面跳转到非 PayPal 识别地址:${url}`, 'warn'); return false; } await sleep(500); } return false; } function buildFrameSummary(inspections = []) { return inspections .map((item) => { const flags = []; if (item.result?.hasPayPal) flags.push('paypal'); if (item.result?.billingFieldsVisible) flags.push('billing'); if (item.result?.hasSubmitButton) flags.push('submit'); if (!flags.length && item.error) flags.push(item.error); if (!flags.length) flags.push('no-match'); return `${item.frame.frameId}:${item.frame.url || 'about:blank'}:${flags.join(',')}`; }) .slice(0, 8) .join(' | '); } return { executeStep12 }; } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } return { createStep12Executor }; });