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 模式未启用
diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js
index eec0060..0fe3a03 100644
--- a/sidepanel/sidepanel.js
+++ b/sidepanel/sidepanel.js
@@ -555,7 +555,7 @@ const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL;
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 DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
+const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION;
const SIGNUP_METHOD_EMAIL = 'email';
const SIGNUP_METHOD_PHONE = 'phone';
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
@@ -9312,31 +9312,8 @@ function updatePlusModeUI() {
selectPlusAccountAccessStrategy.setAttribute('aria-disabled', String(selectPlusAccountAccessStrategy.disabled));
}
if (typeof plusAccountAccessStrategyCaption !== 'undefined' && plusAccountAccessStrategyCaption) {
- if (!enabled) {
- plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth';
- } else if (!canEditPlusAccountAccessStrategy) {
- plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth';
- } else if (effectivePlusAccountAccessStrategy === sub2apiSessionStrategyValue) {
- plusAccountAccessStrategyCaption.textContent = '复用当前 Plus 已登录会话,直接导入到 SUB2API';
- } else if (effectivePlusAccountAccessStrategy === oauthStrategyValue) {
- plusAccountAccessStrategyCaption.textContent = '通过 OAuth 回调创建 SUB2API 账号';
- } else {
- plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth';
- }
- }
- if (typeof plusAccountAccessStrategyCaption !== 'undefined' && plusAccountAccessStrategyCaption) {
- if (!enabled || !canEditPlusAccountAccessStrategy) {
- plusAccountAccessStrategyCaption.textContent = '当前来源仅支持 OAuth';
- } else {
- plusAccountAccessStrategyCaption.textContent = describePlusAccountAccessStrategy(
- effectivePlusAccountAccessStrategy,
- effectiveTargetId
- );
- }
- }
- if (typeof plusAccountAccessStrategyCaption !== 'undefined' && plusAccountAccessStrategyCaption) {
- plusAccountAccessStrategyCaption.textContent = !enabled || !canEditPlusAccountAccessStrategy
- ? '当前来源仅支持 OAuth'
+ plusAccountAccessStrategyCaption.textContent = !enabled
+ ? 'Plus 模式未启用'
: describePlusAccountAccessStrategy(
effectivePlusAccountAccessStrategy,
effectiveTargetId
diff --git a/tests/background-effective-plus-account-access-strategy.test.js b/tests/background-effective-plus-account-access-strategy.test.js
index aae5b48..2b837e5 100644
--- a/tests/background-effective-plus-account-access-strategy.test.js
+++ b/tests/background-effective-plus-account-access-strategy.test.js
@@ -55,6 +55,8 @@ function extractFunction(name) {
function createHarness() {
return new Function(`
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
+const SIGNUP_METHOD_EMAIL = 'email';
+const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
const workflowEngine = null;
const self = {
MultiPageStepDefinitions: {
@@ -120,13 +122,10 @@ function isPlusModeState(state = {}) {
return Boolean(state?.plusModeEnabled);
}
function resolveCurrentFlowCapabilities(state = {}, options = {}) {
- const normalizedPanelMode = String(options.panelMode || '').trim().toLowerCase();
const requestedStrategy = normalizePlusAccountAccessStrategy(state.plusAccountAccessStrategy);
- const effectiveStrategy = normalizedPanelMode === 'sub2api'
- ? (requestedStrategy === 'sub2api_codex_session' ? 'sub2api_codex_session' : 'oauth')
- : (normalizedPanelMode === 'cpa'
- ? (requestedStrategy === 'cpa_codex_session' ? 'cpa_codex_session' : 'oauth')
- : 'oauth');
+ const effectiveStrategy = state.accountContributionEnabled && state.plusModeEnabled
+ ? 'sub2api_codex_session'
+ : requestedStrategy;
return {
effectivePanelMode: options.panelMode,
effectivePlusAccountAccessStrategy: effectiveStrategy,
@@ -153,12 +152,12 @@ return {
`)();
}
-test('background step resolution keeps SUB2API session tail only when the effective Plus target supports it', () => {
+test('background step resolution keeps SUB2API session tail independent from the current panel mode', () => {
const api = createHarness();
const state = {
activeFlowId: 'openai',
flowId: 'openai',
- panelMode: 'sub2api',
+ panelMode: 'cpa',
plusModeEnabled: true,
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'sub2api_codex_session',
@@ -205,7 +204,7 @@ test('background step resolution keeps CPA session tail when the effective Plus
]);
});
-test('background step resolution falls back to OAuth tail when the requested session strategy is not effective for the current panel mode', () => {
+test('background step resolution keeps SUB2API session tail even when the current panel mode is CPA', () => {
const api = createHarness();
const state = {
activeFlowId: 'openai',
@@ -220,12 +219,34 @@ test('background step resolution falls back to OAuth tail when the requested ses
const resolvedState = api.buildResolvedStepDefinitionState(state);
const nodeIds = api.getNodeIdsForState(state);
- assert.equal(resolvedState.plusAccountAccessStrategy, 'oauth');
- assert.equal(nodeIds.includes('sub2api-session-import'), false);
- assert.deepStrictEqual(nodeIds.slice(-4), [
- 'oauth-login',
- 'fetch-login-code',
- 'confirm-oauth',
- 'platform-verify',
+ assert.equal(resolvedState.plusAccountAccessStrategy, 'sub2api_codex_session');
+ assert.deepStrictEqual(nodeIds, [
+ 'open-chatgpt',
+ 'plus-checkout-create',
+ 'plus-checkout-billing',
+ 'paypal-approve',
+ 'plus-checkout-return',
+ 'sub2api-session-import',
]);
});
+
+test('background contribution Plus mode forces SUB2API session import over a CPA session request', () => {
+ const api = createHarness();
+ const state = {
+ activeFlowId: 'openai',
+ flowId: 'openai',
+ panelMode: 'cpa',
+ plusModeEnabled: true,
+ accountContributionEnabled: true,
+ plusPaymentMethod: 'paypal',
+ plusAccountAccessStrategy: 'cpa_codex_session',
+ signupMethod: 'email',
+ };
+
+ const resolvedState = api.buildResolvedStepDefinitionState(state);
+ const nodeIds = api.getNodeIdsForState(state);
+
+ assert.equal(resolvedState.plusAccountAccessStrategy, 'sub2api_codex_session');
+ assert.equal(nodeIds.includes('cpa-session-import'), false);
+ assert.equal(nodeIds.at(-1), 'sub2api-session-import');
+});
diff --git a/tests/background-message-router-module.test.js b/tests/background-message-router-module.test.js
index cc0908e..a9938fe 100644
--- a/tests/background-message-router-module.test.js
+++ b/tests/background-message-router-module.test.js
@@ -453,7 +453,7 @@ test('SAVE_SETTING rebuilds Plus node statuses when the account access strategy
});
});
-test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effective strategy back to OAuth', async () => {
+test('SAVE_SETTING rebuilds Plus node statuses without forcing the Plus strategy back to OAuth', async () => {
const source = fs.readFileSync('background/message-router.js', 'utf8');
const globalScope = { console };
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
@@ -491,8 +491,7 @@ test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effect
: {},
broadcastDataUpdate: (payload) => broadcasts.push(payload),
getNodeIdsForState: (nextState = {}) => (
- String(nextState.panelMode || '').trim() === 'sub2api'
- && String(nextState.plusAccountAccessStrategy || '').trim() === 'sub2api_codex_session'
+ String(nextState.plusAccountAccessStrategy || '').trim() === 'sub2api_codex_session'
? [
'open-chatgpt',
'plus-checkout-create',
@@ -532,61 +531,24 @@ test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effect
assert.equal(response.ok, true);
assert.equal(state.panelMode, 'cpa');
assert.equal(state.plusAccountAccessStrategy, 'sub2api_codex_session');
- assert.equal(state.currentNodeId, '');
- assert.equal(state.oauthUrl, null);
- assert.equal(state.localhostUrl, null);
- assert.equal(state.sub2apiSessionId, null);
- assert.equal(state.plusManualConfirmationPending, false);
- assert.equal(state.plusManualConfirmationMessage, '');
+ assert.equal(state.currentNodeId, 'sub2api-session-import');
+ assert.equal(state.oauthUrl, 'https://oauth.example/current');
+ assert.equal(state.localhostUrl, 'http://localhost:38080/callback');
+ assert.equal(state.sub2apiSessionId, 'sub-session');
+ assert.equal(state.plusManualConfirmationPending, true);
+ assert.equal(state.plusManualConfirmationMessage, '完成后继续导入当前 ChatGPT 会话到 SUB2API。');
assert.deepStrictEqual(state.nodeStatuses, {
- 'open-chatgpt': 'pending',
- 'plus-checkout-create': 'pending',
- 'plus-checkout-billing': 'pending',
- 'paypal-approve': 'pending',
- 'plus-checkout-return': 'pending',
- 'oauth-login': 'pending',
- 'fetch-login-code': 'pending',
- 'post-login-phone-verification': 'pending',
- 'confirm-oauth': 'pending',
- 'platform-verify': 'pending',
+ 'open-chatgpt': 'completed',
+ 'plus-checkout-create': 'completed',
+ 'plus-checkout-billing': 'completed',
+ 'paypal-approve': 'completed',
+ 'plus-checkout-return': 'completed',
+ 'sub2api-session-import': 'running',
});
- assert.equal(Object.prototype.hasOwnProperty.call(state.nodeStatuses, 'sub2api-session-import'), false);
+ assert.equal(Object.prototype.hasOwnProperty.call(state.nodeStatuses, 'oauth-login'), false);
assert.deepStrictEqual(broadcasts.at(-1), {
panelMode: 'cpa',
signupMethod: 'email',
- oauthUrl: null,
- localhostUrl: null,
- oauthFlowDeadlineAt: null,
- oauthFlowDeadlineSourceUrl: null,
- cpaOAuthState: null,
- cpaManagementOrigin: null,
- sub2apiSessionId: null,
- sub2apiOAuthState: null,
- sub2apiGroupId: null,
- sub2apiGroupIds: [],
- sub2apiDraftName: null,
- sub2apiProxyId: null,
- codex2apiSessionId: null,
- codex2apiOAuthState: null,
- plusManualConfirmationPending: false,
- plusManualConfirmationRequestId: '',
- plusManualConfirmationStep: 0,
- plusManualConfirmationMethod: '',
- plusManualConfirmationTitle: '',
- plusManualConfirmationMessage: '',
- nodeStatuses: {
- 'open-chatgpt': 'pending',
- 'plus-checkout-create': 'pending',
- 'plus-checkout-billing': 'pending',
- 'paypal-approve': 'pending',
- 'plus-checkout-return': 'pending',
- 'oauth-login': 'pending',
- 'fetch-login-code': 'pending',
- 'post-login-phone-verification': 'pending',
- 'confirm-oauth': 'pending',
- 'platform-verify': 'pending',
- },
- currentNodeId: '',
});
});
diff --git a/tests/flow-capabilities-module.test.js b/tests/flow-capabilities-module.test.js
index 0f9befe..3f3b729 100644
--- a/tests/flow-capabilities-module.test.js
+++ b/tests/flow-capabilities-module.test.js
@@ -212,7 +212,7 @@ test('flow capability registry exposes editable Plus account access strategies f
assert.deepEqual(
capabilityState.availablePlusAccountAccessStrategies,
- ['oauth', 'sub2api_codex_session']
+ ['oauth', 'cpa_codex_session', 'sub2api_codex_session']
);
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'sub2api_codex_session');
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'sub2api_codex_session');
@@ -236,7 +236,7 @@ test('flow capability registry exposes editable Plus account access strategies f
assert.deepEqual(
capabilityState.availablePlusAccountAccessStrategies,
- ['oauth', 'cpa_codex_session']
+ ['oauth', 'cpa_codex_session', 'sub2api_codex_session']
);
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'cpa_codex_session');
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'cpa_codex_session');
@@ -244,7 +244,7 @@ test('flow capability registry exposes editable Plus account access strategies f
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'cpa_codex_session');
});
-test('flow capability registry forces OAuth when the current target only supports OAuth', () => {
+test('flow capability registry keeps Plus session strategies independent from the current OpenAI target', () => {
const api = loadApi();
const registry = api.createFlowCapabilityRegistry();
@@ -254,13 +254,38 @@ test('flow capability registry forces OAuth when the current target only support
openaiIntegrationTargetId: 'codex2api',
signupMethod: 'email',
plusModeEnabled: true,
+ plusAccountAccessStrategy: 'sub2api_codex_session',
+ },
+ });
+
+ assert.deepEqual(
+ capabilityState.availablePlusAccountAccessStrategies,
+ ['oauth', 'cpa_codex_session', 'sub2api_codex_session']
+ );
+ assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'sub2api_codex_session');
+ assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'sub2api_codex_session');
+ assert.equal(capabilityState.canEditPlusAccountAccessStrategy, true);
+ assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'sub2api_codex_session');
+});
+
+test('flow capability registry forces SUB2API Plus session import during contribution mode', () => {
+ const api = loadApi();
+ const registry = api.createFlowCapabilityRegistry();
+
+ const capabilityState = registry.resolveSidepanelCapabilities({
+ state: {
+ activeFlowId: 'openai',
+ openaiIntegrationTargetId: 'cpa',
+ signupMethod: 'email',
+ plusModeEnabled: true,
+ accountContributionEnabled: true,
plusAccountAccessStrategy: 'cpa_codex_session',
},
});
- assert.deepEqual(capabilityState.availablePlusAccountAccessStrategies, ['oauth']);
+ assert.deepEqual(capabilityState.availablePlusAccountAccessStrategies, ['sub2api_codex_session']);
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'cpa_codex_session');
- assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'oauth');
+ assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'sub2api_codex_session');
assert.equal(capabilityState.canEditPlusAccountAccessStrategy, false);
- assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'oauth');
+ assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'sub2api_codex_session');
});
diff --git a/tests/hosted-checkout-background-wiring.test.js b/tests/hosted-checkout-background-wiring.test.js
new file mode 100644
index 0000000..d230a67
--- /dev/null
+++ b/tests/hosted-checkout-background-wiring.test.js
@@ -0,0 +1,120 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+
+const source = fs.readFileSync('background.js', 'utf8');
+
+function extractFunction(name) {
+ const markers = [`async function ${name}(`, `function ${name}(`];
+ const start = markers
+ .map((marker) => source.indexOf(marker))
+ .find((index) => index >= 0);
+ if (start < 0) {
+ throw new Error(`missing function ${name}`);
+ }
+
+ let parenDepth = 0;
+ let signatureEnded = false;
+ let braceStart = -1;
+ for (let index = start; index < source.length; index += 1) {
+ const ch = source[index];
+ if (ch === '(') {
+ parenDepth += 1;
+ } else if (ch === ')') {
+ parenDepth -= 1;
+ if (parenDepth === 0) {
+ signatureEnded = true;
+ }
+ } else if (ch === '{' && signatureEnded) {
+ braceStart = index;
+ break;
+ }
+ }
+
+ let depth = 0;
+ let end = braceStart;
+ for (; end < source.length; end += 1) {
+ const ch = source[end];
+ if (ch === '{') depth += 1;
+ if (ch === '}') {
+ depth -= 1;
+ if (depth === 0) {
+ end += 1;
+ break;
+ }
+ }
+ }
+
+ return source.slice(start, end);
+}
+
+test('background routes plus-checkout-create through a completion signal for hosted checkout', () => {
+ const backgroundSetStart = source.indexOf('const AUTO_RUN_BACKGROUND_COMPLETED_STEP_KEYS = new Set([');
+ const signalSetStart = source.indexOf('const STEP_COMPLETION_SIGNAL_STEP_KEYS = new Set([');
+ const timeoutMapStart = source.indexOf('const STEP_COMPLETION_SIGNAL_TIMEOUTS_BY_STEP_KEY = new Map([');
+ const backgroundSetSource = source.slice(backgroundSetStart, signalSetStart);
+ const signalSetSource = source.slice(signalSetStart, timeoutMapStart);
+
+ assert.doesNotMatch(backgroundSetSource, /'plus-checkout-create'/);
+ assert.match(signalSetSource, /'plus-checkout-create'/);
+});
+
+test('background gives PayPal session-import hosted checkout a long completion and idle window', () => {
+ const bundle = `
+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 AUTO_RUN_SIGNAL_COMPLETION_TIMEOUT_MS = 120000;
+const AUTO_RUN_STEP_IDLE_LOG_TIMEOUT_MS = 5 * 60 * 1000;
+const HOSTED_CHECKOUT_SUCCESS_SIGNAL_TIMEOUT_MS = 30 * 60 * 1000;
+const STEP_COMPLETION_SIGNAL_TIMEOUTS_BY_STEP_KEY = new Map([
+ ['fill-profile', 150000],
+ ['gopay-subscription-confirm', 1800000],
+]);
+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 PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
+}
+function getNodeExecutionKeyForState(nodeId, state = {}) {
+ return String(state?.nodes?.[nodeId]?.executeKey || nodeId || '').trim();
+}
+${extractFunction('isHostedCheckoutSuccessCompletionNode')}
+${extractFunction('getNodeCompletionSignalTimeoutMs')}
+${extractFunction('getAutoRunNodeIdleLogTimeoutMs')}
+return { isHostedCheckoutSuccessCompletionNode, getNodeCompletionSignalTimeoutMs, getAutoRunNodeIdleLogTimeoutMs };
+`;
+ const api = new Function(bundle)();
+ const hostedState = {
+ plusModeEnabled: true,
+ plusPaymentMethod: 'paypal',
+ signupMethod: 'email',
+ plusAccountAccessStrategy: 'sub2api_codex_session',
+ };
+
+ assert.equal(api.isHostedCheckoutSuccessCompletionNode('plus-checkout-create', hostedState), true);
+ assert.equal(api.getNodeCompletionSignalTimeoutMs('plus-checkout-create', hostedState), 30 * 60 * 1000);
+ assert.equal(api.getAutoRunNodeIdleLogTimeoutMs('plus-checkout-create', hostedState), 30 * 60 * 1000);
+ assert.equal(api.isHostedCheckoutSuccessCompletionNode('plus-checkout-create', {
+ ...hostedState,
+ accountContributionEnabled: true,
+ plusAccountAccessStrategy: 'oauth',
+ }), true);
+ assert.equal(api.isHostedCheckoutSuccessCompletionNode('plus-checkout-create', { ...hostedState, plusAccountAccessStrategy: 'oauth' }), false);
+ assert.equal(api.isHostedCheckoutSuccessCompletionNode('plus-checkout-create', { ...hostedState, signupMethod: 'phone' }), false);
+ assert.equal(api.isHostedCheckoutSuccessCompletionNode('plus-checkout-create', {
+ ...hostedState,
+ accountContributionEnabled: true,
+ signupMethod: 'phone',
+ }), false);
+ assert.equal(api.isHostedCheckoutSuccessCompletionNode('plus-checkout-create', { ...hostedState, plusPaymentMethod: 'gopay' }), false);
+ assert.equal(api.getNodeCompletionSignalTimeoutMs('plus-checkout-create', { ...hostedState, plusAccountAccessStrategy: 'oauth' }), 120000);
+ assert.equal(api.getAutoRunNodeIdleLogTimeoutMs('plus-checkout-create', { ...hostedState, plusAccountAccessStrategy: 'oauth' }), 5 * 60 * 1000);
+});
+
+test('background imports and registers the hosted checkout success manager', () => {
+ assert.match(source, /importScripts\([\s\S]*'background\/plus-hosted-checkout-success\.js'/);
+ assert.match(source, /createPlusHostedCheckoutSuccessManager\(\{/);
+ assert.match(source, /chrome\.tabs\.onUpdated\.addListener\(\(tabId, changeInfo, tab\) => \{[\s\S]*plusHostedCheckoutSuccessManager\?\.handleTabUpdated/);
+});
diff --git a/tests/plus-account-access-strategy.test.js b/tests/plus-account-access-strategy.test.js
index 3998edc..7d8966f 100644
--- a/tests/plus-account-access-strategy.test.js
+++ b/tests/plus-account-access-strategy.test.js
@@ -115,13 +115,14 @@ return {
`)();
}
-test('sidepanel keeps requested Plus account strategy while OAuth-only targets force the effective value', () => {
+test('sidepanel keeps requested SUB2API session strategy independent from the selected target', () => {
const api = buildHarness(
`{
canShowPlusSettings: true,
runtimeLocks: { plusModeEnabled: true },
- canEditPlusAccountAccessStrategy: false,
- effectivePlusAccountAccessStrategy: 'oauth',
+ canEditPlusAccountAccessStrategy: true,
+ availablePlusAccountAccessStrategies: ['oauth', 'cpa_codex_session', 'sub2api_codex_session'],
+ effectivePlusAccountAccessStrategy: 'sub2api_codex_session',
}`,
`{
activeFlowId: 'openai',
@@ -134,11 +135,11 @@ test('sidepanel keeps requested Plus account strategy while OAuth-only targets f
api.updatePlusModeUI();
assert.equal(api.rowPlusAccountAccessStrategy.style.display, '');
- assert.equal(api.selectPlusAccountAccessStrategy.disabled, true);
+ assert.equal(api.selectPlusAccountAccessStrategy.disabled, false);
assert.equal(api.selectPlusAccountAccessStrategy.dataset.requestedValue, 'sub2api_codex_session');
- assert.equal(api.selectPlusAccountAccessStrategy.value, 'oauth');
+ assert.equal(api.selectPlusAccountAccessStrategy.value, 'sub2api_codex_session');
assert.equal(api.getRequestedPlusAccountAccessStrategy(), 'sub2api_codex_session');
- assert.match(api.plusAccountAccessStrategyCaption.textContent, /OAuth/);
+ assert.match(api.plusAccountAccessStrategyCaption.textContent, /SUB2API/);
});
test('sidepanel enables SUB2API session strategy selection when the current Plus target supports it', () => {
@@ -283,6 +284,7 @@ return {
plusAccountAccessStrategy: 'sub2api_codex_session',
signupMethod: 'email',
phoneSignupReloginAfterBindEmailEnabled: false,
+ accountContributionEnabled: false,
},
},
{
@@ -294,6 +296,7 @@ return {
plusAccountAccessStrategy: 'sub2api_codex_session',
signupMethod: 'email',
phoneSignupReloginAfterBindEmailEnabled: false,
+ accountContributionEnabled: false,
},
},
]);
diff --git a/tests/plus-checkout-address-input.test.js b/tests/plus-checkout-address-input.test.js
index 738aa1e..377d71f 100644
--- a/tests/plus-checkout-address-input.test.js
+++ b/tests/plus-checkout-address-input.test.js
@@ -38,7 +38,7 @@ test('plus checkout content script can be injected repeatedly on the same page',
assert.equal(context.__MULTIPAGE_PLUS_CHECKOUT_READY__, true);
});
-function createPlusCheckoutMessageHarness({ checkoutSessionId = 'cs_test_123' } = {}) {
+function createPlusCheckoutMessageHarness({ checkoutSessionId = 'cs_test_123', checkoutResponse = null } = {}) {
const attrs = new Map();
let listener = null;
const fetchCalls = [];
@@ -84,7 +84,7 @@ function createPlusCheckoutMessageHarness({ checkoutSessionId = 'cs_test_123' }
return {
ok: true,
status: 200,
- json: async () => ({ checkout_session_id: checkoutSessionId }),
+ json: async () => checkoutResponse || ({ checkout_session_id: checkoutSessionId }),
};
}
throw new Error(`unexpected fetch url: ${url}`);
@@ -124,9 +124,58 @@ test('CREATE_PLUS_CHECKOUT keeps PayPal on DE/EUR and openai_ie merchant path by
assert.equal(checkoutCall.options.headers.Authorization, 'Bearer test-access-token');
const payload = JSON.parse(checkoutCall.options.body);
assert.equal(payload.plan_name, 'chatgptplusplan');
+ assert.equal(payload.checkout_ui_mode, 'custom');
assert.deepEqual(payload.billing_details, { country: 'DE', currency: 'EUR' });
});
+test('CREATE_PLUS_CHECKOUT ignores hosted PayPal URLs outside hosted mode', async () => {
+ const harness = createPlusCheckoutMessageHarness({
+ checkoutResponse: {
+ checkout_session_id: 'cs_paypal_custom',
+ nested: {
+ hosted: 'https://pay.openai.com/c/pay/hosted_cs_paypal_custom',
+ },
+ },
+ });
+
+ const result = await harness.send({
+ type: 'CREATE_PLUS_CHECKOUT',
+ source: 'test',
+ payload: {},
+ });
+
+ assert.equal(result.ok, true);
+ assert.equal(result.checkoutUrl, 'https://chatgpt.com/checkout/openai_ie/cs_paypal_custom');
+ assert.equal(result.hostedCheckoutUrl, 'https://pay.openai.com/c/pay/hosted_cs_paypal_custom');
+ assert.equal(result.fallbackCheckoutUrl, 'https://chatgpt.com/checkout/openai_ie/cs_paypal_custom');
+});
+
+test('CREATE_PLUS_CHECKOUT prefers the hosted PayPal checkout URL only in hosted mode', async () => {
+ const harness = createPlusCheckoutMessageHarness({
+ checkoutResponse: {
+ checkout_session_id: 'cs_paypal_hosted',
+ nested: {
+ hosted: 'https://pay.openai.com/c/pay/hosted_cs_paypal',
+ },
+ },
+ });
+
+ const result = await harness.send({
+ type: 'CREATE_PLUS_CHECKOUT',
+ source: 'test',
+ payload: { hostedCheckoutMode: true },
+ });
+
+ assert.equal(result.ok, true);
+ assert.equal(result.checkoutUrl, 'https://pay.openai.com/c/pay/hosted_cs_paypal');
+ assert.equal(result.hostedCheckoutUrl, 'https://pay.openai.com/c/pay/hosted_cs_paypal');
+ assert.equal(result.fallbackCheckoutUrl, 'https://chatgpt.com/checkout/openai_ie/cs_paypal_hosted');
+
+ const checkoutCall = harness.fetchCalls.find((call) => call.url === 'https://chatgpt.com/backend-api/payments/checkout');
+ const payload = JSON.parse(checkoutCall.options.body);
+ assert.equal(payload.checkout_ui_mode, 'hosted');
+});
+
test('CREATE_PLUS_CHECKOUT uses ID/IDR and openai_llc merchant path for GoPay', async () => {
const harness = createPlusCheckoutMessageHarness({ checkoutSessionId: 'cs_gopay' });
diff --git a/tests/plus-checkout-create-wait.test.js b/tests/plus-checkout-create-wait.test.js
index 15ee2e0..4bd2ff5 100644
--- a/tests/plus-checkout-create-wait.test.js
+++ b/tests/plus-checkout-create-wait.test.js
@@ -257,6 +257,102 @@ test('Plus checkout create does not wait 20 seconds after opening checkout page'
assert.equal(events.some((event) => event.type === 'sleep' && event.ms === 20000), false);
});
+test('PayPal session-import checkout waits for the ChatGPT payment success signal', async () => {
+ const events = [];
+ const executor = api.createPlusCheckoutCreateExecutor({
+ addLog: async (message, level = 'info') => events.push({ type: 'log', message, level }),
+ chrome: {
+ tabs: {
+ create: async (payload) => {
+ events.push({ type: 'tab-create', payload });
+ return { id: 43 };
+ },
+ update: async (tabId, payload) => events.push({ type: 'tab-update', tabId, payload }),
+ },
+ },
+ completeNodeFromBackground: async (step, payload) => events.push({ type: 'complete', step, payload }),
+ ensureContentScriptReadyOnTabUntilStopped: async () => events.push({ type: 'ready' }),
+ registerTab: async (source, tabId) => events.push({ type: 'register', source, tabId }),
+ sendTabMessageUntilStopped: async (tabId, source, message) => {
+ events.push({ type: 'tab-message', tabId, source, message });
+ return {
+ checkoutUrl: 'https://pay.openai.com/c/pay/hosted_session',
+ country: 'US',
+ currency: 'USD',
+ };
+ },
+ setState: async (payload) => events.push({ type: 'set-state', payload }),
+ sleepWithStop: async (ms) => events.push({ type: 'sleep', ms }),
+ waitForTabCompleteUntilStopped: async () => events.push({ type: 'tab-complete' }),
+ });
+
+ await executor.executePlusCheckoutCreate({
+ plusModeEnabled: true,
+ plusPaymentMethod: 'paypal',
+ plusAccountAccessStrategy: 'sub2api_codex_session',
+ signupMethod: 'email',
+ });
+
+ assert.equal(events.some((event) => event.type === 'complete'), false);
+ assert.deepEqual(
+ events.find((event) => event.type === 'tab-message')?.message?.payload,
+ { paymentMethod: 'paypal', hostedCheckoutMode: true }
+ );
+ assert.deepEqual(
+ events.find((event) => event.type === 'tab-update'),
+ { type: 'tab-update', tabId: 43, payload: { url: 'https://pay.openai.com/c/pay/hosted_session', active: true } }
+ );
+ const statePayload = events.find((event) => event.type === 'set-state')?.payload || {};
+ assert.equal(statePayload.plusCheckoutTabId, 43);
+ assert.equal(statePayload.plusCheckoutUrl, 'https://pay.openai.com/c/pay/hosted_session');
+ assert.equal(statePayload.plusHostedCheckoutCompletionPending, true);
+ assert.equal(statePayload.plusHostedCheckoutCompleted, false);
+ assert.equal(statePayload.plusReturnUrl, '');
+ assert.equal(events.some((event) => event.type === 'log' && /waiting for the ChatGPT payment success page/.test(event.message)), true);
+});
+
+test('Contribution Plus checkout uses hosted success waiting even when the requested strategy is OAuth', async () => {
+ const events = [];
+ const executor = api.createPlusCheckoutCreateExecutor({
+ addLog: async () => {},
+ chrome: {
+ tabs: {
+ create: async () => ({ id: 44 }),
+ update: async (tabId, payload) => events.push({ type: 'tab-update', tabId, payload }),
+ },
+ },
+ completeNodeFromBackground: async (step, payload) => events.push({ type: 'complete', step, payload }),
+ ensureContentScriptReadyOnTabUntilStopped: async () => {},
+ registerTab: async () => {},
+ sendTabMessageUntilStopped: async (tabId, source, message) => {
+ events.push({ type: 'tab-message', tabId, source, message });
+ return {
+ checkoutUrl: 'https://pay.openai.com/c/pay/contribution_hosted_session',
+ country: 'US',
+ currency: 'USD',
+ };
+ },
+ setState: async (payload) => events.push({ type: 'set-state', payload }),
+ sleepWithStop: async () => {},
+ waitForTabCompleteUntilStopped: async () => {},
+ });
+
+ await executor.executePlusCheckoutCreate({
+ plusModeEnabled: true,
+ accountContributionEnabled: true,
+ plusPaymentMethod: 'paypal',
+ plusAccountAccessStrategy: 'oauth',
+ signupMethod: 'email',
+ });
+
+ assert.deepEqual(
+ events.find((event) => event.type === 'tab-message')?.message?.payload,
+ { paymentMethod: 'paypal', hostedCheckoutMode: true }
+ );
+ assert.equal(events.some((event) => event.type === 'complete'), false);
+ assert.equal(events.find((event) => event.type === 'set-state')?.payload?.plusHostedCheckoutCompletionPending, true);
+});
+
test('GoPay plus checkout create forwards gopay payment method to the checkout content script', async () => {
const events = [];
const executor = api.createPlusCheckoutCreateExecutor({
diff --git a/tests/plus-hosted-checkout-success.test.js b/tests/plus-hosted-checkout-success.test.js
new file mode 100644
index 0000000..9a51b6a
--- /dev/null
+++ b/tests/plus-hosted-checkout-success.test.js
@@ -0,0 +1,135 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+
+const source = fs.readFileSync('background/plus-hosted-checkout-success.js', 'utf8');
+
+function loadApi() {
+ const scope = {};
+ return new Function('self', `${source}; return self.MultiPagePlusHostedCheckoutSuccess;`)(scope);
+}
+
+function createState(overrides = {}) {
+ return {
+ plusModeEnabled: true,
+ plusPaymentMethod: 'paypal',
+ plusHostedCheckoutCompletionPending: true,
+ plusCheckoutTabId: 77,
+ nodeStatuses: {
+ 'plus-checkout-create': 'running',
+ },
+ ...overrides,
+ };
+}
+
+test('hosted checkout success manager completes plus-checkout-create on the tracked success tab', async () => {
+ const api = loadApi();
+ const events = [];
+ let state = createState();
+ const manager = api.createPlusHostedCheckoutSuccessManager({
+ addLog: async (message, level, options) => events.push({ type: 'log', message, level, options }),
+ completeNodeFromBackground: async (nodeId, payload) => events.push({ type: 'complete', nodeId, payload }),
+ getState: async () => state,
+ setState: async (payload) => {
+ events.push({ type: 'set-state', payload });
+ state = { ...state, ...payload };
+ },
+ });
+
+ const successUrl = 'https://chatgpt.com/payments/success?session_id=cs_test';
+ const result = await manager.handleTabUpdated(77, { status: 'complete' }, { url: successUrl });
+
+ assert.deepEqual(result, {
+ completed: true,
+ plusReturnUrl: successUrl,
+ });
+ assert.deepEqual(events.find((event) => event.type === 'complete'), {
+ type: 'complete',
+ nodeId: 'plus-checkout-create',
+ payload: {
+ plusReturnUrl: successUrl,
+ plusHostedCheckoutCompleted: true,
+ },
+ });
+ assert.equal(state.plusHostedCheckoutCompletionPending, false);
+ assert.equal(state.plusHostedCheckoutCompleted, true);
+ assert.equal(state.plusReturnUrl, successUrl);
+ assert.equal(events.find((event) => event.type === 'log')?.options?.nodeId, 'plus-checkout-create');
+});
+
+test('hosted checkout success manager ignores unrelated updates and inactive states', async () => {
+ const api = loadApi();
+ const events = [];
+ let state = createState();
+ const manager = api.createPlusHostedCheckoutSuccessManager({
+ completeNodeFromBackground: async () => events.push('complete'),
+ getState: async () => state,
+ setState: async () => events.push('set-state'),
+ });
+
+ assert.equal(api.isPaymentSuccessUrl('https://chatgpt.com/payments/success'), true);
+ assert.equal(api.isPaymentSuccessUrl('https://example.com/payments/success'), false);
+ assert.equal(await manager.handleTabUpdated(77, { status: 'loading' }, { url: 'https://chatgpt.com/payments/success' }), null);
+ assert.equal(await manager.handleTabUpdated(77, { status: 'complete' }, { url: 'https://chatgpt.com/' }), null);
+ assert.equal(await manager.handleTabUpdated(88, { status: 'complete' }, { url: 'https://chatgpt.com/payments/success' }), null);
+
+ state = createState({ plusHostedCheckoutCompletionPending: false });
+ assert.equal(await manager.handleTabUpdated(77, { status: 'complete' }, { url: 'https://chatgpt.com/payments/success' }), null);
+
+ state = createState({ plusHostedCheckoutCompletionPending: true, nodeStatuses: { 'plus-checkout-create': 'completed' } });
+ assert.equal(await manager.handleTabUpdated(77, { status: 'complete' }, { url: 'https://chatgpt.com/payments/success' }), null);
+ assert.deepEqual(events, []);
+});
+
+test('hosted checkout success manager deduplicates concurrent success events for one tab', async () => {
+ const api = loadApi();
+ const events = [];
+ const state = createState();
+ let releaseComplete;
+ const completeGate = new Promise((resolve) => {
+ releaseComplete = resolve;
+ });
+ const manager = api.createPlusHostedCheckoutSuccessManager({
+ addLog: async () => {},
+ completeNodeFromBackground: async (nodeId) => {
+ events.push({ type: 'complete', nodeId });
+ await completeGate;
+ },
+ getState: async () => state,
+ setState: async (payload) => events.push({ type: 'set-state', payload }),
+ });
+
+ const first = manager.handleTabUpdated(77, { status: 'complete' }, { url: 'https://chatgpt.com/payments/success' });
+ const second = manager.handleTabUpdated(77, { status: 'complete' }, { url: 'https://chatgpt.com/payments/success' });
+ await Promise.resolve();
+ releaseComplete();
+
+ const results = await Promise.all([first, second]);
+ assert.equal(results.filter(Boolean).length, 1);
+ assert.equal(events.filter((event) => event.type === 'complete').length, 1);
+});
+
+test('hosted checkout success manager fails the checkout node when continuation throws', async () => {
+ const api = loadApi();
+ const events = [];
+ const manager = api.createPlusHostedCheckoutSuccessManager({
+ addLog: async (message, level) => events.push({ type: 'log', message, level }),
+ completeNodeFromBackground: async () => {
+ throw new Error('boom');
+ },
+ failNodeFromBackground: async (nodeId, message) => events.push({ type: 'fail', nodeId, message }),
+ getState: async () => createState(),
+ setState: async () => {},
+ });
+
+ const result = await manager.handleTabUpdated(77, { status: 'complete' }, { url: 'https://chatgpt.com/payments/success' });
+
+ assert.equal(result.failed, true);
+ assert.equal(result.message, 'boom');
+ assert.deepEqual(events.find((event) => event.type === 'fail'), {
+ type: 'fail',
+ nodeId: 'plus-checkout-create',
+ message: 'boom',
+ });
+ assert.equal(events.some((event) => event.type === 'log' && event.level === 'error'), true);
+});
diff --git a/tests/sidepanel-flow-source-registry.test.js b/tests/sidepanel-flow-source-registry.test.js
index 58ab6ad..397d1ae 100644
--- a/tests/sidepanel-flow-source-registry.test.js
+++ b/tests/sidepanel-flow-source-registry.test.js
@@ -123,6 +123,7 @@ return {
plusAccountAccessStrategy: 'oauth',
signupMethod: 'email',
phoneSignupReloginAfterBindEmailEnabled: false,
+ accountContributionEnabled: false,
},
});
assert.deepEqual(api.calls[1], { type: 'render', stepIds: [88] });
diff --git a/tests/sidepanel-plus-payment-method.test.js b/tests/sidepanel-plus-payment-method.test.js
index b6560ee..027902e 100644
--- a/tests/sidepanel-plus-payment-method.test.js
+++ b/tests/sidepanel-plus-payment-method.test.js
@@ -113,7 +113,7 @@ return {
assert.deepEqual(api.getStepIds(), [7]);
assert.deepEqual(api.calls[0], {
type: 'getSteps',
- options: { activeFlowId: 'openai', plusModeEnabled: true, plusPaymentMethod: 'gopay', plusAccountAccessStrategy: 'oauth', signupMethod: 'email', phoneSignupReloginAfterBindEmailEnabled: false },
+ options: { activeFlowId: 'openai', plusModeEnabled: true, plusPaymentMethod: 'gopay', plusAccountAccessStrategy: 'oauth', signupMethod: 'email', phoneSignupReloginAfterBindEmailEnabled: false, accountContributionEnabled: false },
});
assert.deepEqual(api.calls[1], { type: 'render', stepIds: [7] });
});
@@ -303,7 +303,7 @@ return {
assert.deepEqual(api.getStepIds(), [13]);
assert.deepEqual(api.calls[0], {
type: 'getSteps',
- options: { activeFlowId: 'openai', plusModeEnabled: true, plusPaymentMethod: 'gpc-helper', plusAccountAccessStrategy: 'oauth', signupMethod: 'email', phoneSignupReloginAfterBindEmailEnabled: false },
+ options: { activeFlowId: 'openai', plusModeEnabled: true, plusPaymentMethod: 'gpc-helper', plusAccountAccessStrategy: 'oauth', signupMethod: 'email', phoneSignupReloginAfterBindEmailEnabled: false, accountContributionEnabled: false },
});
});
diff --git a/tests/step-definitions-module.test.js b/tests/step-definitions-module.test.js
index 9dfb329..1271b8c 100644
--- a/tests/step-definitions-module.test.js
+++ b/tests/step-definitions-module.test.js
@@ -276,8 +276,8 @@ test('Plus session strategy swaps the OAuth tail for a single SUB2API import nod
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'sub2api_codex_session',
},
- previousNodeId: 'plus-checkout-return',
- expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+ previousNodeId: 'plus-checkout-create',
+ expectedStepIds: [1, 2, 3, 4, 5, 6, 7],
},
{
label: 'gopay',
@@ -357,8 +357,8 @@ test('Plus session strategy swaps the OAuth tail for a single CPA import node',
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'cpa_codex_session',
},
- previousNodeId: 'plus-checkout-return',
- expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+ previousNodeId: 'plus-checkout-create',
+ expectedStepIds: [1, 2, 3, 4, 5, 6, 7],
},
{
label: 'gopay',