// content/checkout-paypal.js — PayPal page automation for login and checkout (function attachCheckoutPaypal() { if (document.documentElement.hasAttribute('data-multipage-checkout-paypal-listener')) return; document.documentElement.setAttribute('data-multipage-checkout-paypal-listener', ''); let _cleaned = false; chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === 'EXECUTE_STEP') { resetStopState(); const payload = message.payload || {}; if (message.step === 8) { runPaypalLogin(payload).then( (result) => sendResponse(result), (err) => sendResponse({ error: err.message }) ); return true; } if (message.step === 9) { runPaypalPayment(payload).then( (result) => sendResponse(result), (err) => sendResponse({ error: err.message }) ); return true; } } }); // ========== PayPal Login (Step 8) ========== async function runPaypalLogin(payload) { try { throwIfStopped(); log('开始执行 PayPal 登录页面自动化...'); clearPaypalSession(); await sleep(2000); const email = payload.email || randEmail(); log(`正在填写邮箱: ${email}`); fillById('email', email); await sleep(1200); await clickNextButton(); log('PayPal 登录邮箱已提交'); reportComplete(8, { email }); return { ok: true }; } catch (e) { if (isStopError(e)) throw e; log('PayPal 登录流程出错: ' + e.message, 'error'); reportError(8, e.message); return { error: e.message }; } } // ========== PayPal Checkout (Step 9) ========== async function runPaypalPayment(payload) { try { throwIfStopped(); log('开始执行 PayPal 结账页面自动化...'); await sleep(2000); if (isPaypalHostedReviewPage()) { await clickHostedReviewConsent(); log('PayPal 二次确认页已提交'); reportComplete(9, { reviewSubmitted: true }); return { ok: true, reviewSubmitted: true }; } // Switch country to US const country = document.getElementById('country'); if (country && country.value !== 'US') { log('检测到国家非 US,正在切换...'); fillSelect(country, 'US'); country.dispatchEvent(new Event('change', { bubbles: true })); await sleep(3000); } else { log('国家已是 US'); } // Fill card info from payload const card = payload.card || {}; const addr = payload.address || {}; const email = payload.email || randEmail(); const password = payload.password || randPass(); const phone = payload.phone || normalizePaypalPhoneForInput(payload.paypalPhone || '+15822201173'); fillById('email', email); fillById('phone', phone); fillById('cardNumber', card.Credit_Card_Number || ''); fillById('cardExpiry', normalizeExpiry(card.Expires || '')); fillById('cardCvv', card.CVV2 || ''); fillById('password', password); fillById('firstName', 'James'); fillById('lastName', 'Smith'); fillById('billingLine1', addr.street || '123 Main St'); fillById('billingCity', addr.city || 'New York'); fillById('billingPostalCode', (addr.zip || '10001').substring(0, 5)); fillSelectById('billingState', addr.state || 'New York'); log('PayPal 表单已填充完毕'); await sleep(1200); await clickPaypalSubmit(); const postSubmit = await waitForPaypalPostSubmitDecision(payload); log('PayPal 结账表单已提交'); reportComplete(9, postSubmit); return { ok: true, ...postSubmit }; } catch (e) { if (isStopError(e)) throw e; log('PayPal 结账流程出错: ' + e.message, 'error'); reportError(9, e.message); return { error: e.message }; } } // ========== Helpers ========== function fillById(id, val) { const el = document.getElementById(id); if (el) { fillInput(el, val); } } function fillSelectById(id, text) { const el = document.getElementById(id); if (!el) return; for (let i = 0; i < el.options.length; i++) { const opt = el.options[i]; if (opt.text.toLowerCase().includes(text.toLowerCase()) || opt.value.toLowerCase().includes(text.toLowerCase())) { fillSelect(el, opt.value); return; } } } function clearPaypalSession() { if (_cleaned) return; _cleaned = true; try { localStorage.clear(); } catch (e) {} try { sessionStorage.clear(); } catch (e) {} try { const host = window.location.hostname; const domains = [host, '.' + host]; const parts = host.split('.'); for (let i = 1; i < parts.length - 1; i++) { domains.push('.' + parts.slice(i).join('.')); } const paths = ['/', window.location.pathname]; const cookies = document.cookie ? document.cookie.split(';') : []; cookies.forEach((c) => { const name = c.split('=')[0].trim(); if (!name) return; paths.forEach((p) => { domains.forEach((d) => { document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=' + p + '; domain=' + d; }); document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=' + p; }); }); log('已清理 PayPal 前端 cookie / storage'); } catch (e) { log('清理 cookie 时出错: ' + e.message, 'warn'); } } function randEmail() { const c = 'abcdefghijklmnopqrstuvwxyz0123456789'; let e = ''; for (let i = 0; i < 16; i++) e += c[Math.floor(Math.random() * c.length)]; return e + '@gmail.com'; } function randPass() { const L = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; const D = '0123456789'; const S = '!@#$%^'; const A = L + D + S; let p = L[Math.floor(Math.random() * 26)] + L[26 + Math.floor(Math.random() * 26)] + D[Math.floor(Math.random() * 10)] + S[Math.floor(Math.random() * 6)]; for (let i = 4; i < 14; i++) p += A[Math.floor(Math.random() * A.length)]; return p.split('').sort(() => Math.random() - 0.5).join(''); } function normalizePaypalPhoneForInput(value = '') { const raw = String(value || '').trim(); const digits = raw.replace(/\D+/g, ''); if (digits.length === 11 && digits.startsWith('1')) { return digits.slice(1); } return digits || raw; } function normalizeExpiry(exp) { if (!exp) return ''; const m = String(exp).match(/^(\d{1,2})\s*\/\s*(\d{2,4})$/); if (!m) return exp; const mm = m[1].padStart(2, '0'); const yy = m[2].length === 4 ? m[2].slice(2) : m[2]; return mm + ' / ' + yy; } function isPaypalHostedReviewPage() { const path = String(location.pathname || ''); const text = document.body?.innerText || ''; return /\/webapps\/hermes/i.test(path) || Boolean(document.getElementById('consentButton')) || /set up once\. pay faster next time|agree and continue/i.test(text); } async function waitForPaypalPostSubmitDecision(payload = {}, timeoutMs = 60000) { const startedAt = Date.now(); const startUrl = location.href; while (Date.now() - startedAt < timeoutMs) { throwIfStopped(); if (isPaypalHostedReviewPage()) { await clickHostedReviewConsent(); return { reviewSubmitted: true }; } if (isPaypalSmsVerificationPage()) { const result = await completePaypalSmsVerification(payload); return { smsVerified: true, ...result }; } if (!/paypal\./i.test(String(location.host || ''))) { return { leftPayPal: true }; } if (location.href !== startUrl && /return|success|complete/i.test(location.href)) { return { redirected: true }; } await sleep(1000); } log('提交后未检测到 PayPal 二次确认页或外部跳转,按已提交继续。', 'warn'); return { postSubmitTimeout: true }; } function isPaypalSmsVerificationPage() { return Boolean(findPaypalSmsCodeInput()) || Boolean(findPaypalSplitSmsCodeInputs().length >= 4) || /verification\s*code|confirm.{0,40}phone|verify.{0,40}phone|sent.{0,40}code|text\s*message|短信|验证码|安全代码/i.test(document.body?.innerText || ''); } async function completePaypalSmsVerification(payload = {}) { await clickPaypalSmsSendCodeButtonIfPresent(); const inputState = await waitForPaypalSmsCodeInput(20000); if (!inputState) { throw new Error('PayPal 短信验证码页面已出现,但未找到验证码输入框。'); } const code = await requestPaypalSmsCode(payload); fillPaypalSmsCodeInput(inputState, code); await sleep(500); const submitButton = findPaypalSmsSubmitButton(); if (!submitButton) { throw new Error('PayPal 短信验证码已填写,但未找到继续/验证按钮。'); } submitButton.click(); log('PayPal 短信验证码已填写并提交。'); await sleep(2500); return { smsCodeSubmitted: true }; } async function requestPaypalSmsCode(payload = {}) { const response = await chrome.runtime.sendMessage({ type: 'PAYPAL_FETCH_SMS_CODE', source: 'checkout-paypal', payload: { phone: payload.paypalPhone || payload.phone || '+15822201173', smsApiUrl: payload.paypalSmsApiUrl || '', }, }); if (response?.error) { throw new Error(response.error); } if (!response?.code) { throw new Error('PayPal 短信收码接口未返回验证码。'); } log(`PayPal 短信验证码已获取(第 ${response.attempt || 1} 次轮询)。`); return String(response.code); } async function clickPaypalSmsSendCodeButtonIfPresent() { const button = findPaypalButtonByPatterns([ /send\s*(?:me\s*)?(?:a\s*)?code/i, /text\s*(?:me|code)/i, /resend/i, /发送.*验证码|短信|重新发送/i, ]); if (button) { button.click(); log('已点击 PayPal 发送/重新发送短信验证码按钮。'); await sleep(1200); } } async function waitForPaypalSmsCodeInput(timeoutMs = 20000) { const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { throwIfStopped(); const input = findPaypalSmsCodeInput(); if (input) { return { input, splitInputs: [] }; } const splitInputs = findPaypalSplitSmsCodeInputs(); if (splitInputs.length >= 4) { return { input: null, splitInputs }; } await sleep(400); } return null; } function fillPaypalSmsCodeInput(inputState, code) { const normalizedCode = String(code || '').replace(/\D+/g, ''); if (!normalizedCode) { throw new Error('PayPal 短信验证码为空。'); } if (inputState.input) { fillInput(inputState.input, normalizedCode); return; } inputState.splitInputs.forEach((input, index) => { if (normalizedCode[index]) { fillInput(input, normalizedCode[index]); } }); } function findPaypalSmsCodeInput() { const selectors = [ 'input[autocomplete="one-time-code"]', 'input[name*="code" i]', 'input[id*="code" i]', 'input[aria-label*="code" i]', 'input[placeholder*="code" i]', 'input[inputmode="numeric"]', 'input[maxlength="6"]', ]; const candidates = selectors.flatMap((selector) => Array.from(document.querySelectorAll(selector))); return candidates.find((input) => { if (!isClickable(input)) return false; const text = getPaypalFieldText(input); if (/card|cvv|cvc|postal|zip|phone|tel|amount|卡|邮编|电话/i.test(text)) { return false; } return /code|otp|security|verification|验证码|安全/i.test(text) || String(input.getAttribute('maxlength') || '') === '6' || String(input.getAttribute('autocomplete') || '').toLowerCase() === 'one-time-code'; }) || null; } function findPaypalSplitSmsCodeInputs() { return Array.from(document.querySelectorAll('input')) .filter((input) => { if (!isClickable(input)) return false; const maxLength = Number(input.getAttribute('maxlength') || input.maxLength || 0); const text = getPaypalFieldText(input); return maxLength === 1 && /code|otp|security|verification|验证码|安全/i.test(text); }) .slice(0, 8); } function findPaypalSmsSubmitButton() { return findPaypalButtonByPatterns([ /continue/i, /verify/i, /confirm/i, /submit/i, /next/i, /继续|验证|确认|提交|下一步/i, ]); } function findPaypalButtonByPatterns(patterns) { return Array.from(document.querySelectorAll('button, [role="button"], input[type="submit"]')).find((button) => { if (!isClickable(button)) return false; const text = String(button.textContent || button.value || button.getAttribute?.('aria-label') || '').trim(); return patterns.some((pattern) => pattern.test(text)); }) || null; } function getPaypalFieldText(el) { if (!el) return ''; const id = el.id ? String(el.id) : ''; const labelText = id ? Array.from(document.querySelectorAll(`label[for="${cssEscape(id)}"]`)).map((label) => label.textContent || '').join(' ') : ''; return [ id, el.name, el.getAttribute?.('autocomplete'), el.getAttribute?.('aria-label'), el.getAttribute?.('placeholder'), labelText, el.closest?.('label')?.textContent || '', el.closest?.('[data-testid], [class], div, section, fieldset')?.textContent || '', ].filter(Boolean).join(' '); } function cssEscape(value) { if (window.CSS?.escape) return window.CSS.escape(value); return String(value || '').replace(/["\\]/g, '\\$&'); } async function clickHostedReviewConsent() { log(`PayPal Hermes:开始等待账单确认按钮。当前 URL:${location.href}`, 'info'); let waited = 0; while (waited < 30) { throwIfStopped(); waited += 1; const button = findHostedReviewConsentButton(); if (button) { log('PayPal Hermes:已找到确认按钮,准备点击 Agree and Continue。', 'info'); button.click(); await sleep(1000); return true; } if (waited === 1 || waited % 5 === 0) { log(`PayPal Hermes:尚未找到确认按钮,继续等待(${waited}/30)。`, 'info'); } await sleep(1000); } throw new Error('PayPal hosted checkout 二次确认页超时,未找到确认按钮。'); } function findHostedReviewConsentButton() { const direct = document.getElementById('consentButton') || document.querySelector('button[data-testid="consentButton"]'); if (direct && isClickable(direct)) return direct; const patterns = [ /agree\s*(?:and|&)\s*continue/i, /continue/i, /pay\s*now/i, /同意|继续|付款/i, ]; return Array.from(document.querySelectorAll('button, [role="button"], input[type="submit"]')).find((button) => { if (!isClickable(button)) return false; const text = String(button.textContent || button.value || button.getAttribute?.('aria-label') || '').trim(); return patterns.some((pattern) => pattern.test(text)); }) || null; } function isClickable(el) { if (!el || el.disabled) return false; const rect = el.getBoundingClientRect(); const style = window.getComputedStyle(el); return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none'; } async function clickPaypalSubmit(retries = 0) { throwIfStopped(); if (retries >= 12) throw new Error('未找到 PayPal 提交按钮,已超时'); const btn = document.querySelector('button[data-testid="submit-button"]') || document.querySelector('button[data-testid="hosted-payment-submit-button"]'); if (btn) { const rect = btn.getBoundingClientRect(); if (btn.disabled || rect.height === 0) { log('PayPal 提交按钮被禁用或不可见,等待中...'); await sleep(800); return clickPaypalSubmit(retries + 1); } log(`正在点击 PayPal 提交: ${btn.textContent.trim()}`); btn.click(); } else { const all = document.querySelectorAll('button'); for (let i = 0; i < all.length; i++) { const t = all[i].textContent.trim(); if (['Agree & Create Account', 'Agree and Pay', 'Continue', 'Pay Now'].includes(t) || t.includes('同意')) { all[i].click(); log(`已点击: ${t}`); return; } } log(`未找到提交按钮,重试中... (${retries + 1})`); await sleep(800); return clickPaypalSubmit(retries + 1); } } async function clickNextButton(retries = 0) { throwIfStopped(); if (retries >= 12) throw new Error('未找到 PayPal 下一步按钮,已超时'); const all = document.querySelectorAll('button'); for (let i = 0; i < all.length; i++) { const t = all[i].textContent.trim(); if (['下一页', '下一步', 'Next', 'Continue'].includes(t)) { if (!all[i].disabled && all[i].getBoundingClientRect().height > 0) { all[i].click(); log(`已点击: ${t}`); return; } } } log(`未找到"下一步"按钮,重试中... (${retries + 1})`); await sleep(800); return clickNextButton(retries + 1); } if (isPaypalHostedReviewPage()) { setTimeout(() => { clickHostedReviewConsent().catch((error) => { log(`PayPal Hermes 自动确认失败: ${error?.message || error}`, 'warn'); }); }, 0); } document.documentElement.setAttribute('data-multipage-checkout-paypal-ready', ''); })();