Revert "Integrate Plus hosted session binding"
This reverts commit 3aa9b136d3.
This commit is contained in:
+8
-77
@@ -41,7 +41,6 @@ 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',
|
||||
@@ -858,19 +857,6 @@ 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,
|
||||
@@ -882,7 +868,11 @@ function buildResolvedStepDefinitionState(state = {}) {
|
||||
? plusModeEnabled
|
||||
: Boolean(stepDefinitionOptions.plusModeEnabled),
|
||||
plusPaymentMethod,
|
||||
plusAccountAccessStrategy: effectivePlusAccountAccessStrategy,
|
||||
plusAccountAccessStrategy: normalizePlusAccountAccessStrategy(
|
||||
stepDefinitionOptions.plusAccountAccessStrategy
|
||||
?? capabilityState?.effectivePlusAccountAccessStrategy
|
||||
?? state?.plusAccountAccessStrategy
|
||||
),
|
||||
signupMethod: resolvedSignupMethod,
|
||||
resolvedSignupMethod: resolvedSignupMethod,
|
||||
phoneSignupReloginAfterBindEmailEnabled: Boolean(state?.phoneSignupReloginAfterBindEmailEnabled),
|
||||
@@ -1160,7 +1150,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
customPassword: '',
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: DEFAULT_PLUS_PAYMENT_METHOD,
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
plusAccountAccessStrategy: 'oauth',
|
||||
paypalEmail: '',
|
||||
paypalPassword: '',
|
||||
currentPayPalAccountId: '',
|
||||
@@ -10531,7 +10521,6 @@ 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]);
|
||||
@@ -10541,6 +10530,7 @@ 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',
|
||||
@@ -10568,7 +10558,6 @@ 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',
|
||||
]);
|
||||
@@ -10676,34 +10665,7 @@ 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;
|
||||
}
|
||||
@@ -10712,12 +10674,6 @@ 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);
|
||||
@@ -11018,19 +10974,10 @@ 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,
|
||||
idleTimeoutMs,
|
||||
}
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13494,16 +13441,6 @@ 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,
|
||||
@@ -15604,12 +15541,6 @@ 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);
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
(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,
|
||||
};
|
||||
});
|
||||
@@ -7,8 +7,6 @@
|
||||
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';
|
||||
@@ -49,17 +47,6 @@
|
||||
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) {
|
||||
@@ -76,25 +63,6 @@
|
||||
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 })
|
||||
@@ -471,8 +439,6 @@
|
||||
plusCheckoutCountry: result.country || 'ID',
|
||||
plusCheckoutCurrency: result.currency || 'IDR',
|
||||
plusCheckoutSource: result.checkoutSource,
|
||||
plusHostedCheckoutCompletionPending: false,
|
||||
plusHostedCheckoutCompleted: false,
|
||||
gopayHelperTaskId: result.taskId,
|
||||
gopayHelperTaskStatus: result.taskStatus,
|
||||
gopayHelperStatusText: result.statusText,
|
||||
@@ -518,14 +484,10 @@
|
||||
logMessage: '步骤 6:正在等待 ChatGPT 页面完成加载,再继续创建订阅页...',
|
||||
});
|
||||
|
||||
const waitForHostedSuccess = shouldWaitForHostedCheckoutSuccess(state, paymentMethod);
|
||||
const result = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, {
|
||||
type: 'CREATE_PLUS_CHECKOUT',
|
||||
source: 'background',
|
||||
payload: {
|
||||
paymentMethod,
|
||||
...(waitForHostedSuccess ? { hostedCheckoutMode: true } : {}),
|
||||
},
|
||||
payload: { paymentMethod },
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
@@ -534,10 +496,9 @@
|
||||
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: checkoutUrl, active: true });
|
||||
await chrome.tabs.update(tabId, { url: result.checkoutUrl, active: true });
|
||||
await waitForTabCompleteUntilStopped(tabId);
|
||||
await sleepWithStop(1000);
|
||||
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
|
||||
@@ -548,22 +509,14 @@
|
||||
|
||||
await setState({
|
||||
plusCheckoutTabId: tabId,
|
||||
plusCheckoutUrl: checkoutUrl,
|
||||
plusCheckoutUrl: result.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',
|
||||
|
||||
@@ -616,12 +616,10 @@ function writeGoPayDiagnostics(reason, level = 'info') {
|
||||
return writePaymentMethodDiagnostics(PLUS_PAYMENT_METHOD_GOPAY, reason, level);
|
||||
}
|
||||
|
||||
function buildPlusCheckoutPayload(paymentMethod = PLUS_PAYMENT_METHOD_PAYPAL, options = {}) {
|
||||
function buildPlusCheckoutPayload(paymentMethod = PLUS_PAYMENT_METHOD_PAYPAL) {
|
||||
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,
|
||||
},
|
||||
@@ -637,31 +635,6 @@ 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 登录会话...');
|
||||
@@ -677,8 +650,7 @@ async function createPlusCheckoutSession(options = {}) {
|
||||
|
||||
log('Plus:正在创建 checkout 会话...');
|
||||
const paymentMethod = normalizePlusPaymentMethod(options.paymentMethod);
|
||||
const hostedCheckoutMode = Boolean(options.hostedCheckoutMode);
|
||||
const checkoutPayload = buildPlusCheckoutPayload(paymentMethod, { hostedCheckoutMode });
|
||||
const checkoutPayload = buildPlusCheckoutPayload(paymentMethod);
|
||||
const response = await fetch('https://chatgpt.com/backend-api/payments/checkout', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
@@ -694,16 +666,9 @@ 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,
|
||||
hostedCheckoutUrl,
|
||||
fallbackCheckoutUrl,
|
||||
checkoutUrl: buildPlusCheckoutUrl(data.checkout_session_id, paymentMethod),
|
||||
country: checkoutPayload.billing_details.country,
|
||||
currency: checkoutPayload.billing_details.currency,
|
||||
};
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
{ 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' },
|
||||
@@ -169,16 +168,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_SESSION_PREFIX_STEP_DEFINITIONS,
|
||||
7,
|
||||
70,
|
||||
PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS,
|
||||
10,
|
||||
100,
|
||||
SIGNUP_METHOD_EMAIL,
|
||||
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
||||
);
|
||||
const PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS = createOpenAiSteps(
|
||||
PLUS_PAYPAL_SESSION_PREFIX_STEP_DEFINITIONS,
|
||||
7,
|
||||
70,
|
||||
PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS,
|
||||
10,
|
||||
100,
|
||||
SIGNUP_METHOD_EMAIL,
|
||||
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION }
|
||||
);
|
||||
|
||||
+11
-19
@@ -12,11 +12,6 @@
|
||||
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'];
|
||||
@@ -383,27 +378,24 @@
|
||||
const requestedPlusAccountAccessStrategy = normalizePlusAccountAccessStrategy(
|
||||
options?.plusAccountAccessStrategy ?? state?.plusAccountAccessStrategy
|
||||
);
|
||||
const canUsePlusAccountAccessStrategies = activeFlowId === 'openai'
|
||||
const availablePlusAccountAccessStrategies = activeFlowId === 'openai'
|
||||
&& Boolean(flowState.supportsPlusMode)
|
||||
&& Boolean(runtimeLocks.plusModeEnabled)
|
||||
&& 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())
|
||||
&& effectiveSignupMethod === SIGNUP_METHOD_EMAIL
|
||||
? (
|
||||
Array.isArray(targetState.supportedPlusAccountAccessStrategies)
|
||||
&& targetState.supportedPlusAccountAccessStrategies.length > 0
|
||||
? targetState.supportedPlusAccountAccessStrategies.slice()
|
||||
: [PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH]
|
||||
)
|
||||
: [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 effectivePlusAccountAccessStrategy = 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)
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
plus: {
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
plusAccountAccessStrategy: 'oauth',
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: {
|
||||
|
||||
@@ -320,7 +320,7 @@
|
||||
<option value="cpa_codex_session">导入当前 ChatGPT 会话到 CPA</option>
|
||||
<option value="sub2api_codex_session">导入当前 ChatGPT 会话到 SUB2API</option>
|
||||
</select>
|
||||
<span class="setting-caption" id="plus-account-access-strategy-caption">Plus 模式未启用</span>
|
||||
<span class="setting-caption" id="plus-account-access-strategy-caption">当前来源仅支持 OAuth</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" id="row-plus-payment-method" style="display:none;">
|
||||
|
||||
+26
-3
@@ -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_SUB2API_CODEX_SESSION;
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
const SIGNUP_METHOD_EMAIL = 'email';
|
||||
const SIGNUP_METHOD_PHONE = 'phone';
|
||||
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
|
||||
@@ -9312,8 +9312,31 @@ function updatePlusModeUI() {
|
||||
selectPlusAccountAccessStrategy.setAttribute('aria-disabled', String(selectPlusAccountAccessStrategy.disabled));
|
||||
}
|
||||
if (typeof plusAccountAccessStrategyCaption !== 'undefined' && plusAccountAccessStrategyCaption) {
|
||||
plusAccountAccessStrategyCaption.textContent = !enabled
|
||||
? 'Plus 模式未启用'
|
||||
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'
|
||||
: describePlusAccountAccessStrategy(
|
||||
effectivePlusAccountAccessStrategy,
|
||||
effectiveTargetId
|
||||
|
||||
@@ -55,8 +55,6 @@ 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: {
|
||||
@@ -122,10 +120,13 @@ 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 = state.accountContributionEnabled && state.plusModeEnabled
|
||||
? 'sub2api_codex_session'
|
||||
: requestedStrategy;
|
||||
const effectiveStrategy = normalizedPanelMode === 'sub2api'
|
||||
? (requestedStrategy === 'sub2api_codex_session' ? 'sub2api_codex_session' : 'oauth')
|
||||
: (normalizedPanelMode === 'cpa'
|
||||
? (requestedStrategy === 'cpa_codex_session' ? 'cpa_codex_session' : 'oauth')
|
||||
: 'oauth');
|
||||
return {
|
||||
effectivePanelMode: options.panelMode,
|
||||
effectivePlusAccountAccessStrategy: effectiveStrategy,
|
||||
@@ -152,12 +153,12 @@ return {
|
||||
`)();
|
||||
}
|
||||
|
||||
test('background step resolution keeps SUB2API session tail independent from the current panel mode', () => {
|
||||
test('background step resolution keeps SUB2API session tail only when the effective Plus target supports it', () => {
|
||||
const api = createHarness();
|
||||
const state = {
|
||||
activeFlowId: 'openai',
|
||||
flowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
panelMode: 'sub2api',
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
@@ -204,7 +205,7 @@ test('background step resolution keeps CPA session tail when the effective Plus
|
||||
]);
|
||||
});
|
||||
|
||||
test('background step resolution keeps SUB2API session tail even when the current panel mode is CPA', () => {
|
||||
test('background step resolution falls back to OAuth tail when the requested session strategy is not effective for the current panel mode', () => {
|
||||
const api = createHarness();
|
||||
const state = {
|
||||
activeFlowId: 'openai',
|
||||
@@ -219,34 +220,12 @@ test('background step resolution keeps SUB2API session tail even when the curren
|
||||
const resolvedState = api.buildResolvedStepDefinitionState(state);
|
||||
const nodeIds = api.getNodeIdsForState(state);
|
||||
|
||||
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',
|
||||
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',
|
||||
]);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
@@ -453,7 +453,7 @@ test('SAVE_SETTING rebuilds Plus node statuses when the account access strategy
|
||||
});
|
||||
});
|
||||
|
||||
test('SAVE_SETTING rebuilds Plus node statuses without forcing the Plus strategy back to OAuth', async () => {
|
||||
test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effective 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,7 +491,8 @@ test('SAVE_SETTING rebuilds Plus node statuses without forcing the Plus strategy
|
||||
: {},
|
||||
broadcastDataUpdate: (payload) => broadcasts.push(payload),
|
||||
getNodeIdsForState: (nextState = {}) => (
|
||||
String(nextState.plusAccountAccessStrategy || '').trim() === 'sub2api_codex_session'
|
||||
String(nextState.panelMode || '').trim() === 'sub2api'
|
||||
&& String(nextState.plusAccountAccessStrategy || '').trim() === 'sub2api_codex_session'
|
||||
? [
|
||||
'open-chatgpt',
|
||||
'plus-checkout-create',
|
||||
@@ -531,24 +532,61 @@ test('SAVE_SETTING rebuilds Plus node statuses without forcing the Plus strategy
|
||||
assert.equal(response.ok, true);
|
||||
assert.equal(state.panelMode, 'cpa');
|
||||
assert.equal(state.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
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.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.deepStrictEqual(state.nodeStatuses, {
|
||||
'open-chatgpt': 'completed',
|
||||
'plus-checkout-create': 'completed',
|
||||
'plus-checkout-billing': 'completed',
|
||||
'paypal-approve': 'completed',
|
||||
'plus-checkout-return': 'completed',
|
||||
'sub2api-session-import': 'running',
|
||||
'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',
|
||||
});
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(state.nodeStatuses, 'oauth-login'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(state.nodeStatuses, 'sub2api-session-import'), 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: '',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ test('flow capability registry exposes editable Plus account access strategies f
|
||||
|
||||
assert.deepEqual(
|
||||
capabilityState.availablePlusAccountAccessStrategies,
|
||||
['oauth', 'cpa_codex_session', 'sub2api_codex_session']
|
||||
['oauth', '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', 'sub2api_codex_session']
|
||||
['oauth', 'cpa_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 keeps Plus session strategies independent from the current OpenAI target', () => {
|
||||
test('flow capability registry forces OAuth when the current target only supports OAuth', () => {
|
||||
const api = loadApi();
|
||||
const registry = api.createFlowCapabilityRegistry();
|
||||
|
||||
@@ -254,38 +254,13 @@ test('flow capability registry keeps Plus session strategies independent from th
|
||||
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, ['sub2api_codex_session']);
|
||||
assert.deepEqual(capabilityState.availablePlusAccountAccessStrategies, ['oauth']);
|
||||
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'cpa_codex_session');
|
||||
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'oauth');
|
||||
assert.equal(capabilityState.canEditPlusAccountAccessStrategy, false);
|
||||
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'oauth');
|
||||
});
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
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/);
|
||||
});
|
||||
@@ -115,14 +115,13 @@ return {
|
||||
`)();
|
||||
}
|
||||
|
||||
test('sidepanel keeps requested SUB2API session strategy independent from the selected target', () => {
|
||||
test('sidepanel keeps requested Plus account strategy while OAuth-only targets force the effective value', () => {
|
||||
const api = buildHarness(
|
||||
`{
|
||||
canShowPlusSettings: true,
|
||||
runtimeLocks: { plusModeEnabled: true },
|
||||
canEditPlusAccountAccessStrategy: true,
|
||||
availablePlusAccountAccessStrategies: ['oauth', 'cpa_codex_session', 'sub2api_codex_session'],
|
||||
effectivePlusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
canEditPlusAccountAccessStrategy: false,
|
||||
effectivePlusAccountAccessStrategy: 'oauth',
|
||||
}`,
|
||||
`{
|
||||
activeFlowId: 'openai',
|
||||
@@ -135,11 +134,11 @@ test('sidepanel keeps requested SUB2API session strategy independent from the se
|
||||
api.updatePlusModeUI();
|
||||
|
||||
assert.equal(api.rowPlusAccountAccessStrategy.style.display, '');
|
||||
assert.equal(api.selectPlusAccountAccessStrategy.disabled, false);
|
||||
assert.equal(api.selectPlusAccountAccessStrategy.disabled, true);
|
||||
assert.equal(api.selectPlusAccountAccessStrategy.dataset.requestedValue, 'sub2api_codex_session');
|
||||
assert.equal(api.selectPlusAccountAccessStrategy.value, 'sub2api_codex_session');
|
||||
assert.equal(api.selectPlusAccountAccessStrategy.value, 'oauth');
|
||||
assert.equal(api.getRequestedPlusAccountAccessStrategy(), 'sub2api_codex_session');
|
||||
assert.match(api.plusAccountAccessStrategyCaption.textContent, /SUB2API/);
|
||||
assert.match(api.plusAccountAccessStrategyCaption.textContent, /OAuth/);
|
||||
});
|
||||
|
||||
test('sidepanel enables SUB2API session strategy selection when the current Plus target supports it', () => {
|
||||
@@ -284,7 +283,6 @@ return {
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
signupMethod: 'email',
|
||||
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||
accountContributionEnabled: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -296,7 +294,6 @@ return {
|
||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||
signupMethod: 'email',
|
||||
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||
accountContributionEnabled: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -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', checkoutResponse = null } = {}) {
|
||||
function createPlusCheckoutMessageHarness({ checkoutSessionId = 'cs_test_123' } = {}) {
|
||||
const attrs = new Map();
|
||||
let listener = null;
|
||||
const fetchCalls = [];
|
||||
@@ -84,7 +84,7 @@ function createPlusCheckoutMessageHarness({ checkoutSessionId = 'cs_test_123', c
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => checkoutResponse || ({ checkout_session_id: checkoutSessionId }),
|
||||
json: async () => ({ checkout_session_id: checkoutSessionId }),
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected fetch url: ${url}`);
|
||||
@@ -124,58 +124,9 @@ 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' });
|
||||
|
||||
|
||||
@@ -257,102 +257,6 @@ 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({
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
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);
|
||||
});
|
||||
@@ -123,7 +123,6 @@ return {
|
||||
plusAccountAccessStrategy: 'oauth',
|
||||
signupMethod: 'email',
|
||||
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||
accountContributionEnabled: false,
|
||||
},
|
||||
});
|
||||
assert.deepEqual(api.calls[1], { type: 'render', stepIds: [88] });
|
||||
|
||||
@@ -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, accountContributionEnabled: false },
|
||||
options: { activeFlowId: 'openai', plusModeEnabled: true, plusPaymentMethod: 'gopay', plusAccountAccessStrategy: 'oauth', signupMethod: 'email', phoneSignupReloginAfterBindEmailEnabled: 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, accountContributionEnabled: false },
|
||||
options: { activeFlowId: 'openai', plusModeEnabled: true, plusPaymentMethod: 'gpc-helper', plusAccountAccessStrategy: 'oauth', signupMethod: 'email', phoneSignupReloginAfterBindEmailEnabled: false },
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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-create',
|
||||
expectedStepIds: [1, 2, 3, 4, 5, 6, 7],
|
||||
previousNodeId: 'plus-checkout-return',
|
||||
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
},
|
||||
{
|
||||
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-create',
|
||||
expectedStepIds: [1, 2, 3, 4, 5, 6, 7],
|
||||
previousNodeId: 'plus-checkout-return',
|
||||
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
},
|
||||
{
|
||||
label: 'gopay',
|
||||
|
||||
Reference in New Issue
Block a user