Files
2026-05-29 14:58:32 +08:00

1789 lines
74 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function attachBackgroundPlusCheckoutCreate(root, factory) {
root.MultiPageBackgroundPlusCheckoutCreate = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundPlusCheckoutCreateModule() {
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
const PAYPAL_SOURCE = 'paypal-flow';
const PLUS_CHECKOUT_ENTRY_URL = 'https://chatgpt.com/';
const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'flows/openai/content/plus-checkout.js'];
const PAYPAL_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'flows/openai/content/paypal-flow.js'];
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted';
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
const LOCAL_CHECKOUT_PROXY_HEALTH_URL = 'http://127.0.0.1:21988/health';
const LOCAL_CHECKOUT_PROXY_URL = 'socks5://127.0.0.1:21987';
const LOCAL_CHECKOUT_PROXY_SETTINGS_SCOPE = 'regular';
const LOCAL_CHECKOUT_PROXY_TIMEOUT_MS = 1200;
const LOCAL_CHECKOUT_PROXY_SETTLE_MS = 350;
const DEFAULT_GPC_BASE_URL = 'https://gpc.qlhazycoder.top';
const GPC_PORTAL_URL = 'https://gpc.qlhazycoder.top/';
const HOSTED_CHECKOUT_ADDRESS_ENDPOINT = 'https://www.meiguodizhi.com/api/v1/dz';
const HOSTED_CHECKOUT_SUCCESS_URL_PATTERN = /^https:\/\/(?:chatgpt\.com|www\.chatgpt\.com|chat\.openai\.com)\/(?:backend-api\/)?payments\/success(?:[/?#]|$)/i;
const HOSTED_CHECKOUT_TRANSITION_TIMEOUT_MS = 120000;
const HOSTED_CHECKOUT_PAYPAL_TIMEOUT_MS = 10 * 60 * 1000;
const HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS = 12;
const HOSTED_CHECKOUT_VERIFICATION_POLL_INTERVAL_MS = 5000;
const HOSTED_CHECKOUT_DEFAULT_PHONE = '1234567890';
const PAYPAL_HOSTED_STAGE_OUTSIDE = 'outside_paypal';
const PAYPAL_HOSTED_STAGE_LOGIN = 'pay_login';
const PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT = 'guest_checkout';
const PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT = 'create_account';
const PAYPAL_HOSTED_STAGE_SECURITY_CODE = 'security_code';
const PAYPAL_HOSTED_STAGE_REVIEW = 'review_consent';
const PAYPAL_HOSTED_STAGE_APPROVAL = 'approval';
const PAYPAL_HOSTED_STAGE_UNKNOWN = 'unknown';
const PAYPAL_HOSTED_STEP_OPENAI_CHECKOUT = 'paypal-hosted-openai-checkout';
const PAYPAL_HOSTED_STEP_EMAIL = 'paypal-hosted-email';
const PAYPAL_HOSTED_STEP_CARD = 'paypal-hosted-card';
const PAYPAL_HOSTED_STEP_CREATE_ACCOUNT = 'paypal-hosted-create-account';
const PAYPAL_HOSTED_STEP_REVIEW = 'paypal-hosted-review';
const PAYPAL_HOSTED_STEP_META = Object.freeze({
[PAYPAL_HOSTED_STEP_OPENAI_CHECKOUT]: { step: 6, label: '创建 PayPal 无卡直绑 Checkout' },
[PAYPAL_HOSTED_STEP_EMAIL]: { step: 7, label: '无卡直绑 PayPal 邮箱页' },
[PAYPAL_HOSTED_STEP_CARD]: { step: 8, label: '无卡直绑 PayPal 资料页' },
[PAYPAL_HOSTED_STEP_CREATE_ACCOUNT]: { step: 9, label: '无卡直绑 PayPal 创建确认页' },
[PAYPAL_HOSTED_STEP_REVIEW]: { step: 10, label: '无卡直绑 PayPal 授权复核页' },
});
function createPlusCheckoutCreateExecutor(deps = {}) {
const {
addLog: rawAddLog = async () => {},
chrome,
completeNodeFromBackground,
createAutomationTab = null,
ensureContentScriptReadyOnTabUntilStopped,
fetch: fetchImpl = null,
getTabId = null,
getState = null,
isTabAlive = null,
queryTabsInAutomationWindow = null,
registerTab,
sendTabMessageUntilStopped,
setState,
sleepWithStop,
waitForTabCompleteUntilStopped,
waitForTabUrlMatchUntilStopped = null,
withCheckoutCreationProxy = null,
throwIfStopped = () => {},
} = deps;
function addLog(message, level = 'info', options = {}) {
return rawAddLog(message, level, {
step: 6,
stepKey: 'plus-checkout-create',
...(options && typeof options === 'object' ? options : {}),
});
}
function addHostedStepLog(stepKey, message, level = 'info', options = {}) {
const meta = PAYPAL_HOSTED_STEP_META[stepKey] || {};
return rawAddLog(message, level, {
step: meta.step || 6,
stepKey,
...(options && typeof options === 'object' ? options : {}),
});
}
function parseSocks5Endpoint(proxyUrl = '') {
const text = String(proxyUrl || '').trim();
if (!text) {
return null;
}
let parsed = null;
try {
parsed = new URL(text);
} catch {
return null;
}
if (String(parsed.protocol || '').replace(/:$/, '').toLowerCase() !== 'socks5') {
return null;
}
const host = String(parsed.hostname || '').replace(/^\[|\]$/g, '').trim();
const port = Number.parseInt(String(parsed.port || ''), 10);
if (!host || !Number.isInteger(port) || port <= 0 || port > 65535) {
return null;
}
return { host, port };
}
function buildCheckoutCreationPacScript(endpoint) {
const proxyHost = String(endpoint?.host || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
const port = Number.parseInt(String(endpoint?.port || ''), 10);
return `
function FindProxyForURL(url, host) {
host = String(host || '').toLowerCase();
if (host === 'chatgpt.com' || dnsDomainIs(host, '.chatgpt.com')) {
return "SOCKS5 ${proxyHost}:${port}";
}
return "DIRECT";
}`.trim();
}
function callChromeProxySettings(method, details = {}) {
const proxySettings = chrome?.proxy?.settings;
if (!proxySettings || typeof proxySettings[method] !== 'function') {
return Promise.reject(new Error('当前浏览器不支持扩展代理 API'));
}
return new Promise((resolve, reject) => {
proxySettings[method](details, (value) => {
const lastError = chrome?.runtime?.lastError;
if (lastError) {
reject(new Error(lastError.message || String(lastError)));
return;
}
resolve(value);
});
});
}
function canControlProxySettings(details = {}) {
const level = String(details?.levelOfControl || '').trim();
return !level || level === 'controlled_by_this_extension' || level === 'controllable_by_this_extension';
}
async function readProxySettingsSnapshot() {
return callChromeProxySettings('get', { incognito: false });
}
async function restoreProxySettingsSnapshot(snapshot = null) {
const value = snapshot?.value;
const level = String(snapshot?.levelOfControl || '').trim();
if (level === 'controlled_by_this_extension' && value && typeof value === 'object') {
await callChromeProxySettings('set', {
value,
scope: LOCAL_CHECKOUT_PROXY_SETTINGS_SCOPE,
});
return;
}
await callChromeProxySettings('clear', {
scope: LOCAL_CHECKOUT_PROXY_SETTINGS_SCOPE,
});
}
async function fetchLocalCheckoutProxyHealth() {
if (typeof fetchImpl !== 'function') {
return null;
}
const controller = typeof AbortController === 'function' ? new AbortController() : null;
let timer = null;
try {
timer = controller
? setTimeout(() => controller.abort(), LOCAL_CHECKOUT_PROXY_TIMEOUT_MS)
: null;
const response = await fetchImpl(`${LOCAL_CHECKOUT_PROXY_HEALTH_URL}?t=${Date.now()}`, {
method: 'GET',
cache: 'no-store',
headers: { Accept: 'application/json,text/plain,*/*' },
...(controller ? { signal: controller.signal } : {}),
});
if (!response?.ok) {
return null;
}
const payload = await response.json().catch(() => ({}));
if (!payload?.ok) {
return null;
}
const endpoint = parseSocks5Endpoint(payload.localProxy || LOCAL_CHECKOUT_PROXY_URL);
return endpoint ? { endpoint, payload } : null;
} catch {
return null;
} finally {
if (timer) {
clearTimeout(timer);
}
}
}
async function applyTemporaryCheckoutProxy(endpoint) {
const pacScript = buildCheckoutCreationPacScript(endpoint);
await callChromeProxySettings('set', {
value: {
mode: 'pac_script',
pacScript: {
data: pacScript,
mandatory: true,
},
},
scope: LOCAL_CHECKOUT_PROXY_SETTINGS_SCOPE,
});
}
async function runWithLocalCheckoutCreationProxy(action) {
if (typeof withCheckoutCreationProxy === 'function') {
return withCheckoutCreationProxy({
healthUrl: LOCAL_CHECKOUT_PROXY_HEALTH_URL,
localProxyUrl: LOCAL_CHECKOUT_PROXY_URL,
}, action);
}
if (!chrome?.proxy?.settings || typeof fetchImpl !== 'function') {
return action();
}
const health = await fetchLocalCheckoutProxyHealth();
if (!health?.endpoint) {
return action();
}
let snapshot = null;
let applied = false;
let result = null;
let proxyError = null;
let restoreFailed = false;
try {
try {
snapshot = await readProxySettingsSnapshot();
} catch (error) {
return action();
}
if (!canControlProxySettings(snapshot)) {
return action();
}
await applyTemporaryCheckoutProxy(health.endpoint);
applied = true;
await sleepWithStop(LOCAL_CHECKOUT_PROXY_SETTLE_MS);
result = await action();
if (result?.error && !result?.stopped) {
proxyError = new Error(result.error);
}
} catch (error) {
if (!applied) {
return action();
}
proxyError = error;
} finally {
if (applied) {
try {
await restoreProxySettingsSnapshot(snapshot);
await sleepWithStop(LOCAL_CHECKOUT_PROXY_SETTLE_MS);
} catch (error) {
restoreFailed = true;
}
}
}
if (result && !proxyError) {
return result;
}
if (proxyError) {
if (restoreFailed) {
throw proxyError;
}
return action();
}
return null;
}
function normalizePlusPaymentMethod(value = '') {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (rootScope.GoPayUtils?.normalizePlusPaymentMethod) {
return rootScope.GoPayUtils.normalizePlusPaymentMethod(value);
}
const normalized = String(value || '').trim().toLowerCase();
if (normalized === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED || normalized === 'paypal_direct' || normalized === 'paypal-direct') {
return PLUS_PAYMENT_METHOD_PAYPAL_HOSTED;
}
if (normalized === PLUS_PAYMENT_METHOD_GPC_HELPER) {
return PLUS_PAYMENT_METHOD_GPC_HELPER;
}
return normalized === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_PAYMENT_METHOD_GOPAY : PLUS_PAYMENT_METHOD_PAYPAL;
}
function getCheckoutModeLabel(state = {}) {
const paymentMethod = normalizePlusPaymentMethod(state?.plusPaymentMethod);
if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) {
return 'GPC 订阅页';
}
if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) {
return 'PayPal 无卡直绑';
}
return paymentMethod === PLUS_PAYMENT_METHOD_GOPAY ? 'GoPay 订阅页' : 'Plus Checkout';
}
function getPlusPaymentMethodLabel(method = PLUS_PAYMENT_METHOD_PAYPAL) {
const paymentMethod = normalizePlusPaymentMethod(method);
if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) {
return 'GPC';
}
if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) {
return 'PayPal 无卡直绑';
}
return paymentMethod === PLUS_PAYMENT_METHOD_GOPAY ? 'GoPay' : 'PayPal';
}
async function openFreshChatGptTabForCheckoutCreate(options = {}) {
const active = options?.active !== false;
const tab = typeof createAutomationTab === 'function'
? await createAutomationTab({ url: PLUS_CHECKOUT_ENTRY_URL, active })
: await chrome.tabs.create({ url: PLUS_CHECKOUT_ENTRY_URL, active });
const tabId = Number(tab?.id);
if (!Number.isInteger(tabId)) {
throw new Error('步骤 6:打开 ChatGPT 页面失败,无法创建订阅页。');
}
if (typeof registerTab === 'function') {
await registerTab(PLUS_CHECKOUT_SOURCE, tabId);
}
return tabId;
}
function isPayPalUrl(url = '') {
return /paypal\./i.test(String(url || ''));
}
function isHostedCheckoutSuccessUrl(url = '') {
return HOSTED_CHECKOUT_SUCCESS_URL_PATTERN.test(String(url || ''));
}
function isHostedOpenAiCheckoutUrl(url = '') {
return /^https:\/\/(?:pay\.openai\.com|checkout\.stripe\.com)\/c\/pay(?:\/|$)/i.test(String(url || ''));
}
function isHostedCheckoutRuntimeUrl(url = '') {
return isHostedOpenAiCheckoutUrl(url) || isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url);
}
function getHostedStepNumber(stepKey = '') {
return PAYPAL_HOSTED_STEP_META[stepKey]?.step || 6;
}
function normalizeHostedPhoneForPayload(phone = '') {
const digits = String(phone || '').replace(/\D/g, '');
if (!digits) {
return HOSTED_CHECKOUT_DEFAULT_PHONE;
}
if (digits.length > 10 && digits.startsWith('1')) {
return digits.slice(-10);
}
return digits.length > 10 ? digits.slice(-10) : digits;
}
function getHostedProfileFromState(state = {}) {
const profile = state?.plusHostedCheckoutGuestProfile || state?.hostedCheckoutGuestProfile || null;
return profile && typeof profile === 'object' && !Array.isArray(profile) ? profile : null;
}
async function getLatestHostedState(state = {}) {
const latestState = typeof getState === 'function' ? await getState().catch(() => ({})) : {};
return {
...(latestState && typeof latestState === 'object' ? latestState : {}),
...(state && typeof state === 'object' ? state : {}),
};
}
async function ensureHostedGuestProfile(state = {}) {
const mergedState = await getLatestHostedState(state);
const existingProfile = getHostedProfileFromState(mergedState) || {};
const config = await getHostedCheckoutRuntimeConfig(mergedState);
const address = existingProfile.address && typeof existingProfile.address === 'object'
? existingProfile.address
: await fetchHostedCheckoutAddress();
const generatedProfile = buildHostedGuestProfile(address, {
phone: normalizeHostedPhoneForPayload(config.phone),
});
const nextProfile = {
...generatedProfile,
...existingProfile,
address,
phone: normalizeHostedPhoneForPayload(config.phone || existingProfile.phone),
};
await setState({
plusHostedCheckoutGuestProfile: nextProfile,
plusHostedCheckoutPhoneDigits: nextProfile.phone,
});
return {
profile: nextProfile,
config,
};
}
async function getTabById(tabId) {
const normalizedTabId = Number(tabId) || 0;
if (!normalizedTabId || !chrome?.tabs?.get) {
return null;
}
return chrome.tabs.get(normalizedTabId).catch(() => null);
}
async function registerHostedCheckoutTab(tabId, url = '') {
if (typeof registerTab !== 'function' || !Number.isInteger(Number(tabId))) {
return;
}
await registerTab(isPayPalUrl(url) ? PAYPAL_SOURCE : PLUS_CHECKOUT_SOURCE, Number(tabId));
}
async function findOpenHostedCheckoutTabId() {
const queryTabs = typeof queryTabsInAutomationWindow === 'function'
? queryTabsInAutomationWindow
: (chrome?.tabs?.query ? (queryInfo) => chrome.tabs.query(queryInfo) : null);
if (typeof queryTabs !== 'function') {
return 0;
}
const tabs = await queryTabs({}).catch(() => []);
const candidates = (Array.isArray(tabs) ? tabs : [])
.filter((tab) => Number.isInteger(tab?.id) && isHostedCheckoutRuntimeUrl(tab.url || ''));
if (!candidates.length) {
return 0;
}
const match = candidates.find((tab) => tab.active && tab.currentWindow)
|| candidates.find((tab) => tab.active)
|| candidates[0];
if (match?.id && chrome?.tabs?.update) {
await chrome.tabs.update(match.id, { active: true }).catch(() => {});
}
await registerHostedCheckoutTab(match.id, match.url || '');
return match?.id || 0;
}
async function resolveHostedCheckoutTabId(state = {}, stepKey = '') {
const storedTabId = Number(state?.plusCheckoutTabId) || 0;
const storedTab = await getTabById(storedTabId);
if (storedTab?.id && isHostedCheckoutRuntimeUrl(storedTab.url || '')) {
await registerHostedCheckoutTab(storedTab.id, storedTab.url || '');
return storedTab.id;
}
if (typeof getTabId === 'function') {
const paypalTabId = await Promise.resolve(getTabId(PAYPAL_SOURCE)).catch(() => 0);
const paypalAlive = typeof isTabAlive !== 'function'
? Boolean(paypalTabId)
: await Promise.resolve(isTabAlive(PAYPAL_SOURCE)).catch(() => false);
if (paypalTabId && paypalAlive) {
return paypalTabId;
}
const checkoutTabId = await Promise.resolve(getTabId(PLUS_CHECKOUT_SOURCE)).catch(() => 0);
const checkoutAlive = typeof isTabAlive !== 'function'
? Boolean(checkoutTabId)
: await Promise.resolve(isTabAlive(PLUS_CHECKOUT_SOURCE)).catch(() => false);
if (checkoutTabId && checkoutAlive) {
return checkoutTabId;
}
}
const discoveredTabId = await findOpenHostedCheckoutTabId();
if (discoveredTabId) {
await addHostedStepLog(stepKey, `步骤 ${getHostedStepNumber(stepKey)}:已从当前浏览器标签中发现 PayPal 无卡直绑页面,正在接管继续执行。`, 'info');
return discoveredTabId;
}
throw new Error(`步骤 ${getHostedStepNumber(stepKey)}:未找到 PayPal 无卡直绑标签页,请先完成创建 checkout 节点。`);
}
async function getHostedCurrentUrl(tabId) {
const tab = await getTabById(tabId);
return String(tab?.url || '').trim();
}
async function updateHostedCheckoutTabState(tabId, payload = {}) {
const currentUrl = await getHostedCurrentUrl(tabId);
await setState({
plusCheckoutTabId: tabId,
plusCheckoutUrl: currentUrl,
...(payload && typeof payload === 'object' ? payload : {}),
});
return currentUrl;
}
async function completeHostedStep(stepKey, tabId, payload = {}) {
const currentUrl = await updateHostedCheckoutTabState(tabId, payload);
await completeNodeFromBackground(stepKey, {
plusCheckoutUrl: currentUrl,
...(payload && typeof payload === 'object' ? payload : {}),
});
}
async function completeHostedStepIfSuccessful(stepKey, tabId, state = {}, options = {}) {
const currentUrl = await getHostedCurrentUrl(tabId);
if (!isHostedCheckoutSuccessUrl(currentUrl)) {
return false;
}
const config = await getHostedCheckoutRuntimeConfig(state);
const shouldWait = Boolean(options.waitBeforeComplete);
if (shouldWait && config.oauthDelaySeconds > 0) {
await addHostedStepLog(stepKey, `步骤 ${getHostedStepNumber(stepKey)}:支付成功后等待 ${config.oauthDelaySeconds} 秒,再继续账号接入。`, 'info');
await sleepWithStop(config.oauthDelaySeconds * 1000);
}
await completeHostedStep(stepKey, tabId, {
plusReturnUrl: currentUrl,
plusHostedCheckoutCompleted: true,
plusHostedCheckoutOauthDelaySeconds: config.oauthDelaySeconds,
});
return true;
}
async function waitForUrlMatch(tabId, matcher, timeoutMs = 30000, retryDelayMs = 500) {
const deadline = Date.now() + Math.max(1000, Number(timeoutMs) || 30000);
while (Date.now() < deadline) {
throwIfStopped();
const tab = await chrome?.tabs?.get?.(tabId).catch(() => null);
if (!tab) {
return null;
}
if (matcher(tab.url || '', tab)) {
return tab;
}
await sleepWithStop(retryDelayMs);
}
return null;
}
async function getHostedCheckoutRuntimeConfig(state = {}) {
const latestState = typeof getState === 'function' ? await getState().catch(() => ({})) : {};
return {
verificationUrl: String(
latestState?.hostedCheckoutVerificationUrl
|| state?.hostedCheckoutVerificationUrl
|| ''
).trim(),
phone: String(
latestState?.hostedCheckoutPhoneNumber
|| state?.hostedCheckoutPhoneNumber
|| HOSTED_CHECKOUT_DEFAULT_PHONE
).trim(),
oauthDelaySeconds: normalizeHostedCheckoutDelaySeconds(
latestState?.plusHostedCheckoutOauthDelaySeconds
?? state?.plusHostedCheckoutOauthDelaySeconds
),
};
}
function normalizeHostedCheckoutDelaySeconds(value) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) {
return 3;
}
return Math.min(120, Math.max(0, Math.floor(numeric)));
}
async function fetchHostedCheckoutAddress() {
const { response, data } = await fetchJsonWithTimeout(HOSTED_CHECKOUT_ADDRESS_ENDPOINT, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ path: '/', method: 'address' }),
}, 30000);
if (!response?.ok) {
throw new Error(`获取无卡直绑地址失败(HTTP ${response?.status || 0})。`);
}
const address = data?.address || data || {};
return {
street: String(address.Address || address.street || '123 Main St').trim(),
city: String(address.City || address.city || 'New York').trim(),
state: String(address.State_Full || address.State || address.state || 'New York').trim(),
zip: String(address.Zip_Code || address.zip || '10001').trim().slice(0, 5) || '10001',
};
}
function buildRandomHostedEmail() {
const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789';
let localPart = '';
for (let index = 0; index < 16; index += 1) {
localPart += alphabet[Math.floor(Math.random() * alphabet.length)];
}
return `${localPart}@gmail.com`;
}
function buildRandomHostedPassword() {
const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^';
let value = 'Aa1!';
while (value.length < 14) {
value += alphabet[Math.floor(Math.random() * alphabet.length)];
}
return value;
}
function buildHostedVisaCard() {
const digits = [4, 1, 4, 7];
while (digits.length < 15) {
digits.push(Math.floor(Math.random() * 10));
}
const reversed = digits.slice().reverse();
let sum = 0;
for (let index = 0; index < reversed.length; index += 1) {
let digit = reversed[index];
if (index % 2 === 0) {
digit *= 2;
if (digit > 9) digit -= 9;
}
sum += digit;
}
digits.push((10 - (sum % 10)) % 10);
const month = String(Math.floor(Math.random() * 12) + 1).padStart(2, '0');
const year = (new Date().getFullYear() % 100) + 3;
return {
number: digits.join(''),
expiry: `${month} / ${year}`,
cvv: String(Math.floor(100 + Math.random() * 900)),
};
}
function buildHostedGuestProfile(address = {}, config = {}) {
const card = buildHostedVisaCard();
return {
email: buildRandomHostedEmail(),
password: buildRandomHostedPassword(),
phone: String(config?.phone || HOSTED_CHECKOUT_DEFAULT_PHONE).trim(),
firstName: 'James',
lastName: 'Smith',
cardNumber: card.number,
cardExpiry: card.expiry,
cardCvv: card.cvv,
address,
};
}
function extractHostedVerificationCode(payload = '') {
const candidates = [payload?.data, payload?.code, payload?.text, payload?.message, payload];
for (const candidate of candidates) {
const text = typeof candidate === 'object' ? JSON.stringify(candidate) : String(candidate || '');
const match = text.match(/\d{6}/);
if (match) {
return match[0];
}
}
return '';
}
async function fetchHostedVerificationCode(verificationUrl = '') {
const url = String(verificationUrl || '').trim();
if (!url) {
throw new Error('未配置 OpenAI Checkout 验证码接口。');
}
const fetcher = typeof fetchImpl === 'function'
? fetchImpl
: (typeof fetch === 'function' ? fetch.bind(globalThis) : null);
if (typeof fetcher !== 'function') {
throw new Error('当前运行环境不支持 fetch,无法获取 OpenAI Checkout 验证码。');
}
const separator = url.includes('?') ? '&' : '?';
const response = await fetcher(`${url}${separator}t=${Date.now()}`, {
method: 'GET',
headers: { Accept: 'application/json,text/plain,*/*' },
});
const text = await response.text().catch(() => '');
let payload = text;
try {
payload = text ? JSON.parse(text) : {};
} catch {
payload = text;
}
const code = extractHostedVerificationCode(payload);
if (!code) {
throw new Error('验证码接口暂未返回有效 6 位验证码。');
}
return code;
}
async function pollHostedVerificationCode(verificationUrl = '') {
let lastError = null;
for (let attempt = 1; attempt <= HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS; attempt += 1) {
throwIfStopped();
try {
const code = await fetchHostedVerificationCode(verificationUrl);
await addLog(`步骤 6:已获取 OpenAI Checkout 验证码(${attempt}/${HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS})。`, 'info');
return code;
} catch (error) {
lastError = error;
await addLog(`步骤 6OpenAI Checkout 验证码暂不可用(${attempt}/${HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS}):${error?.message || error}`, 'warn');
if (attempt < HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS) {
await sleepWithStop(HOSTED_CHECKOUT_VERIFICATION_POLL_INTERVAL_MS);
}
}
}
throw lastError || new Error('OpenAI Checkout 验证码轮询失败。');
}
async function runHostedOpenAiCheckout(tabId, profile, config) {
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
inject: PLUS_CHECKOUT_INJECT_FILES,
injectSource: PLUS_CHECKOUT_SOURCE,
logMessage: '步骤 6:正在等待 OpenAI hosted checkout 脚本就绪...',
});
const firstResult = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, {
type: 'RUN_PAYPAL_HOSTED_OPENAI_CHECKOUT_STEP',
source: 'background',
payload: { address: profile.address },
});
if (firstResult?.error) {
throw new Error(firstResult.error);
}
const deadline = Date.now() + HOSTED_CHECKOUT_TRANSITION_TIMEOUT_MS;
let verificationSubmitted = false;
while (Date.now() < deadline) {
throwIfStopped();
const tab = await chrome?.tabs?.get?.(tabId).catch(() => null);
if (!tab) {
throw new Error('步骤 6:无卡直绑 checkout 标签页已关闭。');
}
const currentUrl = String(tab.url || '').trim();
if (isPayPalUrl(currentUrl) || isHostedCheckoutSuccessUrl(currentUrl)) {
return currentUrl;
}
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
inject: PLUS_CHECKOUT_INJECT_FILES,
injectSource: PLUS_CHECKOUT_SOURCE,
});
const pageState = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, {
type: 'PLUS_CHECKOUT_GET_STATE',
source: 'background',
payload: {},
});
if (pageState?.error) {
throw new Error(pageState.error);
}
if (pageState?.hostedVerificationVisible && !verificationSubmitted) {
const verificationCode = await pollHostedVerificationCode(config.verificationUrl);
const verifyResult = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, {
type: 'RUN_PAYPAL_HOSTED_OPENAI_CHECKOUT_STEP',
source: 'background',
payload: { verificationCode },
});
if (verifyResult?.error) {
throw new Error(verifyResult.error);
}
verificationSubmitted = true;
}
await sleepWithStop(500);
}
throw new Error('步骤 6OpenAI hosted checkout 长时间未跳转到 PayPal 或支付成功页。');
}
async function getHostedPayPalState(tabId) {
await waitForTabCompleteUntilStopped(tabId);
await ensureContentScriptReadyOnTabUntilStopped(PAYPAL_SOURCE, tabId, {
inject: PAYPAL_INJECT_FILES,
injectSource: PAYPAL_SOURCE,
logMessage: '步骤 6:正在等待 PayPal 无卡直绑页面脚本就绪...',
});
const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, {
type: 'PAYPAL_HOSTED_GET_STATE',
source: 'background',
payload: {},
});
if (result?.error) {
throw new Error(result.error);
}
return result || {};
}
async function runHostedPayPalStep(tabId, payload = {}) {
await waitForTabCompleteUntilStopped(tabId);
await ensureContentScriptReadyOnTabUntilStopped(PAYPAL_SOURCE, tabId, {
inject: PAYPAL_INJECT_FILES,
injectSource: PAYPAL_SOURCE,
logMessage: '步骤 6:正在等待 PayPal 无卡直绑页面脚本就绪...',
});
const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, {
type: 'PAYPAL_RUN_HOSTED_CHECKOUT_STEP',
source: 'background',
payload,
});
if (result?.error) {
throw new Error(result.error);
}
return result || {};
}
async function submitHostedPayPalSecurityCode(tabId, config = {}, stepKey = PAYPAL_HOSTED_STEP_CREATE_ACCOUNT) {
const stepNumber = getHostedStepNumber(stepKey);
const verificationCode = await pollHostedVerificationCode(config.verificationUrl);
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:已获取 PayPal 手机验证码,正在填写。`, 'info');
return runHostedPayPalStep(tabId, {
expectedStage: PAYPAL_HOSTED_STAGE_SECURITY_CODE,
securityCode: verificationCode,
});
}
function getHostedStageOrder(stage = '') {
switch (stage) {
case PAYPAL_HOSTED_STAGE_LOGIN:
return 1;
case PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT:
return 2;
case PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT:
return 3;
case PAYPAL_HOSTED_STAGE_SECURITY_CODE:
return 3.5;
case PAYPAL_HOSTED_STAGE_REVIEW:
return 4;
case PAYPAL_HOSTED_STAGE_OUTSIDE:
return 5;
default:
return 0;
}
}
function isHostedStageAtOrAfter(stage = '', expectedStage = '') {
const currentOrder = getHostedStageOrder(stage);
const expectedOrder = getHostedStageOrder(expectedStage);
return currentOrder > 0 && expectedOrder > 0 && currentOrder >= expectedOrder;
}
async function waitForHostedPayPalStage(tabId, predicate, options = {}) {
const timeoutMs = Math.max(1000, Number(options.timeoutMs) || HOSTED_CHECKOUT_TRANSITION_TIMEOUT_MS);
const intervalMs = Math.max(100, Number(options.intervalMs) || 500);
const label = String(options.label || 'PayPal 无卡直绑页面').trim();
const deadline = Date.now() + timeoutMs;
let lastStage = '';
while (Date.now() < deadline) {
throwIfStopped();
const currentUrl = await getHostedCurrentUrl(tabId);
if (isHostedCheckoutSuccessUrl(currentUrl)) {
return {
successUrl: currentUrl,
hostedStage: PAYPAL_HOSTED_STAGE_OUTSIDE,
};
}
if (!isPayPalUrl(currentUrl)) {
await sleepWithStop(intervalMs);
continue;
}
try {
const pageState = await getHostedPayPalState(tabId);
lastStage = pageState?.hostedStage || lastStage;
if (await predicate(pageState)) {
return pageState;
}
} catch (error) {
lastStage = error?.message || lastStage;
}
await sleepWithStop(intervalMs);
}
throw new Error(`${label}等待超时${lastStage ? `(最后状态:${lastStage}` : ''}`);
}
async function waitForHostedUrlAfterAction(tabId, matcher, options = {}) {
const timeoutMs = Math.max(1000, Number(options.timeoutMs) || HOSTED_CHECKOUT_TRANSITION_TIMEOUT_MS);
const intervalMs = Math.max(100, Number(options.intervalMs) || 500);
const label = String(options.label || 'PayPal 无卡直绑跳转').trim();
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
throwIfStopped();
const currentTab = await getTabById(tabId);
const currentUrl = String(currentTab?.url || '').trim();
if (matcher(currentUrl, currentTab)) {
await waitForTabCompleteUntilStopped(tabId).catch(() => {});
return currentUrl;
}
await sleepWithStop(intervalMs);
}
throw new Error(`${label}等待超时。`);
}
async function runHostedPayPalStepAndWaitForStageChange(tabId, payload = {}, previousStage = '', options = {}) {
const normalizedPreviousStage = String(previousStage || payload.expectedStage || '').trim();
const label = String(options.label || 'PayPal 无卡直绑页面跳转').trim();
const predicate = typeof options.predicate === 'function'
? options.predicate
: (stateInfo) => stateInfo?.hostedStage && stateInfo.hostedStage !== normalizedPreviousStage;
const stageChangePromise = waitForHostedPayPalStage(tabId, predicate, {
label,
timeoutMs: options.timeoutMs || HOSTED_CHECKOUT_TRANSITION_TIMEOUT_MS,
intervalMs: options.intervalMs || 500,
}).then(
(nextState) => ({ type: 'stage-change', nextState }),
(error) => ({ type: 'stage-error', error })
);
const actionPromise = runHostedPayPalStep(tabId, payload).then(
(result) => ({ type: 'action', result }),
(error) => ({ type: 'action-error', error })
);
const first = await Promise.race([actionPromise, stageChangePromise]);
if (first.type === 'stage-change') {
return {
result: null,
nextState: first.nextState,
completedByStageChange: true,
};
}
if (first.type === 'action-error') {
throw first.error;
}
if (first.type === 'stage-error') {
throw first.error;
}
const stageOutcome = await stageChangePromise;
if (stageOutcome.type === 'stage-change') {
return {
result: first.result,
nextState: stageOutcome.nextState,
completedByStageChange: false,
};
}
throw stageOutcome.error;
}
function resolveCheckoutTargetUrl(result = {}, paymentMethod = PLUS_PAYMENT_METHOD_PAYPAL) {
if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) {
return String(
result?.preferredCheckoutUrl
|| result?.hostedCheckoutUrl
|| result?.checkoutUrl
|| ''
).trim();
}
return String(result?.checkoutUrl || '').trim();
}
async function executeHostedCheckoutCreate(tabId, state = {}, result = {}) {
const targetCheckoutUrl = resolveCheckoutTargetUrl(result, PLUS_PAYMENT_METHOD_PAYPAL_HOSTED);
if (!targetCheckoutUrl) {
throw new Error('步骤 6:PayPal 无卡直绑未返回可用的订阅链接。');
}
await addLog('步骤 6:PayPal 无卡直绑链接已创建,正在打开并提交 OpenAI Checkout 页面...', 'ok');
await chrome.tabs.update(tabId, { url: targetCheckoutUrl, active: true });
await waitForTabCompleteUntilStopped(tabId);
const landedTab = await waitForUrlMatch(
tabId,
(url) => isHostedOpenAiCheckoutUrl(url) || isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url),
HOSTED_CHECKOUT_TRANSITION_TIMEOUT_MS,
500
);
const landedUrl = String(landedTab?.url || targetCheckoutUrl || '').trim();
let completedUrl = landedUrl;
if (isHostedOpenAiCheckoutUrl(completedUrl)) {
const { profile, config } = await ensureHostedGuestProfile(state);
await addLog(`步骤 6:正在提交 OpenAI Checkout,等待跳转到 PayPal 邮箱页(电话使用本地号码 ${profile.phone})。`, 'info');
completedUrl = String(await runHostedOpenAiCheckout(tabId, profile, config) || await getHostedCurrentUrl(tabId) || '').trim();
}
if (isPayPalUrl(completedUrl)) {
await waitForTabCompleteUntilStopped(tabId).catch(() => {});
}
const isAlreadySuccessful = isHostedCheckoutSuccessUrl(completedUrl);
await setState({
plusCheckoutTabId: tabId,
plusCheckoutUrl: completedUrl,
plusCheckoutCountry: result.country || 'US',
plusCheckoutCurrency: result.currency || 'USD',
plusCheckoutSource: PLUS_PAYMENT_METHOD_PAYPAL_HOSTED,
plusReturnUrl: isAlreadySuccessful ? completedUrl : '',
plusHostedCheckoutCompleted: isAlreadySuccessful,
});
await addLog(`步骤 6PayPal 无卡直绑已提交 OpenAI Checkout${result.country || 'US'} ${result.currency || 'USD'}),准备进入 PayPal 邮箱页。`, 'info');
await completeNodeFromBackground('plus-checkout-create', {
plusCheckoutCountry: result.country || 'US',
plusCheckoutCurrency: result.currency || 'USD',
plusCheckoutSource: PLUS_PAYMENT_METHOD_PAYPAL_HOSTED,
plusCheckoutUrl: completedUrl,
plusReturnUrl: isAlreadySuccessful ? completedUrl : '',
plusHostedCheckoutCompleted: isAlreadySuccessful,
});
}
async function executePayPalHostedOpenAiCheckout(state = {}) {
const stepKey = PAYPAL_HOSTED_STEP_OPENAI_CHECKOUT;
const stepNumber = getHostedStepNumber(stepKey);
const tabId = await resolveHostedCheckoutTabId(state, stepKey);
if (await completeHostedStepIfSuccessful(stepKey, tabId, state)) {
return;
}
let currentUrl = await getHostedCurrentUrl(tabId);
if (isPayPalUrl(currentUrl)) {
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:当前已在 PayPal 页面,OpenAI Checkout 节点直接完成。`, 'info');
await completeHostedStep(stepKey, tabId, {
plusCheckoutSource: PLUS_PAYMENT_METHOD_PAYPAL_HOSTED,
});
return;
}
if (!isHostedOpenAiCheckoutUrl(currentUrl)) {
currentUrl = await waitForHostedUrlAfterAction(
tabId,
(url) => isHostedOpenAiCheckoutUrl(url) || isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url),
{ label: `步骤 ${stepNumber}:等待 OpenAI hosted checkout 页面` }
);
}
if (isHostedCheckoutSuccessUrl(currentUrl)) {
await completeHostedStep(stepKey, tabId, {
plusReturnUrl: currentUrl,
plusHostedCheckoutCompleted: true,
});
return;
}
if (isPayPalUrl(currentUrl)) {
await completeHostedStep(stepKey, tabId, {
plusCheckoutSource: PLUS_PAYMENT_METHOD_PAYPAL_HOSTED,
});
return;
}
const { profile, config } = await ensureHostedGuestProfile(state);
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在选择 PayPal 并提交 OpenAI hosted checkout(电话使用本地号码 ${profile.phone})。`, 'info');
const transitionUrl = await runHostedOpenAiCheckout(tabId, profile, config);
const completedUrl = String(transitionUrl || await getHostedCurrentUrl(tabId) || '').trim();
await completeHostedStep(stepKey, tabId, {
plusCheckoutSource: PLUS_PAYMENT_METHOD_PAYPAL_HOSTED,
plusCheckoutUrl: completedUrl,
plusReturnUrl: isHostedCheckoutSuccessUrl(completedUrl) ? completedUrl : '',
plusHostedCheckoutCompleted: isHostedCheckoutSuccessUrl(completedUrl),
});
}
async function executePayPalHostedEmail(state = {}) {
const stepKey = PAYPAL_HOSTED_STEP_EMAIL;
const stepNumber = getHostedStepNumber(stepKey);
const tabId = await resolveHostedCheckoutTabId(state, stepKey);
if (await completeHostedStepIfSuccessful(stepKey, tabId, state)) {
return;
}
const { profile } = await ensureHostedGuestProfile(state);
await waitForHostedUrlAfterAction(
tabId,
(url) => isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url),
{ label: `步骤 ${stepNumber}:等待 PayPal 邮箱页` }
);
if (await completeHostedStepIfSuccessful(stepKey, tabId, state)) {
return;
}
const pageState = await getHostedPayPalState(tabId);
if (isHostedStageAtOrAfter(pageState.hostedStage, PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT)
&& pageState.hostedStage !== PAYPAL_HOSTED_STAGE_LOGIN) {
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:当前 PayPal 已进入后续页面(${pageState.hostedStage}),邮箱节点直接完成。`, 'info');
await completeHostedStep(stepKey, tabId, {
plusHostedCheckoutLastStage: pageState.hostedStage,
});
return;
}
if (pageState.hostedStage !== PAYPAL_HOSTED_STAGE_LOGIN) {
throw new Error(`步骤 ${stepNumber}:当前不是 PayPal 邮箱页(当前状态:${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN})。`);
}
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在填写 PayPal 无卡直绑邮箱。`, 'info');
const { nextState, completedByStageChange } = await runHostedPayPalStepAndWaitForStageChange(tabId, {
expectedStage: PAYPAL_HOSTED_STAGE_LOGIN,
email: profile.email,
}, PAYPAL_HOSTED_STAGE_LOGIN, { label: `步骤 ${stepNumber}:等待 PayPal 邮箱页跳转` });
if (completedByStageChange) {
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:已检测到 PayPal 进入后续页面(${nextState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN}),邮箱节点直接完成。`, 'info');
}
await completeHostedStep(stepKey, tabId, {
plusHostedCheckoutLastStage: nextState.hostedStage || '',
});
}
async function executePayPalHostedCard(state = {}) {
const stepKey = PAYPAL_HOSTED_STEP_CARD;
const stepNumber = getHostedStepNumber(stepKey);
const tabId = await resolveHostedCheckoutTabId(state, stepKey);
if (await completeHostedStepIfSuccessful(stepKey, tabId, state)) {
return;
}
await waitForHostedUrlAfterAction(
tabId,
(url) => isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url),
{ label: `步骤 ${stepNumber}:等待 PayPal 资料页` }
);
if (await completeHostedStepIfSuccessful(stepKey, tabId, state)) {
return;
}
const pageState = await getHostedPayPalState(tabId);
if (isHostedStageAtOrAfter(pageState.hostedStage, PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT)
&& pageState.hostedStage !== PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT) {
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:当前 PayPal 已进入后续页面(${pageState.hostedStage}),资料节点直接完成。`, 'info');
await completeHostedStep(stepKey, tabId, {
plusHostedCheckoutLastStage: pageState.hostedStage,
});
return;
}
if (pageState.hostedStage !== PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT) {
throw new Error(`步骤 ${stepNumber}:当前不是 PayPal 资料页(当前状态:${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN})。`);
}
const { profile } = await ensureHostedGuestProfile(state);
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在填写 PayPal 无卡直绑资料,提交前会复查电话是否为 ${profile.phone}`, 'info');
const cardResult = await runHostedPayPalStep(tabId, {
...profile,
expectedStage: PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT,
phone: profile.phone,
});
if (cardResult?.phoneMatched) {
await addHostedStepLog(
stepKey,
`步骤 ${stepNumber}:PayPal 页面电话复查通过(配置 ${cardResult.payloadPhoneDigits},页面 ${cardResult.renderedPhoneDigits})。`,
'info'
);
}
const nextState = await waitForHostedPayPalStage(
tabId,
(stateInfo) => stateInfo?.hostedStage && stateInfo.hostedStage !== PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT,
{ label: `步骤 ${stepNumber}:等待 PayPal 资料页跳转` }
);
await completeHostedStep(stepKey, tabId, {
plusHostedCheckoutLastStage: nextState.hostedStage || '',
});
}
async function executePayPalHostedCreateAccount(state = {}) {
const stepKey = PAYPAL_HOSTED_STEP_CREATE_ACCOUNT;
const stepNumber = getHostedStepNumber(stepKey);
const tabId = await resolveHostedCheckoutTabId(state, stepKey);
if (await completeHostedStepIfSuccessful(stepKey, tabId, state)) {
return;
}
await waitForHostedUrlAfterAction(
tabId,
(url) => isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url),
{ label: `步骤 ${stepNumber}:等待 PayPal 创建确认页` }
);
if (await completeHostedStepIfSuccessful(stepKey, tabId, state)) {
return;
}
const pageState = await getHostedPayPalState(tabId);
const config = await getHostedCheckoutRuntimeConfig(state);
if (pageState.hostedStage === PAYPAL_HOSTED_STAGE_SECURITY_CODE) {
await submitHostedPayPalSecurityCode(tabId, config, stepKey);
const nextState = await waitForHostedPayPalStage(
tabId,
(stateInfo) => stateInfo?.hostedStage && stateInfo.hostedStage !== PAYPAL_HOSTED_STAGE_SECURITY_CODE,
{ label: `步骤 ${stepNumber}:等待 PayPal 验证码提交后跳转` }
);
await completeHostedStep(stepKey, tabId, {
plusHostedCheckoutLastStage: nextState.hostedStage || '',
});
return;
}
if (isHostedStageAtOrAfter(pageState.hostedStage, PAYPAL_HOSTED_STAGE_REVIEW)
&& pageState.hostedStage !== PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT) {
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:当前 PayPal 已进入后续页面(${pageState.hostedStage}),创建确认节点直接完成。`, 'info');
await completeHostedStep(stepKey, tabId, {
plusHostedCheckoutLastStage: pageState.hostedStage,
});
return;
}
if (pageState.hostedStage !== PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT) {
throw new Error(`步骤 ${stepNumber}:当前不是 PayPal 创建确认页(当前状态:${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN})。`);
}
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在确认创建 PayPal 账号。`, 'info');
await runHostedPayPalStep(tabId, {
expectedStage: PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT,
});
const nextState = await waitForHostedPayPalStage(
tabId,
async (stateInfo) => {
if (stateInfo?.hostedStage === PAYPAL_HOSTED_STAGE_SECURITY_CODE) {
await submitHostedPayPalSecurityCode(tabId, config, stepKey);
return false;
}
return stateInfo?.hostedStage && stateInfo.hostedStage !== PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT;
},
{ label: `步骤 ${stepNumber}:等待 PayPal 创建确认页跳转` }
);
await completeHostedStep(stepKey, tabId, {
plusHostedCheckoutLastStage: nextState.hostedStage || '',
});
}
async function executePayPalHostedReview(state = {}) {
const stepKey = PAYPAL_HOSTED_STEP_REVIEW;
const stepNumber = getHostedStepNumber(stepKey);
const tabId = await resolveHostedCheckoutTabId(state, stepKey);
if (await completeHostedStepIfSuccessful(stepKey, tabId, state, { waitBeforeComplete: true })) {
return;
}
await waitForHostedUrlAfterAction(
tabId,
(url) => isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url),
{ label: `步骤 ${stepNumber}:等待 PayPal 授权复核页` }
);
if (await completeHostedStepIfSuccessful(stepKey, tabId, state, { waitBeforeComplete: true })) {
return;
}
const pageState = await getHostedPayPalState(tabId);
if (pageState.hostedStage !== PAYPAL_HOSTED_STAGE_REVIEW) {
throw new Error(`步骤 ${stepNumber}:当前不是 PayPal 授权复核页(当前状态:${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN})。`);
}
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在确认 PayPal 授权复核页。`, 'info');
await runHostedPayPalStep(tabId, {
expectedStage: PAYPAL_HOSTED_STAGE_REVIEW,
});
await waitForHostedUrlAfterAction(
tabId,
(url) => isHostedCheckoutSuccessUrl(url),
{ label: `步骤 ${stepNumber}:等待 PayPal 回到 ChatGPT 支付成功页`, timeoutMs: HOSTED_CHECKOUT_PAYPAL_TIMEOUT_MS }
);
if (!await completeHostedStepIfSuccessful(stepKey, tabId, state, { waitBeforeComplete: true })) {
throw new Error(`步骤 ${stepNumber}PayPal 授权后未检测到 ChatGPT 支付成功页。`);
}
}
function normalizeGpcBaseUrl(apiUrl = '') {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (rootScope.GoPayUtils?.normalizeGpcBaseUrl) {
return rootScope.GoPayUtils.normalizeGpcBaseUrl(apiUrl);
}
let normalized = String(apiUrl || DEFAULT_GPC_BASE_URL).trim().replace(/\/+$/g, '');
normalized = normalized.replace(/\/api\/checkout\/start$/i, '');
normalized = normalized.replace(/\/api\/web\/card\/balance(?:\?.*)?$/i, '');
normalized = normalized.replace(/\/api\/card\/balance(?:\?.*)?$/i, '');
return normalized || DEFAULT_GPC_BASE_URL;
}
async function fetchJsonWithTimeout(url, options = {}, timeoutMs = 30000) {
const fetcher = typeof fetchImpl === 'function'
? fetchImpl
: (typeof fetch === 'function' ? fetch.bind(globalThis) : null);
if (typeof fetcher !== 'function') {
throw new Error('当前运行环境不支持 fetch,无法调用远端接口。');
}
const controller = typeof AbortController === 'function' ? new AbortController() : null;
const effectiveTimeoutMs = Math.max(1000, Number(timeoutMs) || 30000);
let didTimeout = false;
let timer = null;
const buildTimeoutError = () => new Error(`远端接口请求超时(>${Math.round(effectiveTimeoutMs / 1000)} 秒):${url}`);
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(() => {
didTimeout = true;
reject(buildTimeoutError());
if (controller) {
controller.abort();
}
}, effectiveTimeoutMs);
});
try {
const response = await Promise.race([
fetcher(url, { ...options, ...(controller ? { signal: controller.signal } : {}) }),
timeoutPromise,
]);
const data = await Promise.race([
response.json().catch(() => ({})),
timeoutPromise,
]);
return { response, data };
} catch (error) {
if (didTimeout || error?.name === 'AbortError') {
throw buildTimeoutError();
}
throw error;
} finally {
if (timer) clearTimeout(timer);
}
}
function resolveGpcCardKey(state = {}) {
const cardKey = String(state?.gpcCardKey || '').trim();
if (!cardKey) {
throw new Error('步骤 6:GPC 模式缺少卡密,请先在侧边栏填写 GPC 卡密。');
}
return cardKey;
}
function resolveGpcCardKeySegments(cardKey = '') {
const text = String(cardKey || '').trim().toUpperCase();
const compact = text.replace(/\s+/g, '');
const rawSegments = compact.includes('-') || compact.includes('_')
? compact.split(/[-_]+/).filter(Boolean)
: (compact.replace(/^GPC[-_]?/i, '').match(/.{1,8}/g) || []);
const withoutPrefix = rawSegments[0] === 'GPC' ? rawSegments.slice(1) : rawSegments;
const segments = withoutPrefix.slice(0, 3)
.map((segment) => String(segment || '').replace(/[^A-Z0-9]/g, '').toUpperCase());
if (segments.length !== 3 || segments.some((segment) => !/^[A-Z0-9]{8}$/.test(segment))) {
throw new Error('步骤 6:GPC 卡密格式不正确,应为 GPC-XXXXXXXX-XXXXXXXX-XXXXXXXX。');
}
return segments;
}
function buildGpcPortalUrl(state = {}) {
const baseUrl = normalizeGpcBaseUrl(state?.gpcBaseUrl || DEFAULT_GPC_BASE_URL)
.replace(/\/+$/g, '');
return `${baseUrl || DEFAULT_GPC_BASE_URL}/`;
}
function isGpcPortalUrl(url = '') {
const text = String(url || '').trim();
if (!text) {
return false;
}
try {
const parsed = new URL(text);
return parsed.hostname === 'gpc.qlhazycoder.top';
} catch {
return /^https:\/\/gpc\.qlhazycoder\.top(?:\/|$)/i.test(text);
}
}
async function findOpenGpcPortalTabId(portalUrl = GPC_PORTAL_URL) {
const queryTabs = typeof queryTabsInAutomationWindow === 'function'
? queryTabsInAutomationWindow
: (chrome?.tabs?.query ? (queryInfo) => chrome.tabs.query(queryInfo) : null);
if (typeof queryTabs !== 'function') {
return 0;
}
const tabs = await queryTabs({}).catch(() => []);
const candidates = (Array.isArray(tabs) ? tabs : [])
.filter((tab) => Number.isInteger(tab?.id) && isGpcPortalUrl(tab.url || ''));
if (!candidates.length) {
return 0;
}
const match = candidates.find((tab) => tab.active && tab.currentWindow)
|| candidates.find((tab) => tab.active)
|| candidates[0];
if (match?.id && chrome?.tabs?.update) {
await chrome.tabs.update(match.id, { url: portalUrl, active: true }).catch(() => (
chrome.tabs.update(match.id, { active: true }).catch(() => {})
));
}
return match?.id || 0;
}
async function openOrReuseGpcPortalTab(state = {}) {
const portalUrl = buildGpcPortalUrl(state);
const storedTab = await getTabById(Number(state?.plusCheckoutTabId) || 0);
if (storedTab?.id && isGpcPortalUrl(storedTab.url || '')) {
if (chrome?.tabs?.update) {
await chrome.tabs.update(storedTab.id, { url: portalUrl, active: true }).catch(() => (
chrome.tabs.update(storedTab.id, { active: true }).catch(() => {})
));
}
if (typeof registerTab === 'function') {
await registerTab(PLUS_CHECKOUT_SOURCE, storedTab.id);
}
return { tabId: storedTab.id, portalUrl };
}
const existingTabId = await findOpenGpcPortalTabId(portalUrl);
if (existingTabId) {
if (typeof registerTab === 'function') {
await registerTab(PLUS_CHECKOUT_SOURCE, existingTabId);
}
return { tabId: existingTabId, portalUrl };
}
const tab = typeof createAutomationTab === 'function'
? await createAutomationTab({ url: portalUrl, active: true })
: await chrome.tabs.create({ url: portalUrl, active: true });
const tabId = Number(tab?.id);
if (!Number.isInteger(tabId)) {
throw new Error('步骤 6:打开 GPC 页面失败。');
}
if (typeof registerTab === 'function') {
await registerTab(PLUS_CHECKOUT_SOURCE, tabId);
}
return { tabId, portalUrl };
}
async function readSessionFromChatGptSessionTab(tabId) {
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
inject: PLUS_CHECKOUT_INJECT_FILES,
injectSource: PLUS_CHECKOUT_SOURCE,
logMessage: '步骤 6:正在等待 ChatGPT 页面完成加载,再继续获取 session...',
});
const sessionResult = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, {
type: 'PLUS_CHECKOUT_GET_STATE',
source: 'background',
payload: {
includeSession: true,
includeAccessToken: true,
},
});
if (sessionResult?.error) {
throw new Error(sessionResult.error);
}
const session = sessionResult?.session && typeof sessionResult.session === 'object'
? sessionResult.session
: null;
const accessToken = String(sessionResult?.accessToken || session?.accessToken || '').trim();
if (!session || !accessToken) {
throw new Error('步骤 6GPC 模式获取 ChatGPT session 失败。');
}
return session;
}
async function prepareGpcCardKeyOnPortalPage(tabId, cardSegments = []) {
if (!chrome?.scripting?.executeScript) {
throw new Error('步骤 6:当前运行环境不支持脚本注入,无法填写 GPC 页面。');
}
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(800);
const ensureCardMode = async () => {
const results = await chrome.scripting.executeScript({
target: { tabId },
func: () => {
const textOf = (element) => String(element?.innerText || element?.textContent || element?.value || '').replace(/\s+/g, ' ').trim();
const isVisible = (element) => {
if (!element) return false;
const style = window.getComputedStyle ? window.getComputedStyle(element) : null;
if (style && (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0)) {
return false;
}
const rect = typeof element.getBoundingClientRect === 'function' ? element.getBoundingClientRect() : null;
return !rect || rect.width > 0 || rect.height > 0;
};
const hasActiveMarker = (element) => {
if (!element) return false;
const markerText = [
element.className,
element.getAttribute?.('class'),
element.getAttribute?.('aria-selected'),
element.getAttribute?.('aria-pressed'),
element.getAttribute?.('data-state'),
element.getAttribute?.('data-active'),
].join(' ');
return /\b(active|selected|current|checked|on|true)\b/i.test(markerText);
};
const modeSelector = 'button, [role="button"], .design-mode-card, .mode-card, [class*="mode-card"], [class*="recharge-card"], [class*="tab"], [class*="option"]';
const findModeButton = (label, oppositeLabel) => {
const directCandidates = Array.from(document.querySelectorAll(modeSelector));
const allCandidates = directCandidates.length
? directCandidates
: Array.from(document.querySelectorAll('body *'));
const candidates = allCandidates
.filter(isVisible)
.map((element) => ({ element, text: textOf(element) }))
.filter(({ text }) => text.includes(label) && !text.includes(oppositeLabel) && text.length <= 120)
.sort((left, right) => left.text.length - right.text.length);
const target = candidates[0]?.element || null;
return target?.closest?.(modeSelector) || target;
};
const clickElement = (element) => {
element.scrollIntoView?.({ block: 'center', inline: 'center' });
element.focus?.();
const rect = typeof element.getBoundingClientRect === 'function' ? element.getBoundingClientRect() : null;
const centerTarget = rect && document.elementFromPoint
? document.elementFromPoint(rect.left + (rect.width / 2), rect.top + (rect.height / 2))
: null;
const target = centerTarget && element.contains?.(centerTarget) ? centerTarget : element;
const fire = (type, EventCtor) => {
try {
target.dispatchEvent?.(new EventCtor(type, { bubbles: true, cancelable: true, view: window }));
} catch {
target.dispatchEvent?.(new Event(type, { bubbles: true, cancelable: true }));
}
};
const pointerEventCtor = window.PointerEvent || window.MouseEvent || Event;
const mouseEventCtor = window.MouseEvent || Event;
fire('pointerdown', pointerEventCtor);
fire('mousedown', mouseEventCtor);
fire('pointerup', pointerEventCtor);
fire('mouseup', mouseEventCtor);
target.click?.();
};
const cardModeButton = findModeButton('卡密充值', '免费充值');
const freeModeButton = findModeButton('免费充值', '卡密充值');
const visibleCardInputs = Array.from(document.querySelectorAll('input.card-key-seg, input[placeholder*="XXXXXXXX"], input[maxlength="8"]'))
.filter(isVisible);
const isCardModeActive = Boolean(cardModeButton && hasActiveMarker(cardModeButton))
|| (visibleCardInputs.length > 0 && !(freeModeButton && hasActiveMarker(freeModeButton)));
if (cardModeButton && !isCardModeActive) {
clickElement(cardModeButton);
return {
hasCardMode: true,
clickedCardMode: true,
isCardModeActive: false,
activeModeText: textOf(cardModeButton),
};
}
return {
hasCardMode: Boolean(cardModeButton),
clickedCardMode: false,
isCardModeActive,
activeModeText: textOf(isCardModeActive ? cardModeButton : freeModeButton),
};
},
});
return results?.[0]?.result || {};
};
let modeResult = null;
for (let attempt = 1; attempt <= 8; attempt += 1) {
modeResult = await ensureCardMode();
if (!modeResult.hasCardMode) {
throw new Error('步骤 6:未找到 GPC“卡密充值”模式入口。');
}
if (modeResult.isCardModeActive) {
break;
}
await sleepWithStop(modeResult.clickedCardMode ? 800 : 500);
}
if (!modeResult.hasCardMode) {
throw new Error('步骤 6:未找到 GPC“卡密充值”模式入口。');
}
if (!modeResult.isCardModeActive) {
throw new Error('步骤 6:GPC 页面切换到卡密充值模式超时。');
}
const results = await chrome.scripting.executeScript({
target: { tabId },
func: (rawCardSegments) => {
const textOf = (element) => String(element?.innerText || element?.textContent || element?.value || '').replace(/\s+/g, ' ').trim();
const isVisible = (element) => {
if (!element) return false;
const style = window.getComputedStyle ? window.getComputedStyle(element) : null;
if (style && (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0)) {
return false;
}
const rect = typeof element.getBoundingClientRect === 'function' ? element.getBoundingClientRect() : null;
return !rect || rect.width > 0 || rect.height > 0;
};
const dispatchInput = (element, value) => {
element.focus?.();
element.value = value;
element.dispatchEvent?.(new Event('input', { bubbles: true }));
element.dispatchEvent?.(new Event('change', { bubbles: true }));
element.blur?.();
};
const hasActiveMarker = (element) => {
if (!element) return false;
const markerText = [
element.className,
element.getAttribute?.('class'),
element.getAttribute?.('aria-selected'),
element.getAttribute?.('aria-pressed'),
element.getAttribute?.('data-state'),
element.getAttribute?.('data-active'),
].join(' ');
return /\b(active|selected|current|checked|on|true)\b/i.test(markerText);
};
const modeButtons = Array.from(document.querySelectorAll('button, [role="button"], .design-mode-card, .mode-card'));
const cardModeButton = modeButtons.find((element) => /卡密充值/.test(textOf(element)));
const freeModeButton = modeButtons.find((element) => /免费充值/.test(textOf(element)));
const cardSegments = Array.isArray(rawCardSegments)
? rawCardSegments.map((segment) => String(segment || '').replace(/[^A-Z0-9]/gi, '').toUpperCase()).slice(0, 3)
: [];
const cardInputs = Array.from(document.querySelectorAll('input.card-key-seg, input[placeholder*="XXXXXXXX"], input[maxlength="8"]'))
.filter(isVisible);
const isCardModeActive = Boolean(cardModeButton && hasActiveMarker(cardModeButton))
|| (cardInputs.length > 0 && !(freeModeButton && hasActiveMarker(freeModeButton)));
cardInputs.forEach((input, index) => {
dispatchInput(input, cardSegments[index] || '');
});
let fallbackInput = null;
if (!cardInputs.length) {
fallbackInput = Array.from(document.querySelectorAll('input')).find((input) => /卡密|card/i.test([
input.placeholder,
input.name,
input.id,
input.className,
].join(' ')));
if (fallbackInput) {
dispatchInput(fallbackInput, `GPC-${cardSegments.join('-')}`);
}
}
const startButton = Array.from(document.querySelectorAll('button, [role="button"]'))
.find((button) => /开始\s*Plus\s*充值|任务进行中/.test(textOf(button)));
const filledSegments = cardInputs.map((input) => String(input.value || '').replace(/\s+/g, '').toUpperCase());
const cardKeyMatches = cardSegments.length === 3
&& cardInputs.length >= 3
&& cardSegments.every((segment, index) => filledSegments[index] === segment);
return {
ok: true,
cardInputCount: cardInputs.length,
cardFallbackFilled: Boolean(fallbackInput),
cardSegments: cardSegments.map((segment) => segment ? segment.length : 0),
cardKeyMatches,
cardKeyValue: filledSegments.join('-') || String(fallbackInput?.value || ''),
isCardModeActive,
startButtonText: textOf(startButton),
activeModeText: textOf(isCardModeActive ? cardModeButton : freeModeButton),
url: location.href,
};
},
args: [cardSegments],
});
const result = results?.[0]?.result || {};
if (!result?.ok) {
throw new Error('步骤 6GPC 页面准备失败。');
}
if (!result.isCardModeActive || (!result.cardInputCount && !result.cardFallbackFilled)) {
throw new Error('步骤 6:GPC 页面未进入卡密充值模式,无法填写卡密。');
}
if (result.cardInputCount >= 3 && !result.cardKeyMatches) {
throw new Error(`步骤 6:GPC 卡密填写校验失败,当前页面为 ${result.cardKeyValue || '空'}`);
}
return result;
}
async function fillGpcSessionOnPortalPage(tabId, sessionJson = '') {
if (!chrome?.scripting?.executeScript) {
throw new Error('步骤 6:当前运行环境不支持脚本注入,无法填写 GPC session。');
}
const results = await chrome.scripting.executeScript({
target: { tabId },
func: (rawSessionJson) => {
const dispatchInput = (element, value) => {
element.focus?.();
element.value = value;
element.dispatchEvent?.(new Event('input', { bubbles: true }));
element.dispatchEvent?.(new Event('change', { bubbles: true }));
element.blur?.();
};
const sessionTextarea = Array.from(document.querySelectorAll('textarea')).find((textarea) => /session|accessToken|完整/i.test([
textarea.placeholder,
textarea.name,
textarea.id,
textarea.className,
].join(' '))) || document.querySelector('textarea.design-session-input') || document.querySelector('textarea');
if (!sessionTextarea) {
throw new Error('未找到 GPC session 输入框。');
}
dispatchInput(sessionTextarea, String(rawSessionJson || ''));
return {
ok: true,
sessionLength: String(sessionTextarea.value || '').length,
};
},
args: [sessionJson],
});
const result = results?.[0]?.result || {};
if (!result?.ok) {
throw new Error('步骤 6GPC session 填写失败。');
}
return result;
}
async function executeGpcCheckoutCreate(state = {}) {
const cardKey = resolveGpcCardKey(state);
const cardSegments = resolveGpcCardKeySegments(cardKey);
await addLog('步骤 6:正在打开 GPC 页面并准备卡密充值模式...', 'info');
const { tabId, portalUrl } = await openOrReuseGpcPortalTab(state);
await addLog('步骤 6:正在切换 GPC 卡密充值模式并校验卡密...', 'info');
const prepared = await prepareGpcCardKeyOnPortalPage(tabId, cardSegments);
await addLog('步骤 6:正在从 ChatGPT 获取完整 session...', 'info');
const sessionTabId = await openFreshChatGptTabForCheckoutCreate({ active: false });
if (chrome?.tabs?.update) {
await chrome.tabs.update(tabId, { active: true }).catch(() => {});
}
let session = null;
try {
session = await readSessionFromChatGptSessionTab(sessionTabId);
} finally {
if (chrome?.tabs?.remove && Number.isInteger(sessionTabId)) {
await chrome.tabs.remove(sessionTabId).catch(() => {});
}
if (chrome?.tabs?.update) {
await chrome.tabs.update(tabId, { active: true }).catch(() => {});
}
}
const sessionJson = JSON.stringify(session);
if (chrome?.tabs?.update) {
await chrome.tabs.update(tabId, { active: true }).catch(() => {});
}
await addLog('步骤 6:GPC 卡密已确认,正在填写 ChatGPT session...', 'info');
const sessionFilled = await fillGpcSessionOnPortalPage(tabId, sessionJson);
await setState({
plusCheckoutTabId: tabId,
plusCheckoutUrl: portalUrl,
plusCheckoutCountry: 'US',
plusCheckoutCurrency: 'USD',
plusCheckoutSource: PLUS_PAYMENT_METHOD_GPC_HELPER,
gpcPageStatus: 'prepared',
gpcPageStatusText: '页面已准备',
});
await addLog(
`步骤 6:GPC 页面已准备完成(卡密 ${prepared.cardInputCount || 0} 段,session ${sessionFilled.sessionLength || 0} 字符),准备继续下一步。`,
'ok'
);
await completeNodeFromBackground('plus-checkout-create', {
plusCheckoutCountry: 'US',
plusCheckoutCurrency: 'USD',
plusCheckoutSource: PLUS_PAYMENT_METHOD_GPC_HELPER,
});
}
async function executePlusCheckoutCreate(state = {}) {
const paymentMethod = normalizePlusPaymentMethod(state?.plusPaymentMethod);
if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) {
await executeGpcCheckoutCreate(state);
return;
}
const paymentMethodLabel = getPlusPaymentMethodLabel(paymentMethod);
const checkoutModeLabel = getCheckoutModeLabel(state);
await addLog(`步骤 6:正在打开新的 ChatGPT 会话,准备创建${checkoutModeLabel}...`, 'info');
let tabId = 0;
const createCheckout = async () => {
tabId = await openFreshChatGptTabForCheckoutCreate();
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
inject: PLUS_CHECKOUT_INJECT_FILES,
injectSource: PLUS_CHECKOUT_SOURCE,
logMessage: '步骤 6:正在等待 ChatGPT 页面完成加载,再继续创建订阅页...',
});
return sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, {
type: 'CREATE_PLUS_CHECKOUT',
source: 'background',
payload: { paymentMethod },
});
};
const result = paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED
? await runWithLocalCheckoutCreationProxy(createCheckout)
: await createCheckout();
if (result?.error) {
throw new Error(result.error);
}
const targetCheckoutUrl = resolveCheckoutTargetUrl(result, paymentMethod);
if (!targetCheckoutUrl) {
throw new Error(`步骤 6${checkoutModeLabel}未返回可用的订阅链接。`);
}
if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) {
await executeHostedCheckoutCreate(tabId, state, result);
return;
}
await addLog(`步骤 6${checkoutModeLabel}已创建,正在打开订阅页面...`, 'ok');
await chrome.tabs.update(tabId, { url: targetCheckoutUrl, active: true });
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
inject: PLUS_CHECKOUT_INJECT_FILES,
injectSource: PLUS_CHECKOUT_SOURCE,
logMessage: '步骤 6:正在等待订阅页面完成加载...',
});
await setState({
plusCheckoutTabId: tabId,
plusCheckoutUrl: targetCheckoutUrl,
plusCheckoutCountry: result.country || 'DE',
plusCheckoutCurrency: result.currency || 'EUR',
plusCheckoutSource: '',
});
await addLog(`步骤 6Plus Checkout 页面已就绪(${paymentMethodLabel} / ${result.country || 'DE'} ${result.currency || 'EUR'}),准备继续下一步。`, 'info');
await completeNodeFromBackground('plus-checkout-create', {
plusCheckoutCountry: result.country || 'DE',
plusCheckoutCurrency: result.currency || 'EUR',
});
}
return {
executePlusCheckoutCreate,
executePayPalHostedOpenAiCheckout,
executePayPalHostedEmail,
executePayPalHostedCard,
executePayPalHostedCreateAccount,
executePayPalHostedReview,
};
}
return {
createPlusCheckoutCreateExecutor,
};
});