| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- // 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', '');
- })();
|