// ==UserScript== // @name Stripe支付自动填充(Cursor/Augment/OpenAI) // @namespace http://tampermonkey.net/ // @version 1.1 // @description 现代化界面设计的Stripe支付自动填充工具,支持固定卡号配置,手动操作,无快捷键 // @author ltw // @license MIT // @match https://checkout.stripe.com/* // @match https://billing.augmentcode.com/* // @match https://pay.openai.com/* // @grant none // @downloadURL https://update.greasyfork.icu/scripts/554738/Stripe%E6%94%AF%E4%BB%98%E8%87%AA%E5%8A%A8%E5%A1%AB%E5%85%85%28CursorAugmentOpenAI%29.user.js // @updateURL https://update.greasyfork.icu/scripts/554738/Stripe%E6%94%AF%E4%BB%98%E8%87%AA%E5%8A%A8%E5%A1%AB%E5%85%85%28CursorAugmentOpenAI%29.meta.js // ==/UserScript== ;(function () { "use strict" // 默认配置 const DEFAULT_CONFIG = { name: "陳明", address: "忠孝東路四段88號, 大安區, 台北市, 106", city: "台北市", postal: "106", country: "TW" } // 地址配置 - 多国家/地区 const ADDRESS_CONFIGS = { US: { name: "John Smith", addressLine1: "123 Main Street", addressLine2: "Apt 4B", city: "Carson", state: "CO", postalCode: "81003", country: "US" }, CN: { name: "李伟", addressLine1: "建国门外大街28号8号楼", addressLine2: "朝阳区", city: "北京", state: "北京市", postalCode: "100022", country: "CN" }, TW: { name: "陳明", addressLine1: "忠孝東路四段88號", addressLine2: "大安區", city: "台北市", state: "台北市", postalCode: "106", country: "TW" } } // 当前选中的地址 let currentAddressRegion = "TW" // 固定卡号配置 - 特定URL使用固定卡号而非缓存 const FIXED_CARD_CONFIG = { "pay.openai.com": "4154644401562377|06|2029|000", "billing.augmentcode.com": "5154620020408058|09|2029|306" } // 卡号缓存管理器 class CardCacheManager { constructor() { this.cacheKey = "stripe-autofill-card-cache" } // 从localStorage加载卡号缓存 loadCardCache() { try { const cached = localStorage.getItem(this.cacheKey) return cached ? JSON.parse(cached) : [] } catch (error) { console.error("加载卡号缓存失败:", error) return [] } } // 保存卡号缓存到localStorage saveCardCache(cards) { try { localStorage.setItem(this.cacheKey, JSON.stringify(cards)) return true } catch (error) { console.error("保存卡号缓存失败:", error) return false } } // 批量添加卡号到缓存 addCardsToCache(cardStrings) { const currentCache = this.loadCardCache() const newCards = cardStrings .map(card => card.trim()) .filter(card => card && this.validateCardFormat(card)) const updatedCache = [...currentCache, ...newCards] return this.saveCardCache(updatedCache) } // 验证卡号格式 validateCardFormat(cardString) { const parts = cardString.split("|") return ( parts.length === 4 && parts[0].length >= 13 && parts[1].length >= 1 && parts[2].length >= 2 && parts[3].length >= 3 ) } // 从缓存随机获取一个卡号 getRandomCardFromCache() { const cache = this.loadCardCache() if (cache.length === 0) return null const randomIndex = Math.floor(Math.random() * cache.length) return cache[randomIndex] } // 从缓存中删除已使用的卡号 removeCardFromCache(cardString) { const cache = this.loadCardCache() const updatedCache = cache.filter(card => card !== cardString) return this.saveCardCache(updatedCache) } // 获取缓存中的卡号数量 getCacheSize() { return this.loadCardCache().length } // 清空缓存 clearCache() { return this.saveCardCache([]) } } // 创建全局缓存管理器实例 const cardCache = new CardCacheManager() // 根据当前URL获取固定卡号(如果配置了的话) function getFixedCardForUrl() { const currentUrl = window.location.hostname // 遍历固定卡号配置,查找匹配的域名 for (const domain in FIXED_CARD_CONFIG) { if (currentUrl.includes(domain)) { return { cardInfo: FIXED_CARD_CONFIG[domain], fromCache: false, isFixed: true // 标记为固定卡号 } } } return null // 没有匹配的固定卡号 } // 随机账单信息数据库 const RANDOM_BILLING_DATA = [ { name: "Michael Johnson", addressLine1: "123 Oak Street", addressLine2: "Apt 4B", city: "Austin", state: "TX", postalCode: "73301", country: "US" }, { name: "Sarah Williams", addressLine1: "456 Pine Avenue", addressLine2: "Suite 200", city: "Denver", state: "CO", postalCode: "80202", country: "US" }, { name: "David Brown", addressLine1: "789 Maple Drive", addressLine2: "", city: "Phoenix", state: "AZ", postalCode: "85001", country: "US" }, { name: "Emily Davis", addressLine1: "321 Cedar Lane", addressLine2: "Unit 15", city: "Seattle", state: "WA", postalCode: "98101", country: "US" }, { name: "Robert Miller", addressLine1: "654 Birch Road", addressLine2: "", city: "Miami", state: "FL", postalCode: "33101", country: "US" }, { name: "Jessica Wilson", addressLine1: "987 Elm Street", addressLine2: "Floor 3", city: "Chicago", state: "IL", postalCode: "60601", country: "US" }, { name: "Christopher Moore", addressLine1: "147 Spruce Court", addressLine2: "", city: "Las Vegas", state: "NV", postalCode: "89101", country: "US" }, { name: "Amanda Taylor", addressLine1: "258 Willow Way", addressLine2: "Apt 7A", city: "Portland", state: "OR", postalCode: "97201", country: "US" }, { name: "James Anderson", addressLine1: "369 Poplar Place", addressLine2: "", city: "Nashville", state: "TN", postalCode: "37201", country: "US" }, { name: "Lisa Thomas", addressLine1: "741 Hickory Hill", addressLine2: "Building B", city: "Atlanta", state: "GA", postalCode: "30301", country: "US" } ] // 全局状态管理 - 防止重复执行 const STATE = { isFillingInProgress: false, lastFillTime: 0, fillCooldown: 3000, // 3秒冷却时间 observer: null, autoDetectInterval: null, panelCreated: false, shouldAutoSubmit: false, // 双击后自动提交标记 // 双击检测状态 doubleClickState: { cardOnlyBtn: { lastClickTime: 0, clickCount: 0 }, fillBtn: { lastClickTime: 0, clickCount: 0 } }, doubleClickInterval: 500 // 500ms内的两次点击视为双击 } // 获取配置 function getConfig() { const saved = localStorage.getItem("stripe-autofill-config") if (saved) { const config = JSON.parse(saved) // 清理旧的cardInfo字段,因为现在使用缓存管理 if (config.cardInfo) { delete config.cardInfo // 保存清理后的配置 saveConfig(config) } return config } return DEFAULT_CONFIG } // 保存配置 function saveConfig(config) { localStorage.setItem("stripe-autofill-config", JSON.stringify(config)) } // 双击检测函数 function detectDoubleClick(buttonId) { const now = Date.now() const buttonState = STATE.doubleClickState[buttonId] if (!buttonState) { console.log(`[双击检测] buttonId ${buttonId} 不存在`) return false } const timeSinceLastClick = now - buttonState.lastClickTime console.log( `[双击检测] buttonId: ${buttonId}, 距离上次点击: ${timeSinceLastClick}ms, 阈值: ${STATE.doubleClickInterval}ms` ) // 检查是否在双击时间间隔内 if (buttonState.lastClickTime > 0 && timeSinceLastClick < STATE.doubleClickInterval) { // 这是第二次点击,视为双击 console.log(`[双击检测] 检测到双击!`) buttonState.clickCount = 0 buttonState.lastClickTime = 0 return true } else { // 这是第一次点击或超时后的点击 console.log(`[双击检测] 第一次点击,记录时间戳`) buttonState.clickCount = 1 buttonState.lastClickTime = now return false } } // 自动提交表单函数 async function autoSubmitForm() { try { // 等待填充完成后再提交 await new Promise(resolve => setTimeout(resolve, 800)) const submitButton = document.querySelector('[data-testid="hosted-payment-submit-button"]') if (submitButton) { updateStatus("🚀 检测到双击,正在自动提交...", "info") await new Promise(resolve => setTimeout(resolve, 500)) submitButton.click() updateStatus("✅ 表单已自动提交!", "success") } else { updateStatus("⚠️ 未找到提交按钮", "info") } } catch (error) { console.error("自动提交失败:", error) updateStatus("❌ 自动提交失败: " + error.message, "error") } } // 显示卡号输入弹窗 function showCardInputDialog() { const modal = createCardInputModal() document.body.appendChild(modal) // 添加弹窗动画 setTimeout(() => { modal.style.opacity = "1" modal.querySelector(".modal-content").style.transform = "scale(1)" }, 10) } // 清除所有卡号 function clearAllCards() { // 显示确认对话框 const currentCount = cardCache.getCacheSize() if (currentCount === 0) { updateStatus("⚠️ 缓存中没有卡号", "info") return } if (confirm(`确定要清除所有 ${currentCount} 个缓存卡号吗?\n\n此操作不可恢复!`)) { if (cardCache.clearCache()) { updateStatus("✅ 已清除所有缓存卡号", "success") updateCacheStatus() } else { updateStatus("❌ 清除失败,请重试", "error") } } } // 创建卡号输入弹窗 function createCardInputModal() { const modal = document.createElement("div") modal.id = "card-input-modal" modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.6); z-index: 1000000; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s ease; backdrop-filter: blur(8px); ` modal.innerHTML = `
` // 应用样式 applyModalStyles(modal) // 绑定事件 const cancelBtn = modal.querySelector("#cancel-card-input") const saveBtn = modal.querySelector("#save-card-input") const textarea = modal.querySelector("#card-input-textarea") cancelBtn.addEventListener("click", () => { modal.style.opacity = "0" setTimeout(() => modal.remove(), 300) }) saveBtn.addEventListener("click", () => { const cardData = textarea.value.trim() if (cardData) { const cards = cardData.split("\n").filter(line => line.trim()) if (cardCache.addCardsToCache(cards)) { updateStatus(`✅ 成功添加 ${cards.length} 个卡号到缓存`, "success") modal.style.opacity = "0" setTimeout(() => modal.remove(), 300) } else { updateStatus("❌ 保存卡号失败,请检查格式", "error") } } else { updateStatus("⚠️ 请输入至少一个卡号", "error") } }) // 点击背景关闭 modal.addEventListener("click", e => { if (e.target === modal) { modal.style.opacity = "0" setTimeout(() => modal.remove(), 300) } }) return modal } // 应用弹窗样式 function applyModalStyles(modal) { // 弹窗内容样式 const modalContent = modal.querySelector(".modal-content") modalContent.style.cssText = ` background: linear-gradient(145deg, #ffffff, #f8fafc); border-radius: 20px; padding: 32px; width: 90%; max-width: 600px; max-height: 80vh; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); transform: scale(0.9); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); border: 1px solid rgba(255, 255, 255, 0.2); ` // 弹窗头部样式 const modalHeader = modal.querySelector(".modal-header") modalHeader.style.cssText = ` text-align: center; margin-bottom: 24px; ` const headerTitle = modalHeader.querySelector("h2") headerTitle.style.cssText = ` font-size: 24px; font-weight: 700; color: #1f2937; margin: 0 0 8px 0; letter-spacing: 0.5px; ` const headerDesc = modalHeader.querySelector("p") headerDesc.style.cssText = ` color: #6b7280; font-size: 14px; margin: 0; line-height: 1.5; ` // 弹窗主体样式 const modalBody = modal.querySelector(".modal-body") modalBody.style.cssText = ` margin-bottom: 20px; ` const label = modalBody.querySelector("label") label.style.cssText = ` display: block; font-weight: 600; color: #374151; margin-bottom: 8px; font-size: 14px; ` const textarea = modalBody.querySelector("textarea") textarea.style.cssText = ` width: 100%; height: 200px; padding: 16px; border: 2px solid #e5e7eb; border-radius: 12px; font-size: 14px; font-family: 'Courier New', monospace; resize: vertical; outline: none; transition: border-color 0.3s ease; background: #ffffff; box-sizing: border-box; ` const formHint = modalBody.querySelector(".form-hint") formHint.style.cssText = ` color: #6b7280; font-size: 12px; margin-top: 6px; line-height: 1.4; ` // 弹窗底部样式 const modalFooter = modal.querySelector(".modal-footer") modalFooter.style.cssText = ` display: flex; gap: 12px; justify-content: flex-end; ` const cancelBtn = modalFooter.querySelector("#cancel-card-input") cancelBtn.style.cssText = ` padding: 12px 24px; border: 2px solid #e5e7eb; background: #ffffff; color: #6b7280; border-radius: 12px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; font-size: 14px; ` const saveBtn = modalFooter.querySelector("#save-card-input") saveBtn.style.cssText = ` padding: 12px 24px; border: none; background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); color: white; border-radius: 12px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; font-size: 14px; box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); ` // 添加按钮交互效果 cancelBtn.addEventListener("mouseenter", () => { cancelBtn.style.borderColor = "#d1d5db" cancelBtn.style.background = "#f9fafb" }) cancelBtn.addEventListener("mouseleave", () => { cancelBtn.style.borderColor = "#e5e7eb" cancelBtn.style.background = "#ffffff" }) saveBtn.addEventListener("mouseenter", () => { saveBtn.style.background = "linear-gradient(135deg, #2563eb 0%, #1e40af 100%)" saveBtn.style.boxShadow = "0 6px 20px rgba(59, 130, 246, 0.4)" }) saveBtn.addEventListener("mouseleave", () => { saveBtn.style.background = "linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)" saveBtn.style.boxShadow = "0 4px 12px rgba(59, 130, 246, 0.3)" }) // 添加textarea交互效果 textarea.addEventListener("focus", () => { textarea.style.borderColor = "#6366f1" textarea.style.boxShadow = "0 0 0 3px rgba(99, 102, 241, 0.15)" }) textarea.addEventListener("blur", () => { textarea.style.borderColor = "#e5e7eb" textarea.style.boxShadow = "none" }) } // 生成随机银行卡信息(使用缓存) function generateRandomCardInfo() { const randomCard = cardCache.getRandomCardFromCache() if (!randomCard) { // 如果缓存为空,显示输入弹窗 showCardInputDialog() return null } // 为缓存卡号添加来源标识 return { cardInfo: randomCard, fromCache: true } } // 生成随机账单信息 function generateRandomBillingInfo() { const randomIndex = Math.floor(Math.random() * RANDOM_BILLING_DATA.length) return RANDOM_BILLING_DATA[randomIndex] } // 仅填充卡号功能 function fillCardOnlyFromInput() { // 检测双击 const isDoubleClick = detectDoubleClick("cardOnlyBtn") console.log("[fillCardOnlyFromInput] 双击检测结果:", isDoubleClick) // 如果检测到双击,设置标记并等待当前填充完成 if (isDoubleClick) { console.log("[fillCardOnlyFromInput] 检测到双击,设置自动提交标记") STATE.shouldAutoSubmit = true // 如果正在填充中,直接返回,等待当前填充完成后自动提交 if (STATE.isFillingInProgress) { updateStatus("⚠️ 检测到双击,填充完成后将自动提交", "info") return } } // 检查是否正在填充中(非双击情况) if (STATE.isFillingInProgress) { updateStatus("⚠️ 填充正在进行中...", "info") return } // 检查冷却时间 - 双击时跳过冷却检查 const now = Date.now() if (!isDoubleClick && now - STATE.lastFillTime < STATE.fillCooldown) { const remainingTime = Math.ceil((STATE.fillCooldown - (now - STATE.lastFillTime)) / 1000) updateStatus(`⏰ 请等待 ${remainingTime} 秒后再试`, "info") return } // 获取用户输入的卡号 const cardInput = document.getElementById("card-config") let cardInfo = null let fromCache = false let isFixed = false if (cardInput && cardInput.value.trim()) { // 使用用户手动输入的卡号 cardInfo = cardInput.value.trim() fromCache = false isFixed = false } else { // 优先检查是否有固定卡号 const fixedCard = getFixedCardForUrl() if (fixedCard) { cardInfo = fixedCard.cardInfo fromCache = false isFixed = true } else { // 如果没有固定卡号,从缓存获取 if (cardCache.getCacheSize() === 0) { updateStatus("⚠️ 请先输入卡号或添加卡号到缓存", "error") return } const randomCardData = generateRandomCardInfo() if (!randomCardData) { updateStatus("⚠️ 无法获取卡号,请先添加卡号", "error") return } cardInfo = randomCardData.cardInfo fromCache = randomCardData.fromCache isFixed = false } } // 直接调用填充逻辑,不通过fillCardOnly函数 fillCardOnlyDirect(cardInfo, fromCache, isFixed) } // 直接填充卡号的函数 async function fillCardOnlyDirect(cardInfo, fromCache = false, isFixed = false) { console.log( "[fillCardOnlyDirect] 接收到的参数 - cardInfo:", cardInfo, "shouldAutoSubmit:", STATE.shouldAutoSubmit ) try { STATE.isFillingInProgress = true STATE.lastFillTime = Date.now() updateStatus("正在填充银行卡信息...", "info") // 解析银行卡信息 const cardData = parseCardInfo(cardInfo) // 检查是否需要点击银行卡按钮(兼容不同页面类型) const cardButton = document.querySelector('[data-testid="card-accordion-item-button"]') if (cardButton && cardButton.offsetParent !== null) { cardButton.click() await new Promise(resolve => setTimeout(resolve, 300)) // 点击后等待300ms } // 批量填充银行卡信息 - 无需等待 console.log("[fillCardOnlyDirect] 批量填充银行卡信息") await Promise.all([ fillField("#cardNumber", cardData.number), fillField("#cardExpiry", cardData.expiry), fillField("#cardCvc", cardData.cvc) ]) // 固定卡号不删除,可重复使用;只有来自缓存的卡号才删除(一卡一用) if (isFixed) { updateStatus("✅ 银行卡信息填充完成!(使用固定卡号)", "success") } else if (fromCache) { cardCache.removeCardFromCache(cardInfo) updateStatus(`✅ 银行卡信息填充完成!剩余卡号: ${cardCache.getCacheSize()}`, "success") } else { updateStatus("✅ 银行卡信息填充完成!", "success") } // 如果设置了自动提交标记,自动提交表单 console.log( "[fillCardOnlyDirect] 检查是否需要自动提交 - shouldAutoSubmit:", STATE.shouldAutoSubmit ) if (STATE.shouldAutoSubmit) { console.log("[fillCardOnlyDirect] 触发自动提交表单") STATE.shouldAutoSubmit = false // 重置标记 await autoSubmitForm() } } catch (error) { updateStatus("❌ 银行卡填充失败: " + error.message, "error") } finally { // 重置填充状态 STATE.isFillingInProgress = false STATE.shouldAutoSubmit = false // 确保重置标记 } } // 只填充银行卡信息的函数 async function fillCardOnly(cardInfo) { // 检查是否正在填充中 if (STATE.isFillingInProgress) { updateStatus("⚠️ 填充正在进行中...", "info") return } // 检查冷却时间 const now = Date.now() if (now - STATE.lastFillTime < STATE.fillCooldown) { const remainingTime = Math.ceil((STATE.fillCooldown - (now - STATE.lastFillTime)) / 1000) updateStatus(`⏰ 请等待 ${remainingTime} 秒后再试`, "info") return } // 处理卡号信息 let actualCardInfo = cardInfo let fromCache = false if (!cardInfo) { // 如果没有提供卡号信息,从缓存中获取 const randomCardData = generateRandomCardInfo() if (!randomCardData) { updateStatus("⚠️ 缓存中没有可用的卡号,请先添加卡号", "error") return } actualCardInfo = randomCardData.cardInfo fromCache = randomCardData.fromCache } else if (typeof cardInfo === "object" && cardInfo.fromCache) { // 如果传入的是对象且来自缓存 actualCardInfo = cardInfo.cardInfo fromCache = cardInfo.fromCache } try { STATE.isFillingInProgress = true STATE.lastFillTime = now updateStatus("正在填充银行卡信息...", "info") // 解析银行卡信息 const cardData = parseCardInfo(actualCardInfo) // 等待页面加载完成 await new Promise(resolve => setTimeout(resolve, 500)) // 检查是否需要点击银行卡按钮(兼容不同页面类型) const cardButton = document.querySelector('[data-testid="card-accordion-item-button"]') if (cardButton && cardButton.offsetParent !== null) { cardButton.click() await new Promise(resolve => setTimeout(resolve, 1000)) } // 填充银行卡信息 await fillField("#cardNumber", cardData.number) await new Promise(resolve => setTimeout(resolve, 300)) await fillField("#cardExpiry", cardData.expiry) await new Promise(resolve => setTimeout(resolve, 300)) await fillField("#cardCvc", cardData.cvc) await new Promise(resolve => setTimeout(resolve, 300)) // 只有来自缓存的卡号才删除(一卡一用) if (fromCache) { cardCache.removeCardFromCache(actualCardInfo) updateStatus(`✅ 银行卡信息填充完成!剩余卡号: ${cardCache.getCacheSize()}`, "success") } else { updateStatus("✅ 银行卡信息填充完成!", "success") } } catch (error) { updateStatus("❌ 银行卡填充失败: " + error.message, "error") } finally { // 重置填充状态 STATE.isFillingInProgress = false } } // 智能解析地址信息 function parseAddressInfo(addressText) { if (!addressText || typeof addressText !== "string") { return { addressLine1: "", addressLine2: "", city: "", postalCode: "" } } // 智能识别分隔符类型 let parts = [] if (addressText.includes("\n")) { // 换行符分隔 parts = addressText.split("\n") } else if (addressText.includes(",")) { // 逗号分隔 parts = addressText.split(",") } else { // 空格分隔(作为备选) parts = addressText.split(/\s{2,}/) // 两个或更多空格 } // 清理和过滤部分 parts = parts.map(part => part.trim()).filter(part => part) let addressLine1 = "" let addressLine2 = "" let city = "" let postalCode = "" // 先找邮编(5位数字或5位数字-4位数字格式) const zipRegex = /\b\d{5}(-\d{4})?\b/ for (let i = 0; i < parts.length; i++) { const match = parts[i].match(zipRegex) if (match) { postalCode = match[0] parts.splice(i, 1) break } } // 剩余部分按顺序分配 if (parts.length >= 1) { // 第一部分作为地址第一行 addressLine1 = parts[0] } if (parts.length >= 2) { // 第二部分 → 地址第二行 addressLine2 = parts[1] } if (parts.length >= 3) { // 第三部分 → 城市 city = parts[2] } const result = { addressLine1, addressLine2, city, postalCode } return result } // 解析银行卡信息 function parseCardInfo(cardString) { const parts = cardString.split("|") if (parts.length !== 4) { throw new Error("格式错误:应为 卡号|月份|年份|CVV") } let [cardNumber, month, year, cvv] = parts cardNumber = cardNumber.replace(/\s/g, "") if (year.length === 2) { year = "20" + year } else if (year.length === 4) { year = year.slice(-2) } return { number: cardNumber.replace(/(\d{4})(?=\d)/g, "$1 "), expiry: `${month.padStart(2, "0")}/${year}`, cvc: cvv } } // 等待元素 function waitForElement(selector, timeout = 5000) { return new Promise((resolve, reject) => { const element = document.querySelector(selector) if (element) { resolve(element) return } const observer = new MutationObserver(() => { const element = document.querySelector(selector) if (element) { observer.disconnect() resolve(element) } }) observer.observe(document.body, { childList: true, subtree: true }) setTimeout(() => { observer.disconnect() reject(new Error(`Element ${selector} not found`)) }, timeout) }) } // 强力填充函数 - 专门处理Stripe的React组件 function forceSetValue(element, value) { // 方法1: 直接设置value属性 element.value = value // 方法2: 使用原生setter const nativeInputValueSetter = Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, "value" ).set nativeInputValueSetter.call(element, value) // 方法3: 清除React的value tracker if (element._valueTracker) { element._valueTracker.setValue("") } // 方法4: 触发React事件 const inputEvent = new Event("input", { bubbles: true }) element.dispatchEvent(inputEvent) // 方法5: 模拟键盘输入 element.focus() element.select() document.execCommand("insertText", false, value) } // 改进的填充字段函数 - 优化焦点跳转 function fillField(selector, value) { const element = document.querySelector(selector) if (!element) { return Promise.resolve(false) } // 如果值为空或未定义,仍然尝试填充(可能是清空字段) const fillValue = value || "" return new Promise(resolve => { try { // 一次性聚焦和填充,避免重复焦点操作 element.focus() // 短暂延迟确保焦点生效 setTimeout(() => { // 清空并设置值 element.value = "" forceSetValue(element, fillValue) // 触发必要的事件 const events = ["input", "change"] events.forEach(eventType => { const event = new Event(eventType, { bubbles: true, cancelable: true }) element.dispatchEvent(event) }) // 移除焦点,避免后续跳转 element.blur() // 简单验证 if (element.value === fillValue) { resolve(true) } else { // 最后一次尝试 forceSetValue(element, fillValue) resolve(true) } }, 100) // 减少延迟时间 } catch (error) { resolve(false) } }) } // 填充选择框 async function fillSelect(selector, value) { try { const element = await waitForElement(selector) if (!element) { throw new Error(`Select element ${selector} not found`) } element.value = value element.dispatchEvent(new Event("change", { bubbles: true })) element.dispatchEvent(new Event("blur", { bubbles: true })) return true } catch (error) { return false } } // 切换地址区域并直接填充(仅填充地址,不填充银行卡) function switchAddressRegion(region) { // 检查是否正在填充中 if (STATE.isFillingInProgress) { updateStatus("⚠️ 填充正在进行中...", "info") return } // 地址区域切换无需冷却时间,可以快速切换 console.log("[switchAddressRegion] 切换地址区域到:", region) currentAddressRegion = region const addressConfig = ADDRESS_CONFIGS[region] // 更新界面输入框 const nameInput = document.getElementById("name-config") const addressInput = document.getElementById("address-config") if (nameInput) { nameInput.value = addressConfig.name nameInput.dispatchEvent(new Event("input")) } if (addressInput) { // 组合地址信息 const fullAddress = `${addressConfig.addressLine1}\n${addressConfig.addressLine2}\n${addressConfig.city}\n${addressConfig.postalCode}` addressInput.value = fullAddress addressInput.dispatchEvent(new Event("input")) } // 更新按钮状态 updateAddressButtonStates(region) // 显示提示 const regionNames = { US: "美国", CN: "中国", TW: "台湾" } updateStatus(`🌍 正在切换到${regionNames[region]}地址...`, "info") // 只填充地址信息,不填充银行卡 fillAddressOnly(addressConfig) } // 只填充地址信息(不填充银行卡) async function fillAddressOnly(addressConfig) { try { STATE.isFillingInProgress = true STATE.lastFillTime = Date.now() updateStatus("正在填充地址信息...", "info") // 优化填充顺序:先填充国家,再批量填充其他字段 // 1. 首先填充国家(如果需要且字段存在) console.log("[fillAddressOnly] 填充国家") if (addressConfig.country && document.querySelector("#billingCountry")) { await fillSelect("#billingCountry", addressConfig.country) await new Promise(resolve => setTimeout(resolve, 300)) // 国家选择后等待300ms } // 2. 填充省/州信息(如果存在) if (addressConfig.state && document.querySelector("#billingAdministrativeArea")) { await fillSelect("#billingAdministrativeArea", addressConfig.state) } // 3. 批量填充姓名和地址信息 - 无需等待 console.log("[fillAddressOnly] 批量填充姓名和地址信息") await Promise.all([ fillField("#billingName", addressConfig.name), fillField("#billingAddressLine1", addressConfig.addressLine1), fillField("#billingAddressLine2", addressConfig.addressLine2), fillField("#billingLocality", addressConfig.city), document.querySelector("#billingPostalCode") ? fillField("#billingPostalCode", addressConfig.postalCode) : Promise.resolve() ]) updateStatus("✅ 地址信息填充完成!", "success") } catch (error) { updateStatus("❌ 地址填充失败: " + error.message, "error") } finally { // 重置填充状态 STATE.isFillingInProgress = false } } // 更新地址按钮状态 function updateAddressButtonStates(activeRegion) { const buttons = { US: document.getElementById("address-btn-us"), CN: document.getElementById("address-btn-cn"), TW: document.getElementById("address-btn-tw") } Object.keys(buttons).forEach(region => { const btn = buttons[region] if (btn) { if (region === activeRegion) { btn.style.background = "linear-gradient(135deg, #6366f1 0%, #4f46e5 100%)" btn.style.color = "white" btn.style.borderColor = "#6366f1" btn.style.boxShadow = "0 4px 12px rgba(99, 102, 241, 0.3)" } else { btn.style.background = "#ffffff" btn.style.color = "#6b7280" btn.style.borderColor = "#e5e7eb" btn.style.boxShadow = "0 2px 4px rgba(0, 0, 0, 0.08)" } } }) } // 手动填充 - 使用面板配置和地址解析 function manualFill() { // 检测双击 const isDoubleClick = detectDoubleClick("fillBtn") console.log("[manualFill] 双击检测结果:", isDoubleClick) // 如果检测到双击,设置标记并等待当前填充完成 if (isDoubleClick) { console.log("[manualFill] 检测到双击,设置自动提交标记") STATE.shouldAutoSubmit = true // 如果正在填充中,直接返回,等待当前填充完成后自动提交 if (STATE.isFillingInProgress) { updateStatus("⚠️ 检测到双击,填充完成后将自动提交", "info") return } } // 检查是否正在填充中(非双击情况) if (STATE.isFillingInProgress) { updateStatus("⚠️ 填充正在进行中...", "info") return } // 检查冷却时间 - 双击时跳过冷却检查 const now = Date.now() if (!isDoubleClick && now - STATE.lastFillTime < STATE.fillCooldown) { const remainingTime = Math.ceil((STATE.fillCooldown - (now - STATE.lastFillTime)) / 1000) updateStatus(`⏰ 请等待 ${remainingTime} 秒后再试`, "info") return } // 直接从表单获取当前配置 const cardInput = document.getElementById("card-config") const nameInput = document.getElementById("name-config") const addressInput = document.getElementById("address-config") // 获取卡号信息 let cardInfo = null let fromCache = false let isFixed = false if (cardInput && cardInput.value.trim()) { // 如果用户手动输入了卡号,使用手动输入的卡号 cardInfo = cardInput.value.trim() fromCache = false isFixed = false } else { // 优先检查是否有固定卡号 const fixedCard = getFixedCardForUrl() if (fixedCard) { cardInfo = fixedCard.cardInfo fromCache = false isFixed = true } else { // 如果没有固定卡号,从缓存获取 if (cardCache.getCacheSize() === 0) { updateStatus("⚠️ 缓存中没有卡号,请先添加卡号或手动输入卡号", "error") showCardInputDialog() return } const randomCardData = generateRandomCardInfo() if (!randomCardData) { updateStatus("⚠️ 无法获取卡号,请先添加卡号或手动输入卡号", "error") showCardInputDialog() return } cardInfo = randomCardData.cardInfo fromCache = randomCardData.fromCache isFixed = false } } const config = { cardInfo: cardInfo, fromCache: fromCache, isFixed: isFixed, name: (nameInput ? nameInput.value : "") || DEFAULT_CONFIG.name, address: (addressInput ? addressInput.value : "") || DEFAULT_CONFIG.address, city: DEFAULT_CONFIG.city, postal: DEFAULT_CONFIG.postal, country: DEFAULT_CONFIG.country, useAddressParsing: true // 标记使用地址解析 } try { STATE.isFillingInProgress = true STATE.lastFillTime = now updateStatus("正在准备填充...", "info") // 直接开始填充,performFilling函数内部会处理银行卡按钮 performFilling(config) } catch (error) { updateStatus("❌ 填充失败: " + error.message, "error") STATE.isFillingInProgress = false } } // 自动填充 - 使用默认硬编码地址 function autoFill() { // 检查是否正在填充中 if (STATE.isFillingInProgress) { updateStatus("⚠️ 填充正在进行中...", "info") return } // 检查冷却时间 const now = Date.now() if (now - STATE.lastFillTime < STATE.fillCooldown) { const remainingTime = Math.ceil((STATE.fillCooldown - (now - STATE.lastFillTime)) / 1000) updateStatus(`⏰ 请等待 ${remainingTime} 秒后再试`, "info") return } // 获取卡号信息 let cardInfo = null let fromCache = false let isFixed = false // 优先检查是否有固定卡号 const fixedCard = getFixedCardForUrl() if (fixedCard) { cardInfo = fixedCard.cardInfo fromCache = false isFixed = true } else { // 自动填充从缓存获取卡号 if (cardCache.getCacheSize() === 0) { updateStatus("⚠️ 缓存中没有卡号,请先添加卡号", "error") showCardInputDialog() return } const randomCardData = generateRandomCardInfo() if (!randomCardData) { updateStatus("⚠️ 无法获取卡号,请先添加卡号", "error") showCardInputDialog() return } cardInfo = randomCardData.cardInfo fromCache = randomCardData.fromCache isFixed = false } // 使用默认配置,不解析地址 const config = { cardInfo: cardInfo, fromCache: fromCache, isFixed: isFixed, name: DEFAULT_CONFIG.name, city: DEFAULT_CONFIG.city, postal: DEFAULT_CONFIG.postal, country: DEFAULT_CONFIG.country, state: "台北市", addressLine1: "忠孝東路四段88號", addressLine2: "大安區", useAddressParsing: false // 标记不使用地址解析 } try { STATE.isFillingInProgress = true STATE.lastFillTime = now updateStatus("正在准备填充...", "info") // 直接开始填充,performFilling函数内部会处理银行卡按钮 performFilling(config) } catch (error) { updateStatus("❌ 填充失败: " + error.message, "error") STATE.isFillingInProgress = false } } // 执行实际的填充操作 - 优化为顺序填充避免焦点跳转 async function performFilling(config) { console.log("[performFilling] 开始填充, shouldAutoSubmit:", STATE.shouldAutoSubmit) try { // 解析银行卡信息 const cardData = parseCardInfo(config.cardInfo) updateStatus("正在填充银行卡信息...") // 等待页面加载完成 await new Promise(resolve => setTimeout(resolve, 500)) // 检查是否需要点击银行卡按钮(兼容不同页面类型) const cardButton = document.querySelector('[data-testid="card-accordion-item-button"]') if (cardButton && cardButton.offsetParent !== null) { cardButton.click() await new Promise(resolve => setTimeout(resolve, 1000)) } // 批量填充银行卡信息 - 无需等待 console.log("[performFilling] 批量填充银行卡信息") await Promise.all([ fillField("#cardNumber", cardData.number), fillField("#cardExpiry", cardData.expiry), fillField("#cardCvc", cardData.cvc) ]) updateStatus("正在填充持卡人信息...") await fillField("#billingName", config.name) updateStatus("正在填充地址信息...") if (config.useAddressParsing && config.address) { // 手动填充:解析地址信息 const addressInfo = parseAddressInfo(config.address) // 优化填充顺序:先填充国家,再批量填充其他字段 // 1. 首先填充国家(如果需要且字段存在) console.log("[performFilling] 填充国家") if (config.country && document.querySelector("#billingCountry")) { await fillSelect("#billingCountry", config.country) await new Promise(resolve => setTimeout(resolve, 300)) // 国家选择后等待300ms } // 2. 批量填充地址信息 - 无需等待 console.log("[performFilling] 批量填充地址信息") const cityValue = addressInfo.city || config.city || "" const postalValue = addressInfo.postalCode || config.postal || "" await Promise.all([ fillField("#billingAddressLine1", addressInfo.addressLine1 || ""), fillField("#billingAddressLine2", addressInfo.addressLine2 || ""), fillField("#billingLocality", cityValue), document.querySelector("#billingPostalCode") ? fillField("#billingPostalCode", postalValue) : Promise.resolve() ]) } else { // 自动填充:使用硬编码地址(优化填充顺序) // 1. 首先填充国家(如果需要且字段存在) console.log("[performFilling] 填充国家") if (config.country && document.querySelector("#billingCountry")) { await fillSelect("#billingCountry", config.country) await new Promise(resolve => setTimeout(resolve, 300)) // 国家选择后等待300ms } // 2. 填充省/州信息(如果存在) if (config.state && document.querySelector("#billingAdministrativeArea")) { await fillSelect("#billingAdministrativeArea", config.state) } // 3. 批量填充地址信息 - 无需等待 console.log("[performFilling] 批量填充地址信息") await Promise.all([ fillField("#billingAddressLine1", config.addressLine1 || "忠孝東路四段88號"), fillField("#billingAddressLine2", config.addressLine2 || "大安區"), fillField("#billingLocality", config.city), document.querySelector("#billingPostalCode") ? fillField("#billingPostalCode", config.postal) : Promise.resolve() ]) } // 固定卡号不删除,可重复使用;只有来自缓存的卡号才删除(一卡一用) if (config.isFixed) { updateStatus("✅ 填充完成!(使用固定卡号)", "success") } else if (config.cardInfo && config.fromCache) { cardCache.removeCardFromCache(config.cardInfo) updateStatus(`✅ 填充完成!剩余卡号: ${cardCache.getCacheSize()}`, "success") } else { updateStatus("✅ 填充完成!", "success") } // 如果设置了自动提交标记,自动提交表单 console.log( "[performFilling] 检查是否需要自动提交 - shouldAutoSubmit:", STATE.shouldAutoSubmit ) if (STATE.shouldAutoSubmit) { console.log("[performFilling] 触发自动提交表单") STATE.shouldAutoSubmit = false // 重置标记 await autoSubmitForm() } } catch (error) { updateStatus("❌ 填充失败: " + error.message, "error") } finally { // 重置填充状态 STATE.isFillingInProgress = false STATE.shouldAutoSubmit = false // 确保重置标记 } } // 随机账单信息填充 async function fillRandomBillingInfo() { // 检查是否正在填充中 if (STATE.isFillingInProgress) { updateStatus("⚠️ 填充正在进行中...", "info") return } // 检查冷却时间 const now = Date.now() if (now - STATE.lastFillTime < STATE.fillCooldown) { const remainingTime = Math.ceil((STATE.fillCooldown - (now - STATE.lastFillTime)) / 1000) updateStatus(`⏰ 请等待 ${remainingTime} 秒后再试`, "info") return } try { STATE.isFillingInProgress = true STATE.lastFillTime = now updateStatus("正在生成随机信息...", "info") // 获取卡号信息 let randomCardData = null let isFixed = false // 优先检查是否有固定卡号 const fixedCard = getFixedCardForUrl() if (fixedCard) { randomCardData = { cardInfo: fixedCard.cardInfo, fromCache: false } isFixed = true } else { // 检查缓存中是否有卡号 if (cardCache.getCacheSize() === 0) { updateStatus("⚠️ 缓存中没有卡号,请先添加卡号", "error") showCardInputDialog() STATE.isFillingInProgress = false return } // 生成随机银行卡信息(从缓存) randomCardData = generateRandomCardInfo() if (!randomCardData) { updateStatus("⚠️ 无法获取卡号,请先添加卡号", "error") STATE.isFillingInProgress = false return } isFixed = false } const cardData = parseCardInfo(randomCardData.cardInfo) // 生成随机账单信息 const randomBilling = generateRandomBillingInfo() // 等待页面加载完成 await new Promise(resolve => setTimeout(resolve, 500)) // 检查是否需要点击银行卡按钮(兼容不同页面类型) const cardButton = document.querySelector('[data-testid="card-accordion-item-button"]') if (cardButton && cardButton.offsetParent !== null) { cardButton.click() await new Promise(resolve => setTimeout(resolve, 1000)) } updateStatus("正在填充随机银行卡信息...") // 批量填充银行卡信息 - 无需等待 console.log("[fillRandomBillingInfo] 批量填充银行卡信息") await Promise.all([ fillField("#cardNumber", cardData.number), fillField("#cardExpiry", cardData.expiry), fillField("#cardCvc", cardData.cvc) ]) updateStatus("正在填充随机账单信息...") // 优化填充顺序:先填充国家,再批量填充其他字段 // 1. 首先填充国家(如果需要且字段存在) console.log("[fillRandomBillingInfo] 填充国家") if (randomBilling.country && document.querySelector("#billingCountry")) { await fillSelect("#billingCountry", randomBilling.country) await new Promise(resolve => setTimeout(resolve, 300)) // 国家选择后等待300ms } // 2. 填充省/州信息(如果存在) if (randomBilling.state && document.querySelector("#billingAdministrativeArea")) { await fillSelect("#billingAdministrativeArea", randomBilling.state) } // 3. 批量填充姓名和地址信息 - 无需等待 console.log("[fillRandomBillingInfo] 批量填充姓名和地址信息") await Promise.all([ fillField("#billingName", randomBilling.name), fillField("#billingAddressLine1", randomBilling.addressLine1), randomBilling.addressLine2 ? fillField("#billingAddressLine2", randomBilling.addressLine2) : Promise.resolve(), fillField("#billingLocality", randomBilling.city), document.querySelector("#billingPostalCode") ? fillField("#billingPostalCode", randomBilling.postalCode) : Promise.resolve() ]) // 固定卡号不删除,可重复使用;只有来自缓存的卡号才删除(一卡一用) if (isFixed) { updateStatus("✅ 随机银行卡和账单信息填充完成!(使用固定卡号)", "success") } else if (randomCardData.fromCache) { cardCache.removeCardFromCache(randomCardData.cardInfo) updateStatus( `✅ 随机银行卡和账单信息填充完成!剩余卡号: ${cardCache.getCacheSize()}`, "success" ) } else { updateStatus("✅ 随机银行卡和账单信息填充完成!", "success") } } catch (error) { updateStatus("❌ 随机填充失败: " + error.message, "error") } finally { // 重置填充状态 STATE.isFillingInProgress = false } } // 更新状态 function updateStatus(message, type = "info") { const statusEl = document.getElementById("autofill-status") if (statusEl) { statusEl.textContent = message statusEl.style.display = "block" // 现代化状态样式 let bgGradient = "linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(139, 92, 246, 0.1))" let textColor = "#4f46e5" let borderColor = "rgba(59, 130, 246, 0.2)" let shadowColor = "rgba(59, 130, 246, 0.1)" if (type === "success") { bgGradient = "linear-gradient(135deg, rgba(16, 185, 129, 0.1), rgba(5, 150, 105, 0.1))" textColor = "#059669" borderColor = "rgba(16, 185, 129, 0.3)" shadowColor = "rgba(16, 185, 129, 0.2)" } else if (type === "error") { bgGradient = "linear-gradient(135deg, rgba(239, 68, 68, 0.1), rgba(220, 38, 38, 0.1))" textColor = "#dc2626" borderColor = "rgba(239, 68, 68, 0.3)" shadowColor = "rgba(239, 68, 68, 0.2)" } statusEl.style.cssText = ` font-size: 13px; padding: 12px 16px; border-radius: 12px; margin-bottom: 20px; text-align: center; font-weight: 600; background: ${bgGradient}; color: ${textColor}; border: 1px solid ${borderColor}; backdrop-filter: blur(10px); box-shadow: 0 4px 15px ${shadowColor}; display: block; animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden; min-height: 44px; ` // 添加成功或错误图标效果 - 移除自动隐藏逻辑,保持提示语固定显示 if (type === "success" || type === "error") { statusEl.style.animation = "slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1), pulse 0.5s ease 0.3s" // 更新缓存状态显示 updateCacheStatus() // 不再自动隐藏提示语,保持布局稳定 // 注释掉原有的自动隐藏代码 // setTimeout(() => { // statusEl.style.animation = "slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1) reverse" // setTimeout(() => { // statusEl.style.display = "none" // statusEl.textContent = "" // }, 300) // }, 3000) } } } // 创建控制面板 - 添加重复创建防护 function createPanel() { // 检查是否已存在面板或正在创建中 if (document.getElementById("stripe-autofill-panel") || STATE.panelCreated) { return } STATE.panelCreated = true const panel = document.createElement("div") panel.id = "stripe-autofill-panel" // 移除CSS样式表,改为直接应用到元素 // 设置面板样式 - 现代化玻璃拟态设计,自适应高度 panel.style.cssText = ` position: fixed !important; top: 24px !important; right: 24px !important; width: 320px !important; max-height: 90vh !important; height: auto !important; background: rgba(255, 255, 255, 0.95) !important; border: 1px solid rgba(255, 255, 255, 0.3) !important; border-radius: 16px !important; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 4px 16px rgba(0, 0, 0, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.6) !important; z-index: 999999 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'SF Pro Display', sans-serif !important; overflow: visible !important; backdrop-filter: blur(20px) !important; -webkit-backdrop-filter: blur(20px) !important; transform: translateX(0) scale(1) !important; transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important; ` panel.innerHTML = `