| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- // 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 };
- });
|