From 3aa9b136d32b5ff5e8a9fa9e0882b9a10b36a653 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Wed, 20 May 2026 07:38:49 +0800 Subject: [PATCH] Integrate Plus hosted session binding --- background.js | 85 +++++++++- background/plus-hosted-checkout-success.js | 153 ++++++++++++++++++ background/steps/create-plus-checkout.js | 53 +++++- content/plus-checkout.js | 41 ++++- data/step-definitions.js | 13 +- shared/flow-capabilities.js | 30 ++-- shared/settings-schema.js | 2 +- sidepanel/sidepanel.html | 2 +- sidepanel/sidepanel.js | 29 +--- ...ctive-plus-account-access-strategy.test.js | 53 ++++-- .../background-message-router-module.test.js | 68 ++------ tests/flow-capabilities-module.test.js | 37 ++++- .../hosted-checkout-background-wiring.test.js | 120 ++++++++++++++ tests/plus-account-access-strategy.test.js | 15 +- tests/plus-checkout-address-input.test.js | 53 +++++- tests/plus-checkout-create-wait.test.js | 96 +++++++++++ tests/plus-hosted-checkout-success.test.js | 135 ++++++++++++++++ tests/sidepanel-flow-source-registry.test.js | 1 + tests/sidepanel-plus-payment-method.test.js | 4 +- tests/step-definitions-module.test.js | 8 +- 20 files changed, 850 insertions(+), 148 deletions(-) create mode 100644 background/plus-hosted-checkout-success.js create mode 100644 tests/hosted-checkout-background-wiring.test.js create mode 100644 tests/plus-hosted-checkout-success.test.js diff --git a/background.js b/background.js index d826c65..0e8bd81 100644 --- a/background.js +++ b/background.js @@ -41,6 +41,7 @@ importScripts( 'background/message-router.js', 'background/verification-flow.js', 'background/auto-run-controller.js', + 'background/plus-hosted-checkout-success.js', 'background/tab-runtime.js', 'background/navigation-utils.js', 'background/logging-status.js', @@ -857,6 +858,19 @@ function buildResolvedStepDefinitionState(state = {}) { || capabilityState?.effectiveSignupMethod || requestedSignupMethod ); + const resolvedPlusAccountAccessStrategy = normalizePlusAccountAccessStrategy( + stepDefinitionOptions.plusAccountAccessStrategy + ?? capabilityState?.effectivePlusAccountAccessStrategy + ?? state?.plusAccountAccessStrategy + ); + const effectivePlusAccountAccessStrategy = ( + resolvedActiveFlowId === defaultFlowId + && plusModeEnabled + && Boolean(state?.accountContributionEnabled) + && resolvedSignupMethod === SIGNUP_METHOD_EMAIL + ) + ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION + : resolvedPlusAccountAccessStrategy; return { ...state, @@ -868,11 +882,7 @@ function buildResolvedStepDefinitionState(state = {}) { ? plusModeEnabled : Boolean(stepDefinitionOptions.plusModeEnabled), plusPaymentMethod, - plusAccountAccessStrategy: normalizePlusAccountAccessStrategy( - stepDefinitionOptions.plusAccountAccessStrategy - ?? capabilityState?.effectivePlusAccountAccessStrategy - ?? state?.plusAccountAccessStrategy - ), + plusAccountAccessStrategy: effectivePlusAccountAccessStrategy, signupMethod: resolvedSignupMethod, resolvedSignupMethod: resolvedSignupMethod, phoneSignupReloginAfterBindEmailEnabled: Boolean(state?.phoneSignupReloginAfterBindEmailEnabled), @@ -1150,7 +1160,7 @@ const PERSISTED_SETTING_DEFAULTS = { customPassword: '', plusModeEnabled: false, plusPaymentMethod: DEFAULT_PLUS_PAYMENT_METHOD, - plusAccountAccessStrategy: 'oauth', + plusAccountAccessStrategy: 'sub2api_codex_session', paypalEmail: '', paypalPassword: '', currentPayPalAccountId: '', @@ -10521,6 +10531,7 @@ let resumeWaiter = null; const AUTO_RUN_SIGNAL_COMPLETION_TIMEOUT_MS = 120000; const AUTO_RUN_STEP_IDLE_LOG_TIMEOUT_MS = 5 * 60 * 1000; const AUTO_RUN_STEP_IDLE_LOG_CHECK_INTERVAL_MS = 5000; +const HOSTED_CHECKOUT_SUCCESS_SIGNAL_TIMEOUT_MS = 30 * 60 * 1000; const AUTO_RUN_STEP_IDLE_RESTART_MAX_ATTEMPTS = 3; const AUTO_RUN_STEP_IDLE_RESTART_ERROR_PREFIX = 'AUTO_RUN_STEP_IDLE_RESTART::'; const AUTO_RUN_BACKGROUND_COMPLETED_STEPS = new Set([1, 2, 4, 6, 7, 8, 9]); @@ -10530,7 +10541,6 @@ const AUTO_RUN_BACKGROUND_COMPLETED_STEP_KEYS = new Set([ 'submit-signup-email', 'fetch-signup-code', 'wait-registration-success', - 'plus-checkout-create', 'plus-checkout-billing', 'paypal-approve', 'plus-checkout-return', @@ -10558,6 +10568,7 @@ const AUTO_RUN_BACKGROUND_COMPLETED_STEP_KEYS = new Set([ const STEP_COMPLETION_SIGNAL_STEP_KEYS = new Set([ 'fill-password', 'fill-profile', + 'plus-checkout-create', 'gopay-subscription-confirm', 'platform-verify', ]); @@ -10665,7 +10676,34 @@ function getAutoRunPreExecutionDelayMs(step, state = {}) { return getAutoRunPreExecutionDelayMsForNode(getNodeIdByStepForState(step, state), state); } +function isHostedCheckoutSuccessCompletionNode(nodeId, state = {}) { + const executionKey = getNodeExecutionKeyForState(nodeId, state); + if ((executionKey || nodeId) !== 'plus-checkout-create') { + return false; + } + if (!state?.plusModeEnabled) { + return false; + } + const paymentMethod = String(state?.plusPaymentMethod || '').trim().toLowerCase(); + if (paymentMethod !== 'paypal') { + return false; + } + const signupMethod = String(state?.resolvedSignupMethod || state?.signupMethod || 'email').trim().toLowerCase(); + if (signupMethod === 'phone') { + return false; + } + if (Boolean(state?.accountContributionEnabled)) { + return true; + } + const strategy = normalizePlusAccountAccessStrategy(state?.plusAccountAccessStrategy); + return strategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION + || strategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION; +} + function getNodeCompletionSignalTimeoutMs(nodeId, state = {}) { + if (isHostedCheckoutSuccessCompletionNode(nodeId, state)) { + return HOSTED_CHECKOUT_SUCCESS_SIGNAL_TIMEOUT_MS; + } const executionKey = getNodeExecutionKeyForState(nodeId, state); return STEP_COMPLETION_SIGNAL_TIMEOUTS_BY_STEP_KEY.get(executionKey || nodeId) || AUTO_RUN_SIGNAL_COMPLETION_TIMEOUT_MS; } @@ -10674,6 +10712,12 @@ function getStepCompletionSignalTimeoutMs(step, state = {}) { return getNodeCompletionSignalTimeoutMs(getNodeIdByStepForState(step, state), state); } +function getAutoRunNodeIdleLogTimeoutMs(nodeId, state = {}) { + return isHostedCheckoutSuccessCompletionNode(nodeId, state) + ? HOSTED_CHECKOUT_SUCCESS_SIGNAL_TIMEOUT_MS + : AUTO_RUN_STEP_IDLE_LOG_TIMEOUT_MS; +} + function notifyNodeComplete(nodeId, payload) { const normalizedNodeId = String(nodeId || '').trim(); const waiter = nodeWaiters.get(normalizedNodeId); @@ -10974,10 +11018,19 @@ async function runAutoNodeActionWithIdleLogWatchdog(nodeId, action, options = {} } async function executeNodeAndWaitWithAutoRunIdleLogWatchdog(nodeId, delayAfter = 2000, options = {}) { + const executionState = await getState(); + const idleTimeoutMs = Number(options.idleTimeoutMs) > 0 + ? Number(options.idleTimeoutMs) + : (typeof getAutoRunNodeIdleLogTimeoutMs === 'function' + ? getAutoRunNodeIdleLogTimeoutMs(nodeId, executionState) + : AUTO_RUN_STEP_IDLE_LOG_TIMEOUT_MS); return runAutoNodeActionWithIdleLogWatchdog( nodeId, () => executeNodeAndWait(nodeId, delayAfter), - options + { + ...options, + idleTimeoutMs, + } ); } @@ -13441,6 +13494,16 @@ const plusReturnConfirmExecutor = self.MultiPageBackgroundPlusReturnConfirm?.cre sleepWithStop, waitForTabUrlMatchUntilStopped, }); +const plusHostedCheckoutSuccessManager = self.MultiPagePlusHostedCheckoutSuccess?.createPlusHostedCheckoutSuccessManager({ + addLog, + completeNodeFromBackground, + failNodeFromBackground: async (nodeId, message) => { + notifyNodeError(nodeId, message); + await finalizeDeferredNodeExecutionError(nodeId, new Error(message)); + }, + getState, + setState, +}); const sub2ApiSessionImportExecutor = self.MultiPageBackgroundSub2ApiSessionImport?.createSub2ApiSessionImportExecutor({ addLog, chrome, @@ -15541,6 +15604,12 @@ chrome.alarms.onAlarm.addListener((alarm) => { } }); +chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { + plusHostedCheckoutSuccessManager?.handleTabUpdated(tabId, changeInfo, tab).catch((err) => { + console.error(LOG_PREFIX, 'Failed to process PayPal hosted checkout success page:', err); + }); +}); + chrome.runtime.onStartup.addListener(() => { migrateLegacyAccountContributionState().catch((err) => { console.error(LOG_PREFIX, 'Failed to migrate legacy account contribution state on startup:', err); diff --git a/background/plus-hosted-checkout-success.js b/background/plus-hosted-checkout-success.js new file mode 100644 index 0000000..8bdb2e9 --- /dev/null +++ b/background/plus-hosted-checkout-success.js @@ -0,0 +1,153 @@ +(function attachPlusHostedCheckoutSuccess(root, factory) { + root.MultiPagePlusHostedCheckoutSuccess = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createPlusHostedCheckoutSuccessModule() { + const PAYMENT_SUCCESS_URL_PATTERN = /^https:\/\/(?:chatgpt\.com|www\.chatgpt\.com|chat\.openai\.com)\/(?:backend-api\/)?payments\/success(?:[/?#]|$)/i; + const PLUS_CHECKOUT_NODE_ID = 'plus-checkout-create'; + const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal'; + + function normalizeString(value = '') { + return String(value || '').trim(); + } + + function normalizePaymentMethod(value = '') { + return normalizeString(value).toLowerCase(); + } + + function isPaymentSuccessUrl(url = '') { + return PAYMENT_SUCCESS_URL_PATTERN.test(normalizeString(url)); + } + + function isRunnableNodeStatus(value = '') { + const normalized = normalizeString(value).toLowerCase(); + return !normalized || normalized === 'pending' || normalized === 'running'; + } + + function isHostedCheckoutSuccessWaitActive(state = {}, tabId = null) { + if (!state || typeof state !== 'object') { + return false; + } + if (!state.plusModeEnabled) { + return false; + } + if (normalizePaymentMethod(state.plusPaymentMethod) !== PLUS_PAYMENT_METHOD_PAYPAL) { + return false; + } + if (state.plusHostedCheckoutCompletionPending !== true) { + return false; + } + + const checkoutTabId = Number(state.plusCheckoutTabId); + if (!Number.isInteger(checkoutTabId) || checkoutTabId <= 0) { + return false; + } + if (tabId !== null && checkoutTabId !== Number(tabId)) { + return false; + } + + return isRunnableNodeStatus(state.nodeStatuses?.[PLUS_CHECKOUT_NODE_ID]); + } + + function createPlusHostedCheckoutSuccessManager(deps = {}) { + const { + addLog: rawAddLog = async () => {}, + completeNodeFromBackground = null, + failNodeFromBackground = null, + getState = async () => ({}), + setState = async () => {}, + } = deps; + + const activeTabIds = new Set(); + + function addLog(message, level = 'info', options = {}) { + return rawAddLog(message, level, { + stepKey: PLUS_CHECKOUT_NODE_ID, + nodeId: PLUS_CHECKOUT_NODE_ID, + ...(options && typeof options === 'object' ? options : {}), + }); + } + + async function completeFromSuccessTab(tabId, successUrl = '') { + const numericTabId = Number(tabId); + if (!Number.isInteger(numericTabId) || numericTabId <= 0) { + return null; + } + if (activeTabIds.has(numericTabId)) { + return null; + } + activeTabIds.add(numericTabId); + + try { + const initialState = await getState(); + if (!isHostedCheckoutSuccessWaitActive(initialState, numericTabId)) { + return null; + } + + const latestState = await getState(); + if (!isHostedCheckoutSuccessWaitActive(latestState, numericTabId)) { + return null; + } + + const normalizedSuccessUrl = normalizeString(successUrl); + await setState({ + plusReturnUrl: normalizedSuccessUrl, + }); + await addLog('Detected ChatGPT payment success page; continuing Plus session import flow.', 'ok'); + + if (typeof completeNodeFromBackground === 'function') { + await completeNodeFromBackground(PLUS_CHECKOUT_NODE_ID, { + plusReturnUrl: normalizedSuccessUrl, + plusHostedCheckoutCompleted: true, + }); + } + + await setState({ + plusHostedCheckoutCompletionPending: false, + plusHostedCheckoutCompleted: true, + }); + + return { + completed: true, + plusReturnUrl: normalizedSuccessUrl, + }; + } catch (error) { + const message = normalizeString(error?.message) || 'unknown error'; + await addLog(`Failed to continue after ChatGPT payment success page: ${message}`, 'error'); + if (typeof failNodeFromBackground === 'function') { + await failNodeFromBackground(PLUS_CHECKOUT_NODE_ID, message); + return { + completed: false, + failed: true, + message, + }; + } + throw error; + } finally { + activeTabIds.delete(numericTabId); + } + } + + async function handleTabUpdated(tabId, changeInfo = {}, tab = {}) { + if (changeInfo?.status !== 'complete' && tab?.status !== 'complete') { + return null; + } + const nextUrl = normalizeString(changeInfo?.url || tab?.url); + if (!isPaymentSuccessUrl(nextUrl)) { + return null; + } + return completeFromSuccessTab(tabId, nextUrl); + } + + return { + completeFromSuccessTab, + handleTabUpdated, + isHostedCheckoutSuccessWaitActive, + isPaymentSuccessUrl, + }; + } + + return { + createPlusHostedCheckoutSuccessManager, + isHostedCheckoutSuccessWaitActive, + isPaymentSuccessUrl, + }; +}); diff --git a/background/steps/create-plus-checkout.js b/background/steps/create-plus-checkout.js index 3e5fb30..f4a08fd 100644 --- a/background/steps/create-plus-checkout.js +++ b/background/steps/create-plus-checkout.js @@ -7,6 +7,8 @@ const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal'; const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper'; + const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session'; + const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session'; const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top'; const GPC_HELPER_PHONE_MODE_AUTO = 'auto'; const GPC_HELPER_PHONE_MODE_MANUAL = 'manual'; @@ -47,6 +49,17 @@ return normalized === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_PAYMENT_METHOD_GOPAY : PLUS_PAYMENT_METHOD_PAYPAL; } + function normalizePlusAccountAccessStrategy(value = '') { + const normalized = String(value || '').trim().toLowerCase(); + if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { + return PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION; + } + if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { + return PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION; + } + return 'oauth'; + } + function getCheckoutModeLabel(state = {}) { const paymentMethod = normalizePlusPaymentMethod(state?.plusPaymentMethod); if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) { @@ -63,6 +76,25 @@ return paymentMethod === PLUS_PAYMENT_METHOD_GOPAY ? 'GoPay' : 'PayPal'; } + function shouldWaitForHostedCheckoutSuccess(state = {}, paymentMethod = PLUS_PAYMENT_METHOD_PAYPAL) { + if (!state?.plusModeEnabled) { + return false; + } + if (normalizePlusPaymentMethod(paymentMethod) !== PLUS_PAYMENT_METHOD_PAYPAL) { + return false; + } + const signupMethod = String(state?.resolvedSignupMethod || state?.signupMethod || 'email').trim().toLowerCase(); + if (signupMethod === 'phone') { + return false; + } + if (Boolean(state?.accountContributionEnabled)) { + return true; + } + const strategy = normalizePlusAccountAccessStrategy(state?.plusAccountAccessStrategy); + return strategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION + || strategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION; + } + async function openFreshChatGptTabForCheckoutCreate() { const tab = typeof createAutomationTab === 'function' ? await createAutomationTab({ url: PLUS_CHECKOUT_ENTRY_URL, active: true }) @@ -439,6 +471,8 @@ plusCheckoutCountry: result.country || 'ID', plusCheckoutCurrency: result.currency || 'IDR', plusCheckoutSource: result.checkoutSource, + plusHostedCheckoutCompletionPending: false, + plusHostedCheckoutCompleted: false, gopayHelperTaskId: result.taskId, gopayHelperTaskStatus: result.taskStatus, gopayHelperStatusText: result.statusText, @@ -484,10 +518,14 @@ logMessage: '步骤 6:正在等待 ChatGPT 页面完成加载,再继续创建订阅页...', }); + const waitForHostedSuccess = shouldWaitForHostedCheckoutSuccess(state, paymentMethod); const result = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, { type: 'CREATE_PLUS_CHECKOUT', source: 'background', - payload: { paymentMethod }, + payload: { + paymentMethod, + ...(waitForHostedSuccess ? { hostedCheckoutMode: true } : {}), + }, }); if (result?.error) { @@ -496,9 +534,10 @@ if (!result?.checkoutUrl) { throw new Error(`步骤 6:${checkoutModeLabel}未返回可用的订阅链接。`); } + const checkoutUrl = String(result.checkoutUrl || '').trim(); await addLog(`步骤 6:${checkoutModeLabel}已创建,正在打开订阅页面...`, 'ok'); - await chrome.tabs.update(tabId, { url: result.checkoutUrl, active: true }); + await chrome.tabs.update(tabId, { url: checkoutUrl, active: true }); await waitForTabCompleteUntilStopped(tabId); await sleepWithStop(1000); await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, { @@ -509,14 +548,22 @@ await setState({ plusCheckoutTabId: tabId, - plusCheckoutUrl: result.checkoutUrl, + plusCheckoutUrl: checkoutUrl, plusCheckoutCountry: result.country || 'DE', plusCheckoutCurrency: result.currency || 'EUR', plusCheckoutSource: '', + plusReturnUrl: '', + plusHostedCheckoutCompletionPending: waitForHostedSuccess, + plusHostedCheckoutCompleted: false, }); await addLog(`步骤 6:Plus Checkout 页面已就绪(${paymentMethodLabel} / ${result.country || 'DE'} ${result.currency || 'EUR'}),准备继续下一步。`, 'info'); + if (waitForHostedSuccess) { + await addLog('Step 6: PayPal hosted checkout is open; waiting for the ChatGPT payment success page before importing the Plus session.', 'info'); + return; + } + await completeNodeFromBackground('plus-checkout-create', { plusCheckoutCountry: result.country || 'DE', plusCheckoutCurrency: result.currency || 'EUR', diff --git a/content/plus-checkout.js b/content/plus-checkout.js index b3f7f2a..bc5891e 100644 --- a/content/plus-checkout.js +++ b/content/plus-checkout.js @@ -616,10 +616,12 @@ function writeGoPayDiagnostics(reason, level = 'info') { return writePaymentMethodDiagnostics(PLUS_PAYMENT_METHOD_GOPAY, reason, level); } -function buildPlusCheckoutPayload(paymentMethod = PLUS_PAYMENT_METHOD_PAYPAL) { +function buildPlusCheckoutPayload(paymentMethod = PLUS_PAYMENT_METHOD_PAYPAL, options = {}) { const config = getPaymentMethodConfig(paymentMethod); + const hostedCheckoutMode = config.id === PLUS_PAYMENT_METHOD_PAYPAL && Boolean(options.hostedCheckoutMode); return { ...JSON.parse(JSON.stringify(PLUS_CHECKOUT_PAYLOAD_BASE)), + checkout_ui_mode: hostedCheckoutMode ? 'hosted' : 'custom', billing_details: { ...config.billingDetails, }, @@ -635,6 +637,31 @@ function buildPlusCheckoutUrl(checkoutSessionId, paymentMethod = PLUS_PAYMENT_ME return `https://chatgpt.com/checkout/${config.checkoutMerchantPath}/${sessionId}`; } +function findHostedCheckoutUrl(payload = {}) { + const queue = [payload]; + const seen = new Set(); + while (queue.length) { + const current = queue.shift(); + if (!current || typeof current !== 'object' || seen.has(current)) { + continue; + } + seen.add(current); + + const values = Array.isArray(current) ? current : Object.values(current); + for (const value of values) { + if (typeof value === 'string') { + const candidate = value.trim(); + if (/^https:\/\/(?:pay\.openai\.com|checkout\.stripe\.com)\/c\/pay\//i.test(candidate)) { + return candidate; + } + } else if (value && typeof value === 'object') { + queue.push(value); + } + } + } + return ''; +} + async function createPlusCheckoutSession(options = {}) { await waitForDocumentComplete(); log('Plus:正在读取 ChatGPT 登录会话...'); @@ -650,7 +677,8 @@ async function createPlusCheckoutSession(options = {}) { log('Plus:正在创建 checkout 会话...'); const paymentMethod = normalizePlusPaymentMethod(options.paymentMethod); - const checkoutPayload = buildPlusCheckoutPayload(paymentMethod); + const hostedCheckoutMode = Boolean(options.hostedCheckoutMode); + const checkoutPayload = buildPlusCheckoutPayload(paymentMethod, { hostedCheckoutMode }); const response = await fetch('https://chatgpt.com/backend-api/payments/checkout', { method: 'POST', credentials: 'include', @@ -666,9 +694,16 @@ async function createPlusCheckoutSession(options = {}) { const detail = data?.detail || data?.message || `HTTP ${response.status}`; throw new Error(`创建 Plus Checkout 失败:${detail}`); } + const hostedCheckoutUrl = findHostedCheckoutUrl(data); + const fallbackCheckoutUrl = buildPlusCheckoutUrl(data.checkout_session_id, paymentMethod); + const checkoutUrl = hostedCheckoutMode && paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL && hostedCheckoutUrl + ? hostedCheckoutUrl + : fallbackCheckoutUrl; return { - checkoutUrl: buildPlusCheckoutUrl(data.checkout_session_id, paymentMethod), + checkoutUrl, + hostedCheckoutUrl, + fallbackCheckoutUrl, country: checkoutPayload.billing_details.country, currency: checkoutPayload.billing_details.currency, }; diff --git a/data/step-definitions.js b/data/step-definitions.js index 27b0a0a..3db0ac7 100644 --- a/data/step-definitions.js +++ b/data/step-definitions.js @@ -32,6 +32,7 @@ { id: 8, order: 80, key: 'paypal-approve', title: 'PayPal 登录与授权', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-approve' }, { id: 9, order: 90, key: 'plus-checkout-return', title: '订阅回跳确认', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-return' }, ]; + const PLUS_PAYPAL_SESSION_PREFIX_STEP_DEFINITIONS = PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS.slice(0, 6); const PLUS_GOPAY_PREFIX_STEP_DEFINITIONS = [ { id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' }, @@ -168,16 +169,16 @@ const NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true }); const PLUS_PAYPAL_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL); const PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS = createOpenAiSteps( - PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, - 10, - 100, + PLUS_PAYPAL_SESSION_PREFIX_STEP_DEFINITIONS, + 7, + 70, SIGNUP_METHOD_EMAIL, { plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION } ); const PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS = createOpenAiSteps( - PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, - 10, - 100, + PLUS_PAYPAL_SESSION_PREFIX_STEP_DEFINITIONS, + 7, + 70, SIGNUP_METHOD_EMAIL, { plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION } ); diff --git a/shared/flow-capabilities.js b/shared/flow-capabilities.js index d5f430f..9c7eadf 100644 --- a/shared/flow-capabilities.js +++ b/shared/flow-capabilities.js @@ -12,6 +12,11 @@ const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth'; const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session'; const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session'; + const OPENAI_PLUS_ACCOUNT_ACCESS_STRATEGIES = Object.freeze([ + PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH, + PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION, + PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION, + ]); const VALID_OPENAI_TARGET_IDS = Array.isArray(flowRegistryApi.OPENAI_TARGET_IDS) ? flowRegistryApi.OPENAI_TARGET_IDS.slice() : ['cpa', 'sub2api', 'codex2api']; @@ -378,24 +383,27 @@ const requestedPlusAccountAccessStrategy = normalizePlusAccountAccessStrategy( options?.plusAccountAccessStrategy ?? state?.plusAccountAccessStrategy ); - const availablePlusAccountAccessStrategies = activeFlowId === 'openai' + const canUsePlusAccountAccessStrategies = activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode) && Boolean(runtimeLocks.plusModeEnabled) - && effectiveSignupMethod === SIGNUP_METHOD_EMAIL - ? ( - Array.isArray(targetState.supportedPlusAccountAccessStrategies) - && targetState.supportedPlusAccountAccessStrategies.length > 0 - ? targetState.supportedPlusAccountAccessStrategies.slice() - : [PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH] - ) + && effectiveSignupMethod === SIGNUP_METHOD_EMAIL; + const forceSub2ApiSessionImportForContribution = canUsePlusAccountAccessStrategies + && Boolean(runtimeLocks.accountContribution); + const availablePlusAccountAccessStrategies = canUsePlusAccountAccessStrategies + ? (forceSub2ApiSessionImportForContribution + ? [PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION] + : OPENAI_PLUS_ACCOUNT_ACCESS_STRATEGIES.slice()) : [PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH]; - const effectivePlusAccountAccessStrategy = availablePlusAccountAccessStrategies.includes(requestedPlusAccountAccessStrategy) - ? requestedPlusAccountAccessStrategy - : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; + const effectivePlusAccountAccessStrategy = forceSub2ApiSessionImportForContribution + ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION + : (availablePlusAccountAccessStrategies.includes(requestedPlusAccountAccessStrategy) + ? requestedPlusAccountAccessStrategy + : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH); const canEditPlusAccountAccessStrategy = activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode) && Boolean(runtimeLocks.plusModeEnabled) && effectiveSignupMethod === SIGNUP_METHOD_EMAIL + && !forceSub2ApiSessionImportForContribution && availablePlusAccountAccessStrategies.length > 1; const visibleGroupIds = typeof flowRegistryApi.getVisibleGroupIds === 'function' && isRegisteredFlowId(activeFlowId) diff --git a/shared/settings-schema.js b/shared/settings-schema.js index 2f39824..d9a7070 100644 --- a/shared/settings-schema.js +++ b/shared/settings-schema.js @@ -106,7 +106,7 @@ plus: { plusModeEnabled: false, plusPaymentMethod: 'paypal', - plusAccountAccessStrategy: 'oauth', + plusAccountAccessStrategy: 'sub2api_codex_session', }, autoRun: { stepExecutionRange: { diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 87d721b..39c82c2 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -320,7 +320,7 @@ - 当前来源仅支持 OAuth + Plus 模式未启用