Merge branch 'dev'
This commit is contained in:
+106
-1
@@ -128,6 +128,36 @@ const PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = self.MultiPageSte
|
|||||||
signupMethod: 'phone',
|
signupMethod: 'phone',
|
||||||
phoneSignupReloginAfterBindEmailEnabled: true,
|
phoneSignupReloginAfterBindEmailEnabled: true,
|
||||||
}) || PLUS_PAYPAL_PHONE_STEP_DEFINITIONS;
|
}) || PLUS_PAYPAL_PHONE_STEP_DEFINITIONS;
|
||||||
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
plusModeEnabled: true,
|
||||||
|
plusPaymentMethod: 'paypal-hosted',
|
||||||
|
}) || PLUS_PAYPAL_STEP_DEFINITIONS;
|
||||||
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_SUB2API_SESSION_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
plusModeEnabled: true,
|
||||||
|
plusPaymentMethod: 'paypal-hosted',
|
||||||
|
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
||||||
|
}) || PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS;
|
||||||
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
plusModeEnabled: true,
|
||||||
|
plusPaymentMethod: 'paypal-hosted',
|
||||||
|
plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION,
|
||||||
|
}) || PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS;
|
||||||
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
plusModeEnabled: true,
|
||||||
|
plusPaymentMethod: 'paypal-hosted',
|
||||||
|
signupMethod: 'phone',
|
||||||
|
}) || PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS;
|
||||||
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
plusModeEnabled: true,
|
||||||
|
plusPaymentMethod: 'paypal-hosted',
|
||||||
|
signupMethod: 'phone',
|
||||||
|
phoneSignupReloginAfterBindEmailEnabled: true,
|
||||||
|
}) || PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS;
|
||||||
const PLUS_GOPAY_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
const PLUS_GOPAY_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({
|
||||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
plusModeEnabled: true,
|
plusModeEnabled: true,
|
||||||
@@ -214,6 +244,11 @@ const ALL_STEP_DEFINITIONS = (() => {
|
|||||||
...PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_HOSTED_CHECKOUT_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_STEP_DEFINITIONS,
|
...PLUS_GOPAY_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
...PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS,
|
...PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
@@ -243,6 +278,10 @@ const PLUS_PAYPAL_STEP_IDS = PLUS_PAYPAL_STEP_DEFINITIONS
|
|||||||
.map((definition) => Number(definition?.id))
|
.map((definition) => Number(definition?.id))
|
||||||
.filter(Number.isFinite)
|
.filter(Number.isFinite)
|
||||||
.sort((left, right) => left - right);
|
.sort((left, right) => left - right);
|
||||||
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_IDS = PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS
|
||||||
|
.map((definition) => Number(definition?.id))
|
||||||
|
.filter(Number.isFinite)
|
||||||
|
.sort((left, right) => left - right);
|
||||||
const PLUS_GOPAY_STEP_IDS = PLUS_GOPAY_STEP_DEFINITIONS
|
const PLUS_GOPAY_STEP_IDS = PLUS_GOPAY_STEP_DEFINITIONS
|
||||||
.map((definition) => Number(definition?.id))
|
.map((definition) => Number(definition?.id))
|
||||||
.filter(Number.isFinite)
|
.filter(Number.isFinite)
|
||||||
@@ -255,6 +294,7 @@ const PLUS_STEP_IDS = PLUS_PAYPAL_STEP_IDS;
|
|||||||
const LAST_STEP_ID = Math.max(
|
const LAST_STEP_ID = Math.max(
|
||||||
NORMAL_STEP_IDS[NORMAL_STEP_IDS.length - 1] || 10,
|
NORMAL_STEP_IDS[NORMAL_STEP_IDS.length - 1] || 10,
|
||||||
PLUS_PAYPAL_STEP_IDS[PLUS_PAYPAL_STEP_IDS.length - 1] || 10,
|
PLUS_PAYPAL_STEP_IDS[PLUS_PAYPAL_STEP_IDS.length - 1] || 10,
|
||||||
|
PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_IDS[PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_IDS.length - 1] || 10,
|
||||||
PLUS_GOPAY_STEP_IDS[PLUS_GOPAY_STEP_IDS.length - 1] || 10,
|
PLUS_GOPAY_STEP_IDS[PLUS_GOPAY_STEP_IDS.length - 1] || 10,
|
||||||
PLUS_GPC_STEP_IDS[PLUS_GPC_STEP_IDS.length - 1] || 10
|
PLUS_GPC_STEP_IDS[PLUS_GPC_STEP_IDS.length - 1] || 10
|
||||||
);
|
);
|
||||||
@@ -681,9 +721,11 @@ const HERO_SMS_COUNTRY_BY_PHONE_PREFIX = Object.freeze([
|
|||||||
]);
|
]);
|
||||||
const FIVE_SIM_OPERATOR = DEFAULT_FIVE_SIM_OPERATOR;
|
const FIVE_SIM_OPERATOR = DEFAULT_FIVE_SIM_OPERATOR;
|
||||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||||
|
const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted';
|
||||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||||
const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL;
|
const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL_HOSTED;
|
||||||
|
const DEFAULT_PLUS_HOSTED_CHECKOUT_OAUTH_DELAY_SECONDS = 3;
|
||||||
const DISPLAY_TIMEZONE = 'Asia/Shanghai';
|
const DISPLAY_TIMEZONE = 'Asia/Shanghai';
|
||||||
const MICROSOFT_TOKEN_DNR_RULE_ID = 1001;
|
const MICROSOFT_TOKEN_DNR_RULE_ID = 1001;
|
||||||
const PERSISTENT_ALIAS_STATE_KEYS = [
|
const PERSISTENT_ALIAS_STATE_KEYS = [
|
||||||
@@ -781,6 +823,12 @@ function isPlusModeState(state = {}) {
|
|||||||
|
|
||||||
function normalizePlusPaymentMethod(value = '') {
|
function normalizePlusPaymentMethod(value = '') {
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
const paypalHostedValue = typeof PLUS_PAYMENT_METHOD_PAYPAL_HOSTED !== 'undefined'
|
||||||
|
? PLUS_PAYMENT_METHOD_PAYPAL_HOSTED
|
||||||
|
: 'paypal-hosted';
|
||||||
|
if (normalized === paypalHostedValue || normalized === 'paypal_direct' || normalized === 'paypal-direct') {
|
||||||
|
return paypalHostedValue;
|
||||||
|
}
|
||||||
if (normalized === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
if (normalized === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
||||||
return PLUS_PAYMENT_METHOD_GPC_HELPER;
|
return PLUS_PAYMENT_METHOD_GPC_HELPER;
|
||||||
}
|
}
|
||||||
@@ -937,6 +985,20 @@ function getStepDefinitionsForState(state = {}) {
|
|||||||
}
|
}
|
||||||
return PLUS_GOPAY_STEP_DEFINITIONS;
|
return PLUS_GOPAY_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
|
if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) {
|
||||||
|
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
||||||
|
return Boolean(resolvedState?.phoneSignupReloginAfterBindEmailEnabled)
|
||||||
|
? PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS
|
||||||
|
: PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
|
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
|
return PLUS_PAYPAL_HOSTED_CHECKOUT_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
|
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
|
||||||
|
return PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
|
return PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
signupMethod === SIGNUP_METHOD_EMAIL
|
signupMethod === SIGNUP_METHOD_EMAIL
|
||||||
&& plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
&& plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||||
@@ -967,6 +1029,9 @@ function getStepIdsForState(state = {}) {
|
|||||||
if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
||||||
return PLUS_GPC_STEP_IDS;
|
return PLUS_GPC_STEP_IDS;
|
||||||
}
|
}
|
||||||
|
if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) {
|
||||||
|
return PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_IDS;
|
||||||
|
}
|
||||||
return paymentMethod === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_GOPAY_STEP_IDS : PLUS_PAYPAL_STEP_IDS;
|
return paymentMethod === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_GOPAY_STEP_IDS : PLUS_PAYPAL_STEP_IDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1151,6 +1216,9 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
plusModeEnabled: false,
|
plusModeEnabled: false,
|
||||||
plusPaymentMethod: DEFAULT_PLUS_PAYMENT_METHOD,
|
plusPaymentMethod: DEFAULT_PLUS_PAYMENT_METHOD,
|
||||||
plusAccountAccessStrategy: 'oauth',
|
plusAccountAccessStrategy: 'oauth',
|
||||||
|
hostedCheckoutVerificationUrl: '',
|
||||||
|
hostedCheckoutPhoneNumber: '',
|
||||||
|
plusHostedCheckoutOauthDelaySeconds: DEFAULT_PLUS_HOSTED_CHECKOUT_OAUTH_DELAY_SECONDS,
|
||||||
paypalEmail: '',
|
paypalEmail: '',
|
||||||
paypalPassword: '',
|
paypalPassword: '',
|
||||||
currentPayPalAccountId: '',
|
currentPayPalAccountId: '',
|
||||||
@@ -1325,6 +1393,9 @@ const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
|
|||||||
'plusModeEnabled',
|
'plusModeEnabled',
|
||||||
'plusPaymentMethod',
|
'plusPaymentMethod',
|
||||||
'plusAccountAccessStrategy',
|
'plusAccountAccessStrategy',
|
||||||
|
'hostedCheckoutVerificationUrl',
|
||||||
|
'hostedCheckoutPhoneNumber',
|
||||||
|
'plusHostedCheckoutOauthDelaySeconds',
|
||||||
'mailProvider',
|
'mailProvider',
|
||||||
'ipProxyEnabled',
|
'ipProxyEnabled',
|
||||||
'ipProxyService',
|
'ipProxyService',
|
||||||
@@ -1892,6 +1963,12 @@ function normalizePlusPaymentMethod(value = '') {
|
|||||||
return rootScope.GoPayUtils.normalizePlusPaymentMethod(value);
|
return rootScope.GoPayUtils.normalizePlusPaymentMethod(value);
|
||||||
}
|
}
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
const paypalHostedValue = typeof PLUS_PAYMENT_METHOD_PAYPAL_HOSTED !== 'undefined'
|
||||||
|
? PLUS_PAYMENT_METHOD_PAYPAL_HOSTED
|
||||||
|
: 'paypal-hosted';
|
||||||
|
if (normalized === paypalHostedValue || normalized === 'paypal_direct' || normalized === 'paypal-direct') {
|
||||||
|
return paypalHostedValue;
|
||||||
|
}
|
||||||
if (normalized === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
if (normalized === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
||||||
return PLUS_PAYMENT_METHOD_GPC_HELPER;
|
return PLUS_PAYMENT_METHOD_GPC_HELPER;
|
||||||
}
|
}
|
||||||
@@ -3084,6 +3161,14 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return normalizePlusPaymentMethod(value);
|
return normalizePlusPaymentMethod(value);
|
||||||
case 'plusAccountAccessStrategy':
|
case 'plusAccountAccessStrategy':
|
||||||
return normalizePlusAccountAccessStrategy(value);
|
return normalizePlusAccountAccessStrategy(value);
|
||||||
|
case 'hostedCheckoutVerificationUrl':
|
||||||
|
return String(value || '').trim();
|
||||||
|
case 'hostedCheckoutPhoneNumber':
|
||||||
|
return String(value || '').trim();
|
||||||
|
case 'plusHostedCheckoutOauthDelaySeconds': {
|
||||||
|
const numeric = Number(value);
|
||||||
|
return Math.min(120, Math.max(0, Math.floor(Number.isFinite(numeric) ? numeric : DEFAULT_PLUS_HOSTED_CHECKOUT_OAUTH_DELAY_SECONDS)));
|
||||||
|
}
|
||||||
case 'paypalEmail':
|
case 'paypalEmail':
|
||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
case 'paypalPassword':
|
case 'paypalPassword':
|
||||||
@@ -10626,6 +10711,11 @@ const AUTO_RUN_BACKGROUND_COMPLETED_STEP_KEYS = new Set([
|
|||||||
'fetch-signup-code',
|
'fetch-signup-code',
|
||||||
'wait-registration-success',
|
'wait-registration-success',
|
||||||
'plus-checkout-create',
|
'plus-checkout-create',
|
||||||
|
'paypal-hosted-openai-checkout',
|
||||||
|
'paypal-hosted-email',
|
||||||
|
'paypal-hosted-card',
|
||||||
|
'paypal-hosted-create-account',
|
||||||
|
'paypal-hosted-review',
|
||||||
'plus-checkout-billing',
|
'plus-checkout-billing',
|
||||||
'paypal-approve',
|
'paypal-approve',
|
||||||
'plus-checkout-return',
|
'plus-checkout-return',
|
||||||
@@ -11725,6 +11815,11 @@ const AUTO_RUN_NODE_DELAYS = Object.freeze({
|
|||||||
'fill-profile': 0,
|
'fill-profile': 0,
|
||||||
'wait-registration-success': 3000,
|
'wait-registration-success': 3000,
|
||||||
'plus-checkout-create': 3000,
|
'plus-checkout-create': 3000,
|
||||||
|
'paypal-hosted-openai-checkout': 2000,
|
||||||
|
'paypal-hosted-email': 2000,
|
||||||
|
'paypal-hosted-card': 2000,
|
||||||
|
'paypal-hosted-create-account': 2000,
|
||||||
|
'paypal-hosted-review': 2000,
|
||||||
'plus-checkout-billing': 2000,
|
'plus-checkout-billing': 2000,
|
||||||
'gopay-subscription-confirm': 2000,
|
'gopay-subscription-confirm': 2000,
|
||||||
'paypal-approve': 2000,
|
'paypal-approve': 2000,
|
||||||
@@ -13453,13 +13548,18 @@ const plusCheckoutCreateExecutor = self.MultiPageBackgroundPlusCheckoutCreate?.c
|
|||||||
createAutomationTab,
|
createAutomationTab,
|
||||||
ensureContentScriptReadyOnTabUntilStopped,
|
ensureContentScriptReadyOnTabUntilStopped,
|
||||||
fetch: typeof fetch === 'function' ? fetch.bind(globalThis) : null,
|
fetch: typeof fetch === 'function' ? fetch.bind(globalThis) : null,
|
||||||
|
getTabId,
|
||||||
|
getState,
|
||||||
|
isTabAlive,
|
||||||
markCurrentRegistrationAccountUsed,
|
markCurrentRegistrationAccountUsed,
|
||||||
|
queryTabsInAutomationWindow,
|
||||||
registerTab,
|
registerTab,
|
||||||
sendTabMessageUntilStopped,
|
sendTabMessageUntilStopped,
|
||||||
setState,
|
setState,
|
||||||
sleepWithStop,
|
sleepWithStop,
|
||||||
throwIfStopped,
|
throwIfStopped,
|
||||||
waitForTabCompleteUntilStopped,
|
waitForTabCompleteUntilStopped,
|
||||||
|
waitForTabUrlMatchUntilStopped,
|
||||||
});
|
});
|
||||||
const plusCheckoutBillingExecutor = self.MultiPageBackgroundPlusCheckoutBilling?.createPlusCheckoutBillingExecutor({
|
const plusCheckoutBillingExecutor = self.MultiPageBackgroundPlusCheckoutBilling?.createPlusCheckoutBillingExecutor({
|
||||||
addLog,
|
addLog,
|
||||||
@@ -13735,6 +13835,11 @@ const stepExecutorsByKey = {
|
|||||||
'fill-profile': (state) => step5Executor.executeStep5(state),
|
'fill-profile': (state) => step5Executor.executeStep5(state),
|
||||||
'wait-registration-success': (state) => step6Executor.executeStep6(state),
|
'wait-registration-success': (state) => step6Executor.executeStep6(state),
|
||||||
'plus-checkout-create': (state) => plusCheckoutCreateExecutor.executePlusCheckoutCreate(state),
|
'plus-checkout-create': (state) => plusCheckoutCreateExecutor.executePlusCheckoutCreate(state),
|
||||||
|
'paypal-hosted-openai-checkout': (state) => plusCheckoutCreateExecutor.executePayPalHostedOpenAiCheckout(state),
|
||||||
|
'paypal-hosted-email': (state) => plusCheckoutCreateExecutor.executePayPalHostedEmail(state),
|
||||||
|
'paypal-hosted-card': (state) => plusCheckoutCreateExecutor.executePayPalHostedCard(state),
|
||||||
|
'paypal-hosted-create-account': (state) => plusCheckoutCreateExecutor.executePayPalHostedCreateAccount(state),
|
||||||
|
'paypal-hosted-review': (state) => plusCheckoutCreateExecutor.executePayPalHostedReview(state),
|
||||||
'plus-checkout-billing': (state) => plusCheckoutBillingExecutor.executePlusCheckoutBilling(state),
|
'plus-checkout-billing': (state) => plusCheckoutBillingExecutor.executePlusCheckoutBilling(state),
|
||||||
'gopay-subscription-confirm': (state) => goPayManualConfirmExecutor.executeGoPayManualConfirm(state),
|
'gopay-subscription-confirm': (state) => goPayManualConfirmExecutor.executeGoPayManualConfirm(state),
|
||||||
'paypal-approve': (state) => normalizePlusPaymentMethod(state?.plusPaymentMethod) === PLUS_PAYMENT_METHOD_GOPAY
|
'paypal-approve': (state) => normalizePlusPaymentMethod(state?.plusPaymentMethod) === PLUS_PAYMENT_METHOD_GOPAY
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,8 @@
|
|||||||
const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js'];
|
const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js'];
|
||||||
const PLUS_CHECKOUT_URL_PATTERN = /^https:\/\/chatgpt\.com\/checkout(?:\/|$)/i;
|
const PLUS_CHECKOUT_URL_PATTERN = /^https:\/\/chatgpt\.com\/checkout(?:\/|$)/i;
|
||||||
const PLUS_CHECKOUT_FRAME_READY_DELAY_MS = 500;
|
const PLUS_CHECKOUT_FRAME_READY_DELAY_MS = 500;
|
||||||
const PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS = 3;
|
const PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS = 5;
|
||||||
const PLUS_CHECKOUT_PAYPAL_REDIRECT_TIMEOUT_MS = 20000;
|
const PLUS_CHECKOUT_PAYPAL_REDIRECT_TIMEOUT_MS = 10000;
|
||||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||||
@@ -1883,7 +1883,7 @@
|
|||||||
await addLog(
|
await addLog(
|
||||||
attempt === 1
|
attempt === 1
|
||||||
? '步骤 7:账单地址已填写完成,等待 3 秒让 checkout 完成校验...'
|
? '步骤 7:账单地址已填写完成,等待 3 秒让 checkout 完成校验...'
|
||||||
: `步骤 7:准备第 ${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS} 次重新提交账单地址...`,
|
: `步骤 7:准备第 ${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS} 次重新检测订阅按钮...`,
|
||||||
attempt === 1 ? 'info' : 'warn'
|
attempt === 1 ? 'info' : 'warn'
|
||||||
);
|
);
|
||||||
await sleepWithStop(3000);
|
await sleepWithStop(3000);
|
||||||
@@ -1907,17 +1907,27 @@
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await addLog(`步骤 7:账单地址已提交,正在等待跳转到 ${paymentConfig.label}(${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS})...`, 'info');
|
const subscribeClicked = subscribeResult?.clicked !== false;
|
||||||
|
const subscribeButtonText = String(subscribeResult?.subscribeButtonText || '').trim();
|
||||||
|
const subscribeButtonStatus = String(subscribeResult?.subscribeButtonStatus || '').trim();
|
||||||
|
if (subscribeClicked) {
|
||||||
|
await addLog(`步骤 7:已点击订阅按钮,正在等待跳转到 ${paymentConfig.label}(${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS})...`, 'info');
|
||||||
|
} else {
|
||||||
|
const buttonStateLabel = subscribeButtonText || subscribeButtonStatus || 'unknown';
|
||||||
|
await addLog(`步骤 7:订阅按钮当前为「${buttonStateLabel}」,本轮未点击,正在等待页面是否跳转到 ${paymentConfig.label}(${attempt}/${PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS})...`, 'warn');
|
||||||
|
}
|
||||||
redirectedToPayment = await waitForPaymentRedirectAfterSubmit(tabId, paymentMethod);
|
redirectedToPayment = await waitForPaymentRedirectAfterSubmit(tabId, paymentMethod);
|
||||||
if (redirectedToPayment) {
|
if (redirectedToPayment) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
lastSubmitError = `提交后 ${Math.round(PLUS_CHECKOUT_PAYPAL_REDIRECT_TIMEOUT_MS / 1000)} 秒内未跳转到 ${paymentConfig.label}`;
|
lastSubmitError = subscribeClicked
|
||||||
await addLog(`步骤 7:${lastSubmitError},将重试提交。`, 'warn');
|
? `点击订阅后 ${Math.round(PLUS_CHECKOUT_PAYPAL_REDIRECT_TIMEOUT_MS / 1000)} 秒内未跳转到 ${paymentConfig.label}`
|
||||||
|
: `订阅按钮当前为「${subscribeButtonText || subscribeButtonStatus || 'unknown'}」,${Math.round(PLUS_CHECKOUT_PAYPAL_REDIRECT_TIMEOUT_MS / 1000)} 秒内未跳转到 ${paymentConfig.label}`;
|
||||||
|
await addLog(`步骤 7:${lastSubmitError},将重新检测订阅按钮。`, 'warn');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!redirectedToPayment) {
|
if (!redirectedToPayment) {
|
||||||
throw new Error(`步骤 7:多次提交账单地址后仍未跳转到 ${paymentConfig.label}。${lastSubmitError}`);
|
throw new Error(`步骤 7:多次检测订阅按钮后仍未跳转到 ${paymentConfig.label}。${lastSubmitError}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await completeNodeFromBackground('plus-checkout-billing', {
|
await completeNodeFromBackground('plus-checkout-billing', {
|
||||||
|
|||||||
@@ -3,6 +3,20 @@
|
|||||||
console.log('[MultiPage:paypal-flow] Content script loaded on', location.href);
|
console.log('[MultiPage:paypal-flow] Content script loaded on', location.href);
|
||||||
|
|
||||||
const PAYPAL_FLOW_LISTENER_SENTINEL = 'data-multipage-paypal-flow-listener';
|
const PAYPAL_FLOW_LISTENER_SENTINEL = 'data-multipage-paypal-flow-listener';
|
||||||
|
const PAYPAL_HOSTED_DEFAULT_PHONE = '1234567890';
|
||||||
|
const PAYPAL_HOSTED_STAGE_OUTSIDE = 'outside_paypal';
|
||||||
|
const PAYPAL_HOSTED_STAGE_LOGIN = 'pay_login';
|
||||||
|
const PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT = 'guest_checkout';
|
||||||
|
const PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT = 'create_account';
|
||||||
|
const PAYPAL_HOSTED_STAGE_REVIEW = 'review_consent';
|
||||||
|
const PAYPAL_HOSTED_STAGE_APPROVAL = 'approval';
|
||||||
|
const PAYPAL_HOSTED_STAGE_UNKNOWN = 'unknown';
|
||||||
|
const PAYPAL_HOSTED_STEP_KEYS = {
|
||||||
|
[PAYPAL_HOSTED_STAGE_LOGIN]: 'paypal-hosted-email',
|
||||||
|
[PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT]: 'paypal-hosted-card',
|
||||||
|
[PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT]: 'paypal-hosted-create-account',
|
||||||
|
[PAYPAL_HOSTED_STAGE_REVIEW]: 'paypal-hosted-review',
|
||||||
|
};
|
||||||
|
|
||||||
if (document.documentElement.getAttribute(PAYPAL_FLOW_LISTENER_SENTINEL) !== '1') {
|
if (document.documentElement.getAttribute(PAYPAL_FLOW_LISTENER_SENTINEL) !== '1') {
|
||||||
document.documentElement.setAttribute(PAYPAL_FLOW_LISTENER_SENTINEL, '1');
|
document.documentElement.setAttribute(PAYPAL_FLOW_LISTENER_SENTINEL, '1');
|
||||||
@@ -13,6 +27,8 @@ if (document.documentElement.getAttribute(PAYPAL_FLOW_LISTENER_SENTINEL) !== '1'
|
|||||||
|| message.type === 'PAYPAL_SUBMIT_LOGIN'
|
|| message.type === 'PAYPAL_SUBMIT_LOGIN'
|
||||||
|| message.type === 'PAYPAL_DISMISS_PROMPTS'
|
|| message.type === 'PAYPAL_DISMISS_PROMPTS'
|
||||||
|| message.type === 'PAYPAL_CLICK_APPROVE'
|
|| message.type === 'PAYPAL_CLICK_APPROVE'
|
||||||
|
|| message.type === 'PAYPAL_HOSTED_GET_STATE'
|
||||||
|
|| message.type === 'PAYPAL_RUN_HOSTED_CHECKOUT_STEP'
|
||||||
) {
|
) {
|
||||||
resetStopState();
|
resetStopState();
|
||||||
handlePayPalCommand(message).then((result) => {
|
handlePayPalCommand(message).then((result) => {
|
||||||
@@ -47,6 +63,10 @@ async function handlePayPalCommand(message) {
|
|||||||
return dismissPayPalPrompts();
|
return dismissPayPalPrompts();
|
||||||
case 'PAYPAL_CLICK_APPROVE':
|
case 'PAYPAL_CLICK_APPROVE':
|
||||||
return clickPayPalApprove();
|
return clickPayPalApprove();
|
||||||
|
case 'PAYPAL_HOSTED_GET_STATE':
|
||||||
|
return inspectPayPalHostedState();
|
||||||
|
case 'PAYPAL_RUN_HOSTED_CHECKOUT_STEP':
|
||||||
|
return runPayPalHostedCheckoutStep(message.payload || {});
|
||||||
default:
|
default:
|
||||||
throw new Error(`paypal-flow.js 不处理消息:${message.type}`);
|
throw new Error(`paypal-flow.js 不处理消息:${message.type}`);
|
||||||
}
|
}
|
||||||
@@ -223,6 +243,429 @@ function findApproveButton() {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPayPalPathname() {
|
||||||
|
return String(location?.pathname || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHostedLoginPage() {
|
||||||
|
return getPayPalPathname() === '/pay' || Boolean(document.getElementById('email'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHostedGuestCheckoutPage() {
|
||||||
|
if (document.getElementById('cardNumber') || document.getElementById('billingLine1')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const pageText = normalizeText(document.body?.innerText || document.body?.textContent || '');
|
||||||
|
if (/create\s*(?:paypal\s*)?account|agree\s*(?:&|and)?\s*create|创建.*(?:账户|账号)/i.test(pageText)
|
||||||
|
&& findHostedCreateAccountButton()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /\/checkoutweb\//i.test(getPayPalPathname())
|
||||||
|
&& Boolean(document.getElementById('phone') || document.getElementById('email'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHostedReviewPage() {
|
||||||
|
return /\/webapps\/hermes/i.test(getPayPalPathname());
|
||||||
|
}
|
||||||
|
|
||||||
|
function findHostedCreateAccountButton() {
|
||||||
|
return document.getElementById('createAccount')
|
||||||
|
|| document.getElementById('createAccountButton')
|
||||||
|
|| document.querySelector('button[data-testid="createAccountButton"]')
|
||||||
|
|| document.querySelector('button[data-testid="create-account-button"]')
|
||||||
|
|| findClickableByText([
|
||||||
|
/agree\s*(?:&|and)?\s*create\s*(?:paypal\s*)?account/i,
|
||||||
|
/create\s*(?:paypal\s*)?account/i,
|
||||||
|
/同意.*创建|创建.*账户|创建.*账号/i,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHostedCreateAccountPage() {
|
||||||
|
if (isHostedLoginPage()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (document.getElementById('cardNumber') || document.getElementById('billingLine1')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const button = findHostedCreateAccountButton();
|
||||||
|
if (!button || !isVisibleElement(button) || !isEnabledControl(button)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const pageText = normalizeText(document.body?.innerText || document.body?.textContent || '');
|
||||||
|
return /create\s*(?:paypal\s*)?account|agree\s*(?:&|and)?\s*create|创建.*(?:账户|账号)/i.test(pageText)
|
||||||
|
|| /create/i.test(getActionText(button));
|
||||||
|
}
|
||||||
|
|
||||||
|
function findHostedReviewConsentButton() {
|
||||||
|
const direct = document.getElementById('consentButton')
|
||||||
|
|| document.querySelector('button[data-testid="consentButton"]');
|
||||||
|
if (direct && isVisibleElement(direct) && isEnabledControl(direct)) {
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
return findClickableByText([
|
||||||
|
/agree\s*(?:and)?\s*continue|accept|continue/i,
|
||||||
|
/同意并继续|同意|继续/i,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectPayPalHostedStage() {
|
||||||
|
if (!/paypal\./i.test(String(location?.host || ''))) {
|
||||||
|
return PAYPAL_HOSTED_STAGE_OUTSIDE;
|
||||||
|
}
|
||||||
|
if (isHostedGuestCheckoutPage()) {
|
||||||
|
return PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT;
|
||||||
|
}
|
||||||
|
if (isHostedReviewPage() && findHostedReviewConsentButton()) {
|
||||||
|
return PAYPAL_HOSTED_STAGE_REVIEW;
|
||||||
|
}
|
||||||
|
if (isHostedCreateAccountPage()) {
|
||||||
|
return PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT;
|
||||||
|
}
|
||||||
|
if (isHostedLoginPage()) {
|
||||||
|
return PAYPAL_HOSTED_STAGE_LOGIN;
|
||||||
|
}
|
||||||
|
return findApproveButton() ? PAYPAL_HOSTED_STAGE_APPROVAL : PAYPAL_HOSTED_STAGE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillHostedInputById(id, value) {
|
||||||
|
const input = document.getElementById(String(id || '').trim());
|
||||||
|
if (!input || !isVisibleElement(input) || !isEnabledControl(input)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fillInput(input, String(value || ''));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectHostedOptionByIdText(id, value) {
|
||||||
|
const select = document.getElementById(String(id || '').trim());
|
||||||
|
const expected = normalizeText(value).toLowerCase();
|
||||||
|
if (!select || !expected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const option = Array.from(select.options || []).find((item) => {
|
||||||
|
const optionText = normalizeText(item.textContent || item.label || '').toLowerCase();
|
||||||
|
const optionValue = normalizeText(item.value || '').toLowerCase();
|
||||||
|
return optionText.includes(expected) || optionValue.includes(expected);
|
||||||
|
});
|
||||||
|
if (!option) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
select.value = option.value;
|
||||||
|
select.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findHostedSubmitButton() {
|
||||||
|
return document.querySelector('button[data-testid="submit-button"]')
|
||||||
|
|| document.querySelector('button[data-testid="hosted-payment-submit-button"]')
|
||||||
|
|| document.querySelector('button[data-atomic-wait-intent="Submit_Email"]')
|
||||||
|
|| document.querySelector('button.SubmitButton--complete')
|
||||||
|
|| findEmailNextButton()
|
||||||
|
|| findLoginNextButton()
|
||||||
|
|| findClickableByText([
|
||||||
|
/pay|continue|next|agree|subscribe/i,
|
||||||
|
/支付|继续|下一步|同意|订阅/i,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHostedStepKey(stage = '', fallback = 'plus-checkout-create') {
|
||||||
|
return PAYPAL_HOSTED_STEP_KEYS[stage] || fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clickHostedSubmitButton(options = {}) {
|
||||||
|
const stepKey = String(options.stepKey || getHostedStepKey(options.stage)).trim();
|
||||||
|
const label = String(options.label || 'hosted-paypal-submit').trim();
|
||||||
|
const maxAttempts = Math.max(1, Math.floor(Number(options.maxAttempts) || 3));
|
||||||
|
let lastButtonText = '';
|
||||||
|
let lastDisabled = false;
|
||||||
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||||
|
const button = await waitUntil(() => {
|
||||||
|
const candidate = findHostedSubmitButton();
|
||||||
|
return candidate && isVisibleElement(candidate) ? candidate : null;
|
||||||
|
}, {
|
||||||
|
intervalMs: 500,
|
||||||
|
timeoutMs: 15000,
|
||||||
|
timeoutMessage: 'PayPal hosted checkout 未找到可点击的继续/提交按钮。',
|
||||||
|
});
|
||||||
|
lastButtonText = getActionText(button);
|
||||||
|
lastDisabled = !isEnabledControl(button);
|
||||||
|
if (lastDisabled) {
|
||||||
|
if (attempt >= maxAttempts) {
|
||||||
|
throw new Error('PayPal hosted checkout 继续/提交按钮长时间不可用。');
|
||||||
|
}
|
||||||
|
await sleep(1000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await performPayPalOperationWithDelay({ stepKey, kind: 'click', label }, async () => {
|
||||||
|
simulateClick(button);
|
||||||
|
});
|
||||||
|
await sleep(1000);
|
||||||
|
return {
|
||||||
|
clicked: true,
|
||||||
|
buttonText: lastButtonText,
|
||||||
|
attempt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
clicked: false,
|
||||||
|
buttonText: lastButtonText,
|
||||||
|
disabled: lastDisabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clickHostedEmailNextButton() {
|
||||||
|
const button = await waitUntil(() => {
|
||||||
|
const candidate = findEmailNextButton();
|
||||||
|
return candidate && isVisibleElement(candidate) && isEnabledControl(candidate) ? candidate : null;
|
||||||
|
}, {
|
||||||
|
intervalMs: 500,
|
||||||
|
timeoutMs: 15000,
|
||||||
|
timeoutMessage: 'PayPal hosted checkout 未找到邮箱页“下一页”按钮。',
|
||||||
|
});
|
||||||
|
const buttonText = getActionText(button);
|
||||||
|
await performPayPalOperationWithDelay({
|
||||||
|
stepKey: getHostedStepKey(PAYPAL_HOSTED_STAGE_LOGIN),
|
||||||
|
kind: 'click',
|
||||||
|
label: 'hosted-paypal-email-next',
|
||||||
|
}, async () => {
|
||||||
|
simulateClick(button);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
clicked: true,
|
||||||
|
buttonText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHostedPhoneDigits(value = '') {
|
||||||
|
return String(value || '').replace(/\D/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyHostedPhoneBeforeSubmit(expectedPhone = '') {
|
||||||
|
const phoneInput = document.getElementById('phone');
|
||||||
|
if (!phoneInput || !isVisibleElement(phoneInput)) {
|
||||||
|
throw new Error('PayPal hosted checkout 未找到电话输入框。');
|
||||||
|
}
|
||||||
|
const expectedDigits = normalizeHostedPhoneDigits(expectedPhone || PAYPAL_HOSTED_DEFAULT_PHONE);
|
||||||
|
const renderedDigits = normalizeHostedPhoneDigits(phoneInput.value || '');
|
||||||
|
if (!expectedDigits) {
|
||||||
|
throw new Error('PayPal hosted checkout 电话配置为空。');
|
||||||
|
}
|
||||||
|
const comparableRenderedDigits = renderedDigits.length > expectedDigits.length
|
||||||
|
? renderedDigits.slice(-expectedDigits.length)
|
||||||
|
: renderedDigits;
|
||||||
|
if (comparableRenderedDigits !== expectedDigits) {
|
||||||
|
throw new Error(`PayPal hosted checkout 电话不一致:配置 ${expectedDigits},页面 ${renderedDigits || '(空)'}。`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
payloadPhoneDigits: expectedDigits,
|
||||||
|
renderedPhoneDigits: renderedDigits,
|
||||||
|
phoneMatched: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clickHostedCreateAccount(payload = {}) {
|
||||||
|
await waitForDocumentComplete();
|
||||||
|
const button = await waitUntil(() => {
|
||||||
|
const candidate = findHostedCreateAccountButton();
|
||||||
|
return candidate && isVisibleElement(candidate) && isEnabledControl(candidate) ? candidate : null;
|
||||||
|
}, {
|
||||||
|
intervalMs: 500,
|
||||||
|
timeoutMs: 30000,
|
||||||
|
timeoutMessage: 'PayPal hosted checkout 未找到创建账号确认按钮。',
|
||||||
|
});
|
||||||
|
await performPayPalOperationWithDelay({
|
||||||
|
stepKey: getHostedStepKey(PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT),
|
||||||
|
kind: 'click',
|
||||||
|
label: 'hosted-paypal-create-account',
|
||||||
|
}, async () => {
|
||||||
|
simulateClick(button);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
stage: PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT,
|
||||||
|
clicked: true,
|
||||||
|
submitted: true,
|
||||||
|
buttonText: getActionText(button),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildHostedRandomEmail() {
|
||||||
|
const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
let value = '';
|
||||||
|
for (let index = 0; index < 16; index += 1) {
|
||||||
|
value += alphabet[Math.floor(Math.random() * alphabet.length)];
|
||||||
|
}
|
||||||
|
return `${value}@gmail.com`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildHostedRandomPassword() {
|
||||||
|
const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^';
|
||||||
|
let value = 'Aa1!';
|
||||||
|
while (value.length < 14) {
|
||||||
|
value += alphabet[Math.floor(Math.random() * alphabet.length)];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildHostedVisaCard() {
|
||||||
|
const digits = [4, 1, 4, 7];
|
||||||
|
while (digits.length < 15) {
|
||||||
|
digits.push(Math.floor(Math.random() * 10));
|
||||||
|
}
|
||||||
|
const reversed = digits.slice().reverse();
|
||||||
|
let sum = 0;
|
||||||
|
for (let index = 0; index < reversed.length; index += 1) {
|
||||||
|
let digit = reversed[index];
|
||||||
|
if (index % 2 === 0) {
|
||||||
|
digit *= 2;
|
||||||
|
if (digit > 9) digit -= 9;
|
||||||
|
}
|
||||||
|
sum += digit;
|
||||||
|
}
|
||||||
|
digits.push((10 - (sum % 10)) % 10);
|
||||||
|
const month = String(Math.floor(Math.random() * 12) + 1).padStart(2, '0');
|
||||||
|
const year = (new Date().getFullYear() % 100) + 3;
|
||||||
|
return {
|
||||||
|
number: digits.join(''),
|
||||||
|
expiry: `${month} / ${year}`,
|
||||||
|
cvv: String(Math.floor(100 + Math.random() * 900)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitHostedLogin(payload = {}) {
|
||||||
|
await waitForDocumentComplete();
|
||||||
|
const email = normalizeText(payload.email || buildHostedRandomEmail());
|
||||||
|
const emailInput = document.getElementById('email') || findEmailInput();
|
||||||
|
if (!emailInput) {
|
||||||
|
throw new Error('PayPal hosted checkout 未找到邮箱输入框。');
|
||||||
|
}
|
||||||
|
refillPayPalEmailInput(emailInput, email);
|
||||||
|
const clickResult = await clickHostedEmailNextButton();
|
||||||
|
return {
|
||||||
|
stage: PAYPAL_HOSTED_STAGE_LOGIN,
|
||||||
|
submitted: true,
|
||||||
|
generatedEmail: email,
|
||||||
|
clicked: Boolean(clickResult.clicked),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fillHostedGuestCheckout(payload = {}) {
|
||||||
|
await waitForDocumentComplete();
|
||||||
|
const countrySelect = document.getElementById('country');
|
||||||
|
if (countrySelect && String(countrySelect.value || '').trim().toUpperCase() !== 'US') {
|
||||||
|
countrySelect.value = 'US';
|
||||||
|
countrySelect.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
await sleep(1000);
|
||||||
|
}
|
||||||
|
const generatedCard = buildHostedVisaCard();
|
||||||
|
const address = payload.address && typeof payload.address === 'object' ? payload.address : {};
|
||||||
|
const values = {
|
||||||
|
email: normalizeText(payload.email || buildHostedRandomEmail()),
|
||||||
|
phone: normalizeText(payload.phone || PAYPAL_HOSTED_DEFAULT_PHONE),
|
||||||
|
cardNumber: String(payload.cardNumber || generatedCard.number).replace(/\s+/g, ''),
|
||||||
|
cardExpiry: normalizeText(payload.cardExpiry || generatedCard.expiry),
|
||||||
|
cardCvv: normalizeText(payload.cardCvv || generatedCard.cvv),
|
||||||
|
password: String(payload.password || buildHostedRandomPassword()),
|
||||||
|
firstName: normalizeText(payload.firstName || 'James'),
|
||||||
|
lastName: normalizeText(payload.lastName || 'Smith'),
|
||||||
|
};
|
||||||
|
fillHostedInputById('email', values.email);
|
||||||
|
fillHostedInputById('phone', values.phone);
|
||||||
|
fillHostedInputById('cardNumber', values.cardNumber);
|
||||||
|
fillHostedInputById('cardExpiry', values.cardExpiry);
|
||||||
|
fillHostedInputById('cardCvv', values.cardCvv);
|
||||||
|
fillHostedInputById('password', values.password);
|
||||||
|
fillHostedInputById('firstName', values.firstName);
|
||||||
|
fillHostedInputById('lastName', values.lastName);
|
||||||
|
fillHostedInputById('billingLine1', address.street || address.address1 || '');
|
||||||
|
fillHostedInputById('billingCity', address.city || '');
|
||||||
|
fillHostedInputById('billingPostalCode', address.zip || address.postalCode || '');
|
||||||
|
selectHostedOptionByIdText('billingState', address.state || address.region || '');
|
||||||
|
const phoneCheck = verifyHostedPhoneBeforeSubmit(values.phone);
|
||||||
|
const clickResult = await clickHostedSubmitButton({
|
||||||
|
stage: PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT,
|
||||||
|
label: 'hosted-paypal-card-submit',
|
||||||
|
maxAttempts: 4,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
stage: PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT,
|
||||||
|
submitted: true,
|
||||||
|
payloadPhone: values.phone,
|
||||||
|
...phoneCheck,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clickHostedReviewConsent() {
|
||||||
|
await waitForDocumentComplete();
|
||||||
|
const button = await waitUntil(() => {
|
||||||
|
const candidate = findHostedReviewConsentButton();
|
||||||
|
return candidate && isVisibleElement(candidate) && isEnabledControl(candidate) ? candidate : null;
|
||||||
|
}, {
|
||||||
|
intervalMs: 500,
|
||||||
|
timeoutMs: 30000,
|
||||||
|
timeoutMessage: 'PayPal hosted checkout 未找到账单确认按钮。',
|
||||||
|
});
|
||||||
|
await performPayPalOperationWithDelay({
|
||||||
|
stepKey: getHostedStepKey(PAYPAL_HOSTED_STAGE_REVIEW),
|
||||||
|
kind: 'click',
|
||||||
|
label: 'hosted-paypal-review-consent',
|
||||||
|
}, async () => {
|
||||||
|
simulateClick(button);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
stage: PAYPAL_HOSTED_STAGE_REVIEW,
|
||||||
|
submitted: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runPayPalHostedCheckoutStep(payload = {}) {
|
||||||
|
const stage = detectPayPalHostedStage();
|
||||||
|
const expectedStage = String(payload.expectedStage || '').trim();
|
||||||
|
if (expectedStage && stage !== expectedStage) {
|
||||||
|
return {
|
||||||
|
stage,
|
||||||
|
expectedStage,
|
||||||
|
submitted: false,
|
||||||
|
skipped: true,
|
||||||
|
approveReady: Boolean(findApproveButton()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (stage === PAYPAL_HOSTED_STAGE_LOGIN) {
|
||||||
|
return submitHostedLogin(payload);
|
||||||
|
}
|
||||||
|
if (stage === PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT) {
|
||||||
|
return fillHostedGuestCheckout(payload);
|
||||||
|
}
|
||||||
|
if (stage === PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT) {
|
||||||
|
return clickHostedCreateAccount(payload);
|
||||||
|
}
|
||||||
|
if (stage === PAYPAL_HOSTED_STAGE_REVIEW) {
|
||||||
|
return clickHostedReviewConsent();
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
stage,
|
||||||
|
submitted: false,
|
||||||
|
approveReady: Boolean(findApproveButton()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function inspectPayPalHostedState() {
|
||||||
|
const stage = detectPayPalHostedStage();
|
||||||
|
const createAccountButton = findHostedCreateAccountButton();
|
||||||
|
return {
|
||||||
|
url: location.href,
|
||||||
|
readyState: document.readyState,
|
||||||
|
hostedStage: stage,
|
||||||
|
hasGuestCardFields: Boolean(document.getElementById('cardNumber')),
|
||||||
|
hasHostedEmailInput: Boolean(document.getElementById('email') || findEmailInput()),
|
||||||
|
createAccountReady: Boolean(createAccountButton && isVisibleElement(createAccountButton) && isEnabledControl(createAccountButton)),
|
||||||
|
reviewConsentReady: Boolean(findHostedReviewConsentButton()),
|
||||||
|
approveReady: Boolean(findApproveButton()),
|
||||||
|
bodyTextPreview: normalizeText(document.body?.innerText || '').slice(0, 240),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function findPasskeyPromptButtons() {
|
function findPasskeyPromptButtons() {
|
||||||
const promptPatterns = [
|
const promptPatterns = [
|
||||||
/passkey|通行密钥|安全密钥|下次登录|faster|save/i,
|
/passkey|通行密钥|安全密钥|下次登录|faster|save/i,
|
||||||
|
|||||||
+273
-4
@@ -34,6 +34,7 @@ const PLUS_CHECKOUT_CONFIGS = {
|
|||||||
};
|
};
|
||||||
const PAYPAL_DIAGNOSTIC_LOG_INTERVAL_MS = 5000;
|
const PAYPAL_DIAGNOSTIC_LOG_INTERVAL_MS = 5000;
|
||||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||||
|
const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted';
|
||||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||||
const PAYMENT_METHOD_CONFIGS = {
|
const PAYMENT_METHOD_CONFIGS = {
|
||||||
[PLUS_PAYMENT_METHOD_PAYPAL]: {
|
[PLUS_PAYMENT_METHOD_PAYPAL]: {
|
||||||
@@ -47,6 +48,17 @@ const PAYMENT_METHOD_CONFIGS = {
|
|||||||
},
|
},
|
||||||
patterns: [/paypal/i],
|
patterns: [/paypal/i],
|
||||||
},
|
},
|
||||||
|
[PLUS_PAYMENT_METHOD_PAYPAL_HOSTED]: {
|
||||||
|
id: PLUS_PAYMENT_METHOD_PAYPAL_HOSTED,
|
||||||
|
label: 'PayPal 无卡直绑',
|
||||||
|
diagnosticLabel: 'PayPal hosted',
|
||||||
|
checkoutMerchantPath: 'openai_llc',
|
||||||
|
billingDetails: {
|
||||||
|
country: 'US',
|
||||||
|
currency: 'USD',
|
||||||
|
},
|
||||||
|
patterns: [/paypal/i],
|
||||||
|
},
|
||||||
[PLUS_PAYMENT_METHOD_GOPAY]: {
|
[PLUS_PAYMENT_METHOD_GOPAY]: {
|
||||||
id: PLUS_PAYMENT_METHOD_GOPAY,
|
id: PLUS_PAYMENT_METHOD_GOPAY,
|
||||||
label: 'GoPay',
|
label: 'GoPay',
|
||||||
@@ -80,6 +92,7 @@ if (document.documentElement.getAttribute(PLUS_CHECKOUT_LISTENER_SENTINEL) !== '
|
|||||||
|| message.type === 'PLUS_CHECKOUT_SELECT_ADDRESS_SUGGESTION'
|
|| message.type === 'PLUS_CHECKOUT_SELECT_ADDRESS_SUGGESTION'
|
||||||
|| message.type === 'PLUS_CHECKOUT_ENSURE_BILLING_ADDRESS'
|
|| message.type === 'PLUS_CHECKOUT_ENSURE_BILLING_ADDRESS'
|
||||||
|| message.type === 'PLUS_CHECKOUT_CLICK_SUBSCRIBE'
|
|| message.type === 'PLUS_CHECKOUT_CLICK_SUBSCRIBE'
|
||||||
|
|| message.type === 'RUN_PAYPAL_HOSTED_OPENAI_CHECKOUT_STEP'
|
||||||
|| message.type === 'PLUS_CHECKOUT_GET_STATE'
|
|| message.type === 'PLUS_CHECKOUT_GET_STATE'
|
||||||
) {
|
) {
|
||||||
resetStopState();
|
resetStopState();
|
||||||
@@ -119,6 +132,8 @@ async function handlePlusCheckoutCommand(message) {
|
|||||||
return ensurePlusStructuredBillingAddress(message.payload || {});
|
return ensurePlusStructuredBillingAddress(message.payload || {});
|
||||||
case 'PLUS_CHECKOUT_CLICK_SUBSCRIBE':
|
case 'PLUS_CHECKOUT_CLICK_SUBSCRIBE':
|
||||||
return clickPlusSubscribe(message.payload || {});
|
return clickPlusSubscribe(message.payload || {});
|
||||||
|
case 'RUN_PAYPAL_HOSTED_OPENAI_CHECKOUT_STEP':
|
||||||
|
return runPayPalHostedOpenAiCheckoutStep(message.payload || {});
|
||||||
case 'PLUS_CHECKOUT_GET_STATE':
|
case 'PLUS_CHECKOUT_GET_STATE':
|
||||||
return inspectPlusCheckoutState(message.payload || {});
|
return inspectPlusCheckoutState(message.payload || {});
|
||||||
default:
|
default:
|
||||||
@@ -152,6 +167,164 @@ async function waitForDocumentComplete() {
|
|||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPayPalHostedOpenAiCheckoutPage() {
|
||||||
|
const host = String(location?.host || '').toLowerCase();
|
||||||
|
return host.includes('pay.openai.com') || host.includes('checkout.stripe.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideHostedAddressAutocomplete() {
|
||||||
|
[
|
||||||
|
'.AddressAutocomplete-results',
|
||||||
|
'[class*="AddressAutocomplete"]',
|
||||||
|
'#billing-address-autocomplete-results',
|
||||||
|
].forEach((selector) => {
|
||||||
|
document.querySelectorAll(selector).forEach((node) => {
|
||||||
|
try {
|
||||||
|
node.style.setProperty('display', 'none', 'important');
|
||||||
|
node.style.setProperty('visibility', 'hidden', 'important');
|
||||||
|
node.style.setProperty('pointer-events', 'none', 'important');
|
||||||
|
} catch {
|
||||||
|
// Best effort only; checkout still works without this cleanup.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasHostedOpenAiVerificationDialog() {
|
||||||
|
return Boolean(document.getElementById('ci-ciBasic-0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillHostedInput(selector, value) {
|
||||||
|
const input = document.querySelector(selector);
|
||||||
|
if (!input) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fillInput(input, String(value || ''));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectHostedOptionByText(selector, value) {
|
||||||
|
const select = document.querySelector(selector);
|
||||||
|
const expected = normalizeText(value).toLowerCase();
|
||||||
|
if (!select || !expected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const option = Array.from(select.options || []).find((item) => {
|
||||||
|
const optionText = normalizeText(item.textContent || item.label || '').toLowerCase();
|
||||||
|
const optionValue = normalizeText(item.value || '').toLowerCase();
|
||||||
|
return optionText.includes(expected) || optionValue.includes(expected);
|
||||||
|
});
|
||||||
|
if (!option) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
select.value = option.value;
|
||||||
|
select.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findHostedPayPalButton() {
|
||||||
|
return document.querySelector('[data-testid="paypal-accordion-item-button"]')
|
||||||
|
|| document.querySelector('.paypal-accordion-item button')
|
||||||
|
|| findClickableByText([/paypal/i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findHostedSubmitButton() {
|
||||||
|
return document.querySelector('button[data-testid="submit-button"]')
|
||||||
|
|| document.querySelector('button[data-testid="hosted-payment-submit-button"]')
|
||||||
|
|| document.querySelector('button[data-atomic-wait-intent="Submit_Email"]')
|
||||||
|
|| document.querySelector('button.SubmitButton--complete')
|
||||||
|
|| findClickableByText([
|
||||||
|
/next|continue|pay|subscribe|agree/i,
|
||||||
|
/下一页|继续|支付|订阅|同意/i,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clickHostedSubmitButton() {
|
||||||
|
const button = await waitUntil(() => {
|
||||||
|
hideHostedAddressAutocomplete();
|
||||||
|
const candidate = findHostedSubmitButton();
|
||||||
|
return candidate && isEnabledControl(candidate) && isVisibleElement(candidate) ? candidate : null;
|
||||||
|
}, {
|
||||||
|
label: 'hosted checkout 提交按钮',
|
||||||
|
intervalMs: 500,
|
||||||
|
timeoutMs: 15000,
|
||||||
|
});
|
||||||
|
document.activeElement?.blur?.();
|
||||||
|
await sleep(300);
|
||||||
|
const buttonTextBeforeClick = getActionText(button) || '订阅';
|
||||||
|
log(`Plus Checkout:准备点击“${buttonTextBeforeClick}”提交 OpenAI Checkout。`);
|
||||||
|
simulateClick(button);
|
||||||
|
await sleep(300);
|
||||||
|
const buttonTextAfterClick = getActionText(button);
|
||||||
|
if (buttonTextAfterClick && SUBSCRIBE_PROCESSING_TEXT_PATTERN.test(buttonTextAfterClick)) {
|
||||||
|
log(`Plus Checkout:已点击“${buttonTextBeforeClick}”,按钮进入“${buttonTextAfterClick}”,正在等待 PayPal 跳转。`);
|
||||||
|
} else {
|
||||||
|
log(`Plus Checkout:已点击“${buttonTextBeforeClick}”,正在等待 PayPal 跳转。`);
|
||||||
|
}
|
||||||
|
await sleep(900);
|
||||||
|
return {
|
||||||
|
clicked: true,
|
||||||
|
buttonText: getActionText(button),
|
||||||
|
buttonTextBeforeClick,
|
||||||
|
buttonTextAfterClick,
|
||||||
|
hostedVerificationVisible: hasHostedOpenAiVerificationDialog(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillHostedOpenAiVerificationCode(verificationCode = '') {
|
||||||
|
const code = String(verificationCode || '').replace(/\D+/g, '').slice(0, 6);
|
||||||
|
if (code.length !== 6) {
|
||||||
|
throw new Error('hosted checkout OpenAI 验证码无效。');
|
||||||
|
}
|
||||||
|
for (let index = 0; index < 6; index += 1) {
|
||||||
|
const input = document.getElementById(`ci-ciBasic-${index}`);
|
||||||
|
if (!input) {
|
||||||
|
throw new Error('hosted checkout OpenAI 页面未找到完整的验证码输入框。');
|
||||||
|
}
|
||||||
|
fillInput(input, code[index]);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
verificationCodeFilled: true,
|
||||||
|
hostedVerificationVisible: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runPayPalHostedOpenAiCheckoutStep(payload = {}) {
|
||||||
|
await waitForDocumentComplete();
|
||||||
|
if (!isPayPalHostedOpenAiCheckoutPage()) {
|
||||||
|
throw new Error('当前页面不是 PayPal 无卡直绑的 OpenAI hosted checkout 页面。');
|
||||||
|
}
|
||||||
|
if (payload.verificationCode) {
|
||||||
|
return fillHostedOpenAiVerificationCode(payload.verificationCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideHostedAddressAutocomplete();
|
||||||
|
const payPalButton = findHostedPayPalButton();
|
||||||
|
if (payPalButton) {
|
||||||
|
simulateClick(payPalButton);
|
||||||
|
await sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
const address = payload.address && typeof payload.address === 'object' ? payload.address : {};
|
||||||
|
fillHostedInput('#billingAddressLine1', address.street || address.address1 || '');
|
||||||
|
fillHostedInput('#billingLocality', address.city || '');
|
||||||
|
fillHostedInput('#billingPostalCode', address.zip || address.postalCode || '');
|
||||||
|
selectHostedOptionByText('#billingAdministrativeArea', address.state || address.region || '');
|
||||||
|
|
||||||
|
const consent = document.getElementById('termsOfServiceConsentCheckbox');
|
||||||
|
if (consent && !consent.checked) {
|
||||||
|
simulateClick(consent);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let count = 0; count < 5; count += 1) {
|
||||||
|
hideHostedAddressAutocomplete();
|
||||||
|
await sleep(250);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clickHostedSubmitButton();
|
||||||
|
}
|
||||||
|
|
||||||
function isVisibleElement(el) {
|
function isVisibleElement(el) {
|
||||||
if (!el) return false;
|
if (!el) return false;
|
||||||
const style = window.getComputedStyle(el);
|
const style = window.getComputedStyle(el);
|
||||||
@@ -423,7 +596,14 @@ function findPaymentCardAncestor(el, pattern) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizePlusPaymentMethod(value = '') {
|
function normalizePlusPaymentMethod(value = '') {
|
||||||
return String(value || '').trim().toLowerCase() === PLUS_PAYMENT_METHOD_GOPAY
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
const paypalHostedValue = typeof PLUS_PAYMENT_METHOD_PAYPAL_HOSTED !== 'undefined'
|
||||||
|
? PLUS_PAYMENT_METHOD_PAYPAL_HOSTED
|
||||||
|
: 'paypal-hosted';
|
||||||
|
if (normalized === paypalHostedValue || normalized === 'paypal_direct' || normalized === 'paypal-direct') {
|
||||||
|
return paypalHostedValue;
|
||||||
|
}
|
||||||
|
return normalized === PLUS_PAYMENT_METHOD_GOPAY
|
||||||
? PLUS_PAYMENT_METHOD_GOPAY
|
? PLUS_PAYMENT_METHOD_GOPAY
|
||||||
: PLUS_PAYMENT_METHOD_PAYPAL;
|
: PLUS_PAYMENT_METHOD_PAYPAL;
|
||||||
}
|
}
|
||||||
@@ -620,6 +800,7 @@ function buildPlusCheckoutPayload(paymentMethod = PLUS_PAYMENT_METHOD_PAYPAL) {
|
|||||||
const config = getPaymentMethodConfig(paymentMethod);
|
const config = getPaymentMethodConfig(paymentMethod);
|
||||||
return {
|
return {
|
||||||
...JSON.parse(JSON.stringify(PLUS_CHECKOUT_PAYLOAD_BASE)),
|
...JSON.parse(JSON.stringify(PLUS_CHECKOUT_PAYLOAD_BASE)),
|
||||||
|
checkout_ui_mode: config.id === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED ? 'hosted' : 'custom',
|
||||||
billing_details: {
|
billing_details: {
|
||||||
...config.billingDetails,
|
...config.billingDetails,
|
||||||
},
|
},
|
||||||
@@ -635,6 +816,29 @@ function buildPlusCheckoutUrl(checkoutSessionId, paymentMethod = PLUS_PAYMENT_ME
|
|||||||
return `https://chatgpt.com/checkout/${config.checkoutMerchantPath}/${sessionId}`;
|
return `https://chatgpt.com/checkout/${config.checkoutMerchantPath}/${sessionId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findHostedCheckoutUrl(payload = {}) {
|
||||||
|
const queue = [payload];
|
||||||
|
while (queue.length) {
|
||||||
|
const current = queue.shift();
|
||||||
|
if (!current || typeof current !== 'object') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Array.isArray(current)) {
|
||||||
|
queue.push(...current);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const value of Object.values(current)) {
|
||||||
|
if (typeof value === 'string' && /^https:\/\/(?:pay\.openai\.com|checkout\.stripe\.com)\/c\/pay\//i.test(value.trim())) {
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
if (value && typeof value === 'object') {
|
||||||
|
queue.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
async function createPlusCheckoutSession(options = {}) {
|
async function createPlusCheckoutSession(options = {}) {
|
||||||
await waitForDocumentComplete();
|
await waitForDocumentComplete();
|
||||||
log('Plus:正在读取 ChatGPT 登录会话...');
|
log('Plus:正在读取 ChatGPT 登录会话...');
|
||||||
@@ -667,8 +871,16 @@ async function createPlusCheckoutSession(options = {}) {
|
|||||||
throw new Error(`创建 Plus Checkout 失败:${detail}`);
|
throw new Error(`创建 Plus Checkout 失败:${detail}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkoutUrl = buildPlusCheckoutUrl(data.checkout_session_id, paymentMethod);
|
||||||
|
const hostedCheckoutUrl = findHostedCheckoutUrl(data);
|
||||||
|
const preferredCheckoutUrl = paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED
|
||||||
|
? (hostedCheckoutUrl || checkoutUrl)
|
||||||
|
: checkoutUrl;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
checkoutUrl: buildPlusCheckoutUrl(data.checkout_session_id, paymentMethod),
|
checkoutUrl,
|
||||||
|
hostedCheckoutUrl,
|
||||||
|
preferredCheckoutUrl,
|
||||||
country: checkoutPayload.billing_details.country,
|
country: checkoutPayload.billing_details.country,
|
||||||
currency: checkoutPayload.billing_details.currency,
|
currency: checkoutPayload.billing_details.currency,
|
||||||
};
|
};
|
||||||
@@ -1323,6 +1535,42 @@ function isBusySubscribeButton(button) {
|
|||||||
|| /loading|processing|submitting|请稍候|处理中|加载中/i.test(text);
|
|| /loading|processing|submitting|请稍候|处理中|加载中/i.test(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SUBSCRIBE_READY_TEXT_PATTERN = /\u8ba2\u9605|\u7ee7\u7eed|\u786e\u8ba4|\u652f\u4ed8|subscribe|continue|confirm|pay|\u8d2d\u4e70\s*ChatGPT\s*Plus|start\s*subscription|place\s*order/i;
|
||||||
|
const SUBSCRIBE_PROCESSING_TEXT_PATTERN = /\u6b63\u5728\u5904\u7406|\u5904\u7406\u4e2d|\u8bf7\u7a0d\u5019|\u52a0\u8f7d\u4e2d|loading|processing|submitting/i;
|
||||||
|
|
||||||
|
function getSubscribeButtonState(button) {
|
||||||
|
if (!button) {
|
||||||
|
return {
|
||||||
|
found: false,
|
||||||
|
enabled: false,
|
||||||
|
busy: false,
|
||||||
|
ready: false,
|
||||||
|
status: 'missing',
|
||||||
|
text: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const text = normalizeText([
|
||||||
|
button.innerText,
|
||||||
|
button.textContent,
|
||||||
|
button.value,
|
||||||
|
button.getAttribute?.('aria-label'),
|
||||||
|
].filter(Boolean).join(' ')) || getActionText(button);
|
||||||
|
const searchText = getCombinedSearchText(button);
|
||||||
|
const combinedText = normalizeText(`${text} ${searchText}`);
|
||||||
|
const enabled = isEnabledControl(button);
|
||||||
|
const busy = Boolean(isBusySubscribeButton(button) || SUBSCRIBE_PROCESSING_TEXT_PATTERN.test(combinedText));
|
||||||
|
const readyText = SUBSCRIBE_READY_TEXT_PATTERN.test(combinedText);
|
||||||
|
const ready = Boolean(enabled && readyText && !busy);
|
||||||
|
return {
|
||||||
|
found: true,
|
||||||
|
enabled,
|
||||||
|
busy,
|
||||||
|
ready,
|
||||||
|
status: busy ? 'processing' : (!enabled ? 'disabled' : (readyText ? 'ready' : 'unknown')),
|
||||||
|
text: text || searchText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getAssociatedForm(button) {
|
function getAssociatedForm(button) {
|
||||||
if (!button) return null;
|
if (!button) return null;
|
||||||
if (button.form) return button.form;
|
if (button.form) return button.form;
|
||||||
@@ -1509,12 +1757,23 @@ async function clickPlusSubscribe(payload = {}) {
|
|||||||
|
|
||||||
const subscribeButton = await waitUntil(() => {
|
const subscribeButton = await waitUntil(() => {
|
||||||
const button = findSubscribeButton();
|
const button = findSubscribeButton();
|
||||||
return button && isEnabledControl(button) && !isBusySubscribeButton(button) ? button : null;
|
return button || null;
|
||||||
}, {
|
}, {
|
||||||
label: '订阅按钮',
|
label: '订阅按钮',
|
||||||
intervalMs: 250,
|
intervalMs: 250,
|
||||||
timeoutMs: 10000,
|
timeoutMs: 10000,
|
||||||
});
|
});
|
||||||
|
const buttonState = getSubscribeButtonState(subscribeButton);
|
||||||
|
if (!buttonState.ready) {
|
||||||
|
log(`订阅按钮当前状态 [${buttonState.status}] "${buttonState.text.slice(0, 40)}",本轮不点击`);
|
||||||
|
return {
|
||||||
|
clicked: false,
|
||||||
|
subscribeButtonBusy: buttonState.busy,
|
||||||
|
subscribeButtonEnabled: buttonState.enabled,
|
||||||
|
subscribeButtonStatus: buttonState.status,
|
||||||
|
subscribeButtonText: buttonState.text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
await sleep(Math.max(0, Math.floor(Number(payload.beforeClickDelayMs) || 0)));
|
await sleep(Math.max(0, Math.floor(Number(payload.beforeClickDelayMs) || 0)));
|
||||||
await performOperationWithDelay({ stepKey: 'plus-checkout-billing', kind: 'submit', label: 'click-subscribe' }, async () => {
|
await performOperationWithDelay({ stepKey: 'plus-checkout-billing', kind: 'submit', label: 'click-subscribe' }, async () => {
|
||||||
@@ -1522,6 +1781,8 @@ async function clickPlusSubscribe(payload = {}) {
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
clicked: true,
|
clicked: true,
|
||||||
|
subscribeButtonStatus: 'clicked',
|
||||||
|
subscribeButtonText: buttonState.text,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1538,6 +1799,7 @@ async function readChatGptSessionAccessToken() {
|
|||||||
|
|
||||||
async function inspectPlusCheckoutState(options = {}) {
|
async function inspectPlusCheckoutState(options = {}) {
|
||||||
const structuredAddress = getStructuredAddressFields();
|
const structuredAddress = getStructuredAddressFields();
|
||||||
|
const subscribeButtonState = getSubscribeButtonState(findSubscribeButton());
|
||||||
const state = {
|
const state = {
|
||||||
url: location.href,
|
url: location.href,
|
||||||
readyState: document.readyState,
|
readyState: document.readyState,
|
||||||
@@ -1549,7 +1811,14 @@ async function inspectPlusCheckoutState(options = {}) {
|
|||||||
paymentTextPreview: getPaymentTextPreview(),
|
paymentTextPreview: getPaymentTextPreview(),
|
||||||
cardFieldsVisible: hasCreditCardFields(),
|
cardFieldsVisible: hasCreditCardFields(),
|
||||||
billingFieldsVisible: hasBillingAddressFields(),
|
billingFieldsVisible: hasBillingAddressFields(),
|
||||||
hasSubscribeButton: Boolean(findSubscribeButton()),
|
hasSubscribeButton: subscribeButtonState.found,
|
||||||
|
subscribeButtonBusy: subscribeButtonState.busy,
|
||||||
|
subscribeButtonEnabled: subscribeButtonState.enabled,
|
||||||
|
subscribeButtonStatus: subscribeButtonState.status,
|
||||||
|
subscribeButtonText: subscribeButtonState.text,
|
||||||
|
hostedOpenAiPage: isPayPalHostedOpenAiCheckoutPage(),
|
||||||
|
hostedVerificationVisible: hasHostedOpenAiVerificationDialog(),
|
||||||
|
hostedPayPalButtonFound: Boolean(findHostedPayPalButton()),
|
||||||
checkoutAmountSummary: getCheckoutAmountSummary(),
|
checkoutAmountSummary: getCheckoutAmountSummary(),
|
||||||
addressFieldValues: {
|
addressFieldValues: {
|
||||||
address1: structuredAddress.address1?.value || '',
|
address1: structuredAddress.address1?.value || '',
|
||||||
|
|||||||
+3
-2
@@ -505,6 +505,7 @@ function simulateClick(el) {
|
|||||||
: { method: 'click' };
|
: { method: 'click' };
|
||||||
|
|
||||||
let method = strategy.method || 'click';
|
let method = strategy.method || 'click';
|
||||||
|
const textBeforeClick = el.textContent || '';
|
||||||
|
|
||||||
if (method === 'requestSubmit' && form && typeof form.requestSubmit === 'function') {
|
if (method === 'requestSubmit' && form && typeof form.requestSubmit === 'function') {
|
||||||
form.requestSubmit(el);
|
form.requestSubmit(el);
|
||||||
@@ -516,8 +517,8 @@ function simulateClick(el) {
|
|||||||
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(LOG_PREFIX, `已点击(${method}): ${el.tagName} ${el.textContent?.slice(0, 30) || ''}`);
|
console.log(LOG_PREFIX, `已点击(${method}): ${el.tagName} ${textBeforeClick.slice(0, 30)}`);
|
||||||
log(`已点击(${method}) [${el.tagName}] "${el.textContent?.trim().slice(0, 30) || ''}"`);
|
log(`已点击(${method}) [${el.tagName}] "${textBeforeClick.trim().slice(0, 30) || ''}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
})(typeof self !== 'undefined' ? self : globalThis, function createStepDefinitionsModule() {
|
})(typeof self !== 'undefined' ? self : globalThis, function createStepDefinitionsModule() {
|
||||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||||
|
const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted';
|
||||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
@@ -32,6 +33,13 @@
|
|||||||
{ id: 8, order: 80, key: 'paypal-approve', title: 'PayPal 登录与授权', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-approve' },
|
{ 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' },
|
{ id: 9, order: 90, key: 'plus-checkout-return', title: '订阅回跳确认', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-return' },
|
||||||
];
|
];
|
||||||
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS = [
|
||||||
|
...PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS.slice(0, 6),
|
||||||
|
{ id: 7, order: 70, key: 'paypal-hosted-email', title: '无卡直绑填写 PayPal 邮箱', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-hosted-email' },
|
||||||
|
{ id: 8, order: 80, key: 'paypal-hosted-card', title: '无卡直绑填写 PayPal 资料', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-hosted-card' },
|
||||||
|
{ id: 9, order: 90, key: 'paypal-hosted-create-account', title: '无卡直绑确认创建 PayPal', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-hosted-create-account' },
|
||||||
|
{ id: 10, order: 100, key: 'paypal-hosted-review', title: '无卡直绑完成 PayPal 授权', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-hosted-review' },
|
||||||
|
];
|
||||||
|
|
||||||
const PLUS_GOPAY_PREFIX_STEP_DEFINITIONS = [
|
const PLUS_GOPAY_PREFIX_STEP_DEFINITIONS = [
|
||||||
{ id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' },
|
{ id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' },
|
||||||
@@ -163,6 +171,53 @@
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createHostedCheckoutAuthTail(startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL, options = {}) {
|
||||||
|
const id = Number(startId) || 7;
|
||||||
|
const order = Number(startOrder) || id * 10;
|
||||||
|
const commonStart = [
|
||||||
|
{ id, order, key: 'oauth-login', title: '刷新 OAuth 并登录', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'oauth-login' },
|
||||||
|
{ id: id + 1, order: order + 10, key: 'fetch-login-code', title: '获取登录验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-login-code' },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
||||||
|
if (isPhoneSignupReloginAfterBindEmailEnabled(options)) {
|
||||||
|
return [
|
||||||
|
...commonStart,
|
||||||
|
{ id: id + 2, order: order + 20, key: 'bind-email', title: '绑定邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'bind-email' },
|
||||||
|
{ id: id + 3, order: order + 30, key: 'fetch-bind-email-code', title: '获取绑定邮箱验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fetch-bind-email-code', mailRuleId: 'openai-login-code' },
|
||||||
|
{ id: id + 4, order: order + 40, key: 'relogin-bound-email', title: '绑定邮箱后刷新 OAuth 并登录(邮箱)', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'oauth-login' },
|
||||||
|
{ id: id + 5, order: order + 50, key: 'fetch-bound-email-login-code', title: '获取登录验证码(邮箱)', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-login-code' },
|
||||||
|
{ id: id + 6, order: order + 60, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' },
|
||||||
|
{ id: id + 7, order: order + 70, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
...commonStart,
|
||||||
|
{ id: id + 2, order: order + 20, key: 'bind-email', title: '绑定邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'bind-email' },
|
||||||
|
{ id: id + 3, order: order + 30, key: 'fetch-bind-email-code', title: '获取绑定邮箱验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fetch-bind-email-code', mailRuleId: 'openai-login-code' },
|
||||||
|
{ id: id + 4, order: order + 40, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' },
|
||||||
|
{ id: id + 5, order: order + 50, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...commonStart,
|
||||||
|
{ id: id + 2, order: order + 20, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' },
|
||||||
|
{ id: id + 3, order: order + 30, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHostedCheckoutSteps(prefixSteps, startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL, options = {}) {
|
||||||
|
const sessionTailFactory = resolvePlusSessionImportTail(options, signupMethod);
|
||||||
|
const tailSteps = sessionTailFactory
|
||||||
|
? sessionTailFactory(startId, startOrder)
|
||||||
|
: createHostedCheckoutAuthTail(startId, startOrder, signupMethod, options);
|
||||||
|
return [
|
||||||
|
...prefixSteps,
|
||||||
|
...tailSteps,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const NORMAL_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_EMAIL);
|
const NORMAL_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_EMAIL);
|
||||||
const NORMAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_PHONE);
|
const NORMAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_PHONE);
|
||||||
const NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
const NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
||||||
@@ -183,6 +238,23 @@
|
|||||||
);
|
);
|
||||||
const PLUS_PAYPAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
const PLUS_PAYPAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
||||||
const PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
const PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
||||||
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS = createHostedCheckoutSteps(PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS, 11, 110, SIGNUP_METHOD_EMAIL);
|
||||||
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_SUB2API_SESSION_STEP_DEFINITIONS = createHostedCheckoutSteps(
|
||||||
|
PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS,
|
||||||
|
11,
|
||||||
|
110,
|
||||||
|
SIGNUP_METHOD_EMAIL,
|
||||||
|
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
||||||
|
);
|
||||||
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS = createHostedCheckoutSteps(
|
||||||
|
PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS,
|
||||||
|
11,
|
||||||
|
110,
|
||||||
|
SIGNUP_METHOD_EMAIL,
|
||||||
|
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION }
|
||||||
|
);
|
||||||
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS = createHostedCheckoutSteps(PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS, 11, 110, SIGNUP_METHOD_PHONE);
|
||||||
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createHostedCheckoutSteps(PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS, 11, 110, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
||||||
const PLUS_GOPAY_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL);
|
const PLUS_GOPAY_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL);
|
||||||
const PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS = createOpenAiSteps(
|
const PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS = createOpenAiSteps(
|
||||||
PLUS_GOPAY_PREFIX_STEP_DEFINITIONS,
|
PLUS_GOPAY_PREFIX_STEP_DEFINITIONS,
|
||||||
@@ -313,6 +385,9 @@
|
|||||||
|
|
||||||
function normalizePlusPaymentMethod(value = '') {
|
function normalizePlusPaymentMethod(value = '') {
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (normalized === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED || normalized === 'paypal_direct' || normalized === 'paypal-direct') {
|
||||||
|
return PLUS_PAYMENT_METHOD_PAYPAL_HOSTED;
|
||||||
|
}
|
||||||
if (normalized === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
if (normalized === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
||||||
return PLUS_PAYMENT_METHOD_GPC_HELPER;
|
return PLUS_PAYMENT_METHOD_GPC_HELPER;
|
||||||
}
|
}
|
||||||
@@ -352,6 +427,20 @@
|
|||||||
}
|
}
|
||||||
const paymentMethod = normalizePlusPaymentMethod(options?.plusPaymentMethod || options?.paymentMethod);
|
const paymentMethod = normalizePlusPaymentMethod(options?.plusPaymentMethod || options?.paymentMethod);
|
||||||
const plusAccountAccessStrategy = normalizePlusAccountAccessStrategy(options?.plusAccountAccessStrategy);
|
const plusAccountAccessStrategy = normalizePlusAccountAccessStrategy(options?.plusAccountAccessStrategy);
|
||||||
|
if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) {
|
||||||
|
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
||||||
|
return reloginAfterBindEmail
|
||||||
|
? PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS
|
||||||
|
: PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
|
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
|
return PLUS_PAYPAL_HOSTED_CHECKOUT_SUB2API_SESSION_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
|
if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) {
|
||||||
|
return PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
|
return PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
||||||
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
||||||
return reloginAfterBindEmail
|
return reloginAfterBindEmail
|
||||||
@@ -433,6 +522,11 @@
|
|||||||
...PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_HOSTED_CHECKOUT_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_STEP_DEFINITIONS,
|
...PLUS_GOPAY_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
...PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS,
|
...PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
@@ -639,6 +733,11 @@
|
|||||||
PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS,
|
PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
||||||
PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
|
PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS,
|
||||||
|
PLUS_PAYPAL_HOSTED_CHECKOUT_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
|
PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS,
|
||||||
|
PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS,
|
||||||
|
PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS,
|
||||||
PLUS_GOPAY_STEP_DEFINITIONS,
|
PLUS_GOPAY_STEP_DEFINITIONS,
|
||||||
PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS,
|
||||||
PLUS_GOPAY_PHONE_STEP_DEFINITIONS,
|
PLUS_GOPAY_PHONE_STEP_DEFINITIONS,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
root.GoPayUtils = factory();
|
root.GoPayUtils = factory();
|
||||||
})(typeof self !== 'undefined' ? self : globalThis, function createGoPayUtils() {
|
})(typeof self !== 'undefined' ? self : globalThis, function createGoPayUtils() {
|
||||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||||
|
const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted';
|
||||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||||
const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top';
|
const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top';
|
||||||
@@ -11,6 +12,9 @@
|
|||||||
|
|
||||||
function normalizePlusPaymentMethod(value = '') {
|
function normalizePlusPaymentMethod(value = '') {
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (normalized === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED || normalized === 'paypal_direct' || normalized === 'paypal-direct') {
|
||||||
|
return PLUS_PAYMENT_METHOD_PAYPAL_HOSTED;
|
||||||
|
}
|
||||||
if (normalized === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
if (normalized === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
||||||
return PLUS_PAYMENT_METHOD_GPC_HELPER;
|
return PLUS_PAYMENT_METHOD_GPC_HELPER;
|
||||||
}
|
}
|
||||||
@@ -416,6 +420,7 @@
|
|||||||
PLUS_PAYMENT_METHOD_GPC_HELPER,
|
PLUS_PAYMENT_METHOD_GPC_HELPER,
|
||||||
PLUS_PAYMENT_METHOD_GOPAY,
|
PLUS_PAYMENT_METHOD_GOPAY,
|
||||||
PLUS_PAYMENT_METHOD_PAYPAL,
|
PLUS_PAYMENT_METHOD_PAYPAL,
|
||||||
|
PLUS_PAYMENT_METHOD_PAYPAL_HOSTED,
|
||||||
buildGpcCardBalanceUrl,
|
buildGpcCardBalanceUrl,
|
||||||
buildGpcApiKeyBalanceUrl,
|
buildGpcApiKeyBalanceUrl,
|
||||||
buildGpcApiKeyHeaders,
|
buildGpcApiKeyHeaders,
|
||||||
|
|||||||
@@ -378,24 +378,31 @@
|
|||||||
const requestedPlusAccountAccessStrategy = normalizePlusAccountAccessStrategy(
|
const requestedPlusAccountAccessStrategy = normalizePlusAccountAccessStrategy(
|
||||||
options?.plusAccountAccessStrategy ?? state?.plusAccountAccessStrategy
|
options?.plusAccountAccessStrategy ?? state?.plusAccountAccessStrategy
|
||||||
);
|
);
|
||||||
|
const basePlusAccountAccessStrategies = [
|
||||||
|
PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH,
|
||||||
|
PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION,
|
||||||
|
PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
|
||||||
|
];
|
||||||
const availablePlusAccountAccessStrategies = activeFlowId === 'openai'
|
const availablePlusAccountAccessStrategies = activeFlowId === 'openai'
|
||||||
&& Boolean(flowState.supportsPlusMode)
|
&& Boolean(flowState.supportsPlusMode)
|
||||||
&& Boolean(runtimeLocks.plusModeEnabled)
|
&& Boolean(runtimeLocks.plusModeEnabled)
|
||||||
&& effectiveSignupMethod === SIGNUP_METHOD_EMAIL
|
&& effectiveSignupMethod === SIGNUP_METHOD_EMAIL
|
||||||
? (
|
? (runtimeLocks.accountContribution
|
||||||
Array.isArray(targetState.supportedPlusAccountAccessStrategies)
|
? [PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION]
|
||||||
&& targetState.supportedPlusAccountAccessStrategies.length > 0
|
: basePlusAccountAccessStrategies)
|
||||||
? targetState.supportedPlusAccountAccessStrategies.slice()
|
|
||||||
: [PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH]
|
|
||||||
)
|
|
||||||
: [PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH];
|
: [PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH];
|
||||||
const effectivePlusAccountAccessStrategy = availablePlusAccountAccessStrategies.includes(requestedPlusAccountAccessStrategy)
|
const effectivePlusAccountAccessStrategy = runtimeLocks.accountContribution
|
||||||
|
&& runtimeLocks.plusModeEnabled
|
||||||
|
&& effectiveSignupMethod === SIGNUP_METHOD_EMAIL
|
||||||
|
? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
|
||||||
|
: availablePlusAccountAccessStrategies.includes(requestedPlusAccountAccessStrategy)
|
||||||
? requestedPlusAccountAccessStrategy
|
? requestedPlusAccountAccessStrategy
|
||||||
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||||
const canEditPlusAccountAccessStrategy = activeFlowId === 'openai'
|
const canEditPlusAccountAccessStrategy = activeFlowId === 'openai'
|
||||||
&& Boolean(flowState.supportsPlusMode)
|
&& Boolean(flowState.supportsPlusMode)
|
||||||
&& Boolean(runtimeLocks.plusModeEnabled)
|
&& Boolean(runtimeLocks.plusModeEnabled)
|
||||||
&& effectiveSignupMethod === SIGNUP_METHOD_EMAIL
|
&& effectiveSignupMethod === SIGNUP_METHOD_EMAIL
|
||||||
|
&& !runtimeLocks.accountContribution
|
||||||
&& availablePlusAccountAccessStrategies.length > 1;
|
&& availablePlusAccountAccessStrategies.length > 1;
|
||||||
const visibleGroupIds = typeof flowRegistryApi.getVisibleGroupIds === 'function'
|
const visibleGroupIds = typeof flowRegistryApi.getVisibleGroupIds === 'function'
|
||||||
&& isRegisteredFlowId(activeFlowId)
|
&& isRegisteredFlowId(activeFlowId)
|
||||||
|
|||||||
+13
-2
@@ -191,11 +191,22 @@
|
|||||||
},
|
},
|
||||||
'content/plus-checkout': {
|
'content/plus-checkout': {
|
||||||
sourceId: 'plus-checkout',
|
sourceId: 'plus-checkout',
|
||||||
commands: ['plus-checkout-create', 'plus-checkout-billing', 'plus-checkout-return'],
|
commands: [
|
||||||
|
'plus-checkout-create',
|
||||||
|
'paypal-hosted-openai-checkout',
|
||||||
|
'plus-checkout-billing',
|
||||||
|
'plus-checkout-return',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
'content/paypal-flow': {
|
'content/paypal-flow': {
|
||||||
sourceId: 'paypal-flow',
|
sourceId: 'paypal-flow',
|
||||||
commands: ['paypal-approve'],
|
commands: [
|
||||||
|
'paypal-approve',
|
||||||
|
'paypal-hosted-email',
|
||||||
|
'paypal-hosted-card',
|
||||||
|
'paypal-hosted-create-account',
|
||||||
|
'paypal-hosted-review',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
'content/gopay-flow': {
|
'content/gopay-flow': {
|
||||||
sourceId: 'gopay-flow',
|
sourceId: 'gopay-flow',
|
||||||
|
|||||||
@@ -105,8 +105,11 @@
|
|||||||
},
|
},
|
||||||
plus: {
|
plus: {
|
||||||
plusModeEnabled: false,
|
plusModeEnabled: false,
|
||||||
plusPaymentMethod: 'paypal',
|
plusPaymentMethod: 'paypal-hosted',
|
||||||
plusAccountAccessStrategy: 'oauth',
|
plusAccountAccessStrategy: 'oauth',
|
||||||
|
hostedCheckoutVerificationUrl: '',
|
||||||
|
hostedCheckoutPhoneNumber: '',
|
||||||
|
plusHostedCheckoutOauthDelaySeconds: 3,
|
||||||
},
|
},
|
||||||
autoRun: {
|
autoRun: {
|
||||||
stepExecutionRange: {
|
stepExecutionRange: {
|
||||||
@@ -317,11 +320,29 @@
|
|||||||
?? nested?.flows?.openai?.plus?.plusAccountAccessStrategy
|
?? nested?.flows?.openai?.plus?.plusAccountAccessStrategy
|
||||||
?? defaults.flows.openai.plus.plusAccountAccessStrategy
|
?? defaults.flows.openai.plus.plusAccountAccessStrategy
|
||||||
),
|
),
|
||||||
|
hostedCheckoutVerificationUrl: String(
|
||||||
|
input?.hostedCheckoutVerificationUrl
|
||||||
|
?? nested?.flows?.openai?.plus?.hostedCheckoutVerificationUrl
|
||||||
|
?? defaults.flows.openai.plus.hostedCheckoutVerificationUrl
|
||||||
|
).trim(),
|
||||||
|
hostedCheckoutPhoneNumber: String(
|
||||||
|
input?.hostedCheckoutPhoneNumber
|
||||||
|
?? nested?.flows?.openai?.plus?.hostedCheckoutPhoneNumber
|
||||||
|
?? defaults.flows.openai.plus.hostedCheckoutPhoneNumber
|
||||||
|
).trim(),
|
||||||
|
plusHostedCheckoutOauthDelaySeconds: (() => {
|
||||||
|
const numeric = Number(
|
||||||
|
input?.plusHostedCheckoutOauthDelaySeconds
|
||||||
|
?? nested?.flows?.openai?.plus?.plusHostedCheckoutOauthDelaySeconds
|
||||||
|
?? defaults.flows.openai.plus.plusHostedCheckoutOauthDelaySeconds
|
||||||
|
);
|
||||||
|
return Math.min(120, Math.max(0, Math.floor(Number.isFinite(numeric) ? numeric : defaults.flows.openai.plus.plusHostedCheckoutOauthDelaySeconds)));
|
||||||
|
})(),
|
||||||
},
|
},
|
||||||
autoRun: {
|
autoRun: {
|
||||||
stepExecutionRange: normalizeStepExecutionRangeEntry(
|
stepExecutionRange: normalizeStepExecutionRangeEntry(
|
||||||
nested?.flows?.openai?.autoRun?.stepExecutionRange
|
stepExecutionRangeByFlow.openai
|
||||||
?? stepExecutionRangeByFlow.openai
|
?? nested?.flows?.openai?.autoRun?.stepExecutionRange
|
||||||
?? {},
|
?? {},
|
||||||
defaults.flows.openai.autoRun.stepExecutionRange
|
defaults.flows.openai.autoRun.stepExecutionRange
|
||||||
),
|
),
|
||||||
@@ -349,8 +370,8 @@
|
|||||||
},
|
},
|
||||||
autoRun: {
|
autoRun: {
|
||||||
stepExecutionRange: normalizeStepExecutionRangeEntry(
|
stepExecutionRange: normalizeStepExecutionRangeEntry(
|
||||||
nested?.flows?.kiro?.autoRun?.stepExecutionRange
|
stepExecutionRangeByFlow.kiro
|
||||||
?? stepExecutionRangeByFlow.kiro
|
?? nested?.flows?.kiro?.autoRun?.stepExecutionRange
|
||||||
?? {},
|
?? {},
|
||||||
defaults.flows.kiro.autoRun.stepExecutionRange
|
defaults.flows.kiro.autoRun.stepExecutionRange
|
||||||
),
|
),
|
||||||
@@ -456,6 +477,9 @@
|
|||||||
next.plusModeEnabled = openaiState.plus.plusModeEnabled;
|
next.plusModeEnabled = openaiState.plus.plusModeEnabled;
|
||||||
next.plusPaymentMethod = openaiState.plus.plusPaymentMethod;
|
next.plusPaymentMethod = openaiState.plus.plusPaymentMethod;
|
||||||
next.plusAccountAccessStrategy = openaiState.plus.plusAccountAccessStrategy;
|
next.plusAccountAccessStrategy = openaiState.plus.plusAccountAccessStrategy;
|
||||||
|
next.hostedCheckoutVerificationUrl = openaiState.plus.hostedCheckoutVerificationUrl;
|
||||||
|
next.hostedCheckoutPhoneNumber = openaiState.plus.hostedCheckoutPhoneNumber;
|
||||||
|
next.plusHostedCheckoutOauthDelaySeconds = openaiState.plus.plusHostedCheckoutOauthDelaySeconds;
|
||||||
next.mailProvider = normalizedState.services.email.provider;
|
next.mailProvider = normalizedState.services.email.provider;
|
||||||
next.ipProxyEnabled = normalizedState.services.proxy.enabled;
|
next.ipProxyEnabled = normalizedState.services.proxy.enabled;
|
||||||
next.ipProxyService = normalizedState.services.proxy.provider;
|
next.ipProxyService = normalizedState.services.proxy.provider;
|
||||||
|
|||||||
@@ -327,6 +327,7 @@
|
|||||||
<span class="data-label">Plus 支付</span>
|
<span class="data-label">Plus 支付</span>
|
||||||
<div class="data-inline">
|
<div class="data-inline">
|
||||||
<select id="select-plus-payment-method" class="data-select">
|
<select id="select-plus-payment-method" class="data-select">
|
||||||
|
<option value="paypal-hosted">PayPal 无卡直绑</option>
|
||||||
<option value="paypal">PayPal</option>
|
<option value="paypal">PayPal</option>
|
||||||
<!--
|
<!--
|
||||||
<option value="gopay">GoPay</option>
|
<option value="gopay">GoPay</option>
|
||||||
@@ -352,6 +353,22 @@
|
|||||||
<button id="btn-add-paypal-account" class="btn btn-outline btn-sm data-inline-btn" type="button">添加</button>
|
<button id="btn-add-paypal-account" class="btn btn-outline btn-sm data-inline-btn" type="button">添加</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="data-row" id="row-hosted-checkout-verification-url" style="display:none;">
|
||||||
|
<span class="data-label">直绑验证码</span>
|
||||||
|
<input type="text" id="input-hosted-checkout-verification-url" class="data-input"
|
||||||
|
placeholder="用于读取 PayPal / OpenAI 验证码的接口地址" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-hosted-checkout-phone" style="display:none;">
|
||||||
|
<span class="data-label">直绑电话</span>
|
||||||
|
<input type="text" id="input-hosted-checkout-phone" class="data-input"
|
||||||
|
placeholder="填 10 位美国本地号码,不要带 +1" autocomplete="tel" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row" id="row-plus-hosted-checkout-oauth-delay" style="display:none;">
|
||||||
|
<span class="data-label">成功等待</span>
|
||||||
|
<input type="number" id="input-plus-hosted-checkout-oauth-delay-seconds" class="data-input auto-delay-input"
|
||||||
|
value="3" min="0" max="120" step="1" />
|
||||||
|
<span class="setting-caption">支付成功页出现后再继续账号接入</span>
|
||||||
|
</div>
|
||||||
<div class="data-row" id="row-gpc-helper-api" style="display:none;">
|
<div class="data-row" id="row-gpc-helper-api" style="display:none;">
|
||||||
<span class="data-label">GPC API</span>
|
<span class="data-label">GPC API</span>
|
||||||
<div class="data-inline">
|
<div class="data-inline">
|
||||||
|
|||||||
+113
-28
@@ -209,6 +209,12 @@ const btnPayPalAccountMenu = document.getElementById('btn-paypal-account-menu');
|
|||||||
const payPalAccountCurrent = document.getElementById('paypal-account-current');
|
const payPalAccountCurrent = document.getElementById('paypal-account-current');
|
||||||
const payPalAccountMenu = document.getElementById('paypal-account-menu');
|
const payPalAccountMenu = document.getElementById('paypal-account-menu');
|
||||||
const btnAddPayPalAccount = document.getElementById('btn-add-paypal-account');
|
const btnAddPayPalAccount = document.getElementById('btn-add-paypal-account');
|
||||||
|
const rowHostedCheckoutVerificationUrl = document.getElementById('row-hosted-checkout-verification-url');
|
||||||
|
const inputHostedCheckoutVerificationUrl = document.getElementById('input-hosted-checkout-verification-url');
|
||||||
|
const rowHostedCheckoutPhone = document.getElementById('row-hosted-checkout-phone');
|
||||||
|
const inputHostedCheckoutPhone = document.getElementById('input-hosted-checkout-phone');
|
||||||
|
const rowPlusHostedCheckoutOauthDelay = document.getElementById('row-plus-hosted-checkout-oauth-delay');
|
||||||
|
const inputPlusHostedCheckoutOauthDelaySeconds = document.getElementById('input-plus-hosted-checkout-oauth-delay-seconds');
|
||||||
const rowGpcHelperApi = document.getElementById('row-gpc-helper-api');
|
const rowGpcHelperApi = document.getElementById('row-gpc-helper-api');
|
||||||
const inputGpcHelperApi = document.getElementById('input-gpc-helper-api');
|
const inputGpcHelperApi = document.getElementById('input-gpc-helper-api');
|
||||||
const btnGpcHelperConvertApiKey = document.getElementById('btn-gpc-helper-convert-api-key');
|
const btnGpcHelperConvertApiKey = document.getElementById('btn-gpc-helper-convert-api-key');
|
||||||
@@ -545,13 +551,15 @@ const btnAutoStartContinue = document.getElementById('btn-auto-start-continue');
|
|||||||
const autoHintText = document.querySelector('.auto-hint');
|
const autoHintText = document.querySelector('.auto-hint');
|
||||||
const stepsList = document.querySelector('.steps-list');
|
const stepsList = document.querySelector('.steps-list');
|
||||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||||
|
const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted';
|
||||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||||
const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top';
|
const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top';
|
||||||
const GPC_HELPER_PORTAL_URL = 'https://gpc.qlhazycoder.top/';
|
const GPC_HELPER_PORTAL_URL = 'https://gpc.qlhazycoder.top/';
|
||||||
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||||
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL;
|
const DEFAULT_PLUS_HOSTED_CHECKOUT_OAUTH_DELAY_SECONDS = 3;
|
||||||
|
const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL_HOSTED;
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
|
||||||
@@ -2614,6 +2622,19 @@ function getStepExecutionRangeBoundaryNodeId(stepNumber, boundary = 'start') {
|
|||||||
return String(resolvedNodeId || fallbackNode?.nodeId || '').trim();
|
return String(resolvedNodeId || fallbackNode?.nodeId || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStepExecutionRangeStepOptionLabel(node = {}) {
|
||||||
|
const nodeId = String(node?.nodeId || '').trim();
|
||||||
|
const step = getStepIdByNodeIdForCurrentMode(nodeId);
|
||||||
|
const displayOrder = Number(node?.displayOrder);
|
||||||
|
if (Number.isInteger(step) && step > 0) {
|
||||||
|
return String(step);
|
||||||
|
}
|
||||||
|
if (Number.isInteger(displayOrder) && displayOrder > 0) {
|
||||||
|
return String(displayOrder);
|
||||||
|
}
|
||||||
|
return nodeId;
|
||||||
|
}
|
||||||
|
|
||||||
function syncStepExecutionRangeSelectOptions(selectedFromNodeId = '', selectedToNodeId = '') {
|
function syncStepExecutionRangeSelectOptions(selectedFromNodeId = '', selectedToNodeId = '') {
|
||||||
const nodes = getStepExecutionRangeNodes();
|
const nodes = getStepExecutionRangeNodes();
|
||||||
const fromSelect = inputStepExecutionRangeFrom;
|
const fromSelect = inputStepExecutionRangeFrom;
|
||||||
@@ -2624,7 +2645,7 @@ function syncStepExecutionRangeSelectOptions(selectedFromNodeId = '', selectedTo
|
|||||||
|
|
||||||
const buildOptions = (selectedValue) => nodes.map((node) => {
|
const buildOptions = (selectedValue) => nodes.map((node) => {
|
||||||
const nodeId = String(node?.nodeId || '').trim();
|
const nodeId = String(node?.nodeId || '').trim();
|
||||||
const label = getStepExecutionRangeNodeLabel(node);
|
const label = getStepExecutionRangeStepOptionLabel(node);
|
||||||
return `<option value="${escapeHtml(nodeId)}"${nodeId === selectedValue ? ' selected' : ''}>${escapeHtml(label)}</option>`;
|
return `<option value="${escapeHtml(nodeId)}"${nodeId === selectedValue ? ' selected' : ''}>${escapeHtml(label)}</option>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
@@ -2939,13 +2960,25 @@ function normalizePlusPaymentMethod(value = '') {
|
|||||||
const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay';
|
const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay';
|
||||||
const gpcValue = typeof PLUS_PAYMENT_METHOD_GPC_HELPER !== 'undefined' ? PLUS_PAYMENT_METHOD_GPC_HELPER : 'gpc-helper';
|
const gpcValue = typeof PLUS_PAYMENT_METHOD_GPC_HELPER !== 'undefined' ? PLUS_PAYMENT_METHOD_GPC_HELPER : 'gpc-helper';
|
||||||
const paypalValue = typeof PLUS_PAYMENT_METHOD_PAYPAL !== 'undefined' ? PLUS_PAYMENT_METHOD_PAYPAL : 'paypal';
|
const paypalValue = typeof PLUS_PAYMENT_METHOD_PAYPAL !== 'undefined' ? PLUS_PAYMENT_METHOD_PAYPAL : 'paypal';
|
||||||
|
const paypalHostedValue = typeof PLUS_PAYMENT_METHOD_PAYPAL_HOSTED !== 'undefined' ? PLUS_PAYMENT_METHOD_PAYPAL_HOSTED : 'paypal-hosted';
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (normalized === paypalHostedValue || normalized === 'paypal_direct' || normalized === 'paypal-direct') {
|
||||||
|
return paypalHostedValue;
|
||||||
|
}
|
||||||
if (normalized === gpcValue) {
|
if (normalized === gpcValue) {
|
||||||
return gpcValue;
|
return gpcValue;
|
||||||
}
|
}
|
||||||
return normalized === gopayValue ? gopayValue : paypalValue;
|
return normalized === gopayValue ? gopayValue : paypalValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizePlusHostedCheckoutOauthDelaySeconds(value) {
|
||||||
|
const numeric = Number(String(value ?? '').trim());
|
||||||
|
if (!Number.isFinite(numeric)) {
|
||||||
|
return DEFAULT_PLUS_HOSTED_CHECKOUT_OAUTH_DELAY_SECONDS;
|
||||||
|
}
|
||||||
|
return Math.min(120, Math.max(0, Math.floor(numeric)));
|
||||||
|
}
|
||||||
|
|
||||||
function normalizePlusAccountAccessStrategy(value = '') {
|
function normalizePlusAccountAccessStrategy(value = '') {
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) {
|
||||||
@@ -4505,6 +4538,15 @@ function collectSettingsPayload() {
|
|||||||
const currentKiroRsKeyValue = typeof inputKiroRsKey !== 'undefined' && inputKiroRsKey
|
const currentKiroRsKeyValue = typeof inputKiroRsKey !== 'undefined' && inputKiroRsKey
|
||||||
? String(inputKiroRsKey.value ?? '').trim()
|
? String(inputKiroRsKey.value ?? '').trim()
|
||||||
: null;
|
: null;
|
||||||
|
const normalizeHostedCheckoutDelaySecondsSafe = typeof normalizePlusHostedCheckoutOauthDelaySeconds === 'function'
|
||||||
|
? normalizePlusHostedCheckoutOauthDelaySeconds
|
||||||
|
: ((value) => {
|
||||||
|
const numeric = Number(String(value ?? '').trim());
|
||||||
|
if (!Number.isFinite(numeric)) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
return Math.min(120, Math.max(0, Math.floor(numeric)));
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
activeFlowId,
|
activeFlowId,
|
||||||
...(accountContributionEnabled ? {} : {
|
...(accountContributionEnabled ? {} : {
|
||||||
@@ -4562,6 +4604,17 @@ function collectSettingsPayload() {
|
|||||||
plusModeEnabled: effectivePlusModeEnabled,
|
plusModeEnabled: effectivePlusModeEnabled,
|
||||||
plusPaymentMethod,
|
plusPaymentMethod,
|
||||||
plusAccountAccessStrategy: requestedPlusAccountAccessStrategy,
|
plusAccountAccessStrategy: requestedPlusAccountAccessStrategy,
|
||||||
|
hostedCheckoutVerificationUrl: typeof inputHostedCheckoutVerificationUrl !== 'undefined' && inputHostedCheckoutVerificationUrl
|
||||||
|
? String(inputHostedCheckoutVerificationUrl.value || '').trim()
|
||||||
|
: String(latestState?.hostedCheckoutVerificationUrl || '').trim(),
|
||||||
|
hostedCheckoutPhoneNumber: typeof inputHostedCheckoutPhone !== 'undefined' && inputHostedCheckoutPhone
|
||||||
|
? String(inputHostedCheckoutPhone.value || '').trim()
|
||||||
|
: String(latestState?.hostedCheckoutPhoneNumber || '').trim(),
|
||||||
|
plusHostedCheckoutOauthDelaySeconds: normalizeHostedCheckoutDelaySecondsSafe(
|
||||||
|
typeof inputPlusHostedCheckoutOauthDelaySeconds !== 'undefined' && inputPlusHostedCheckoutOauthDelaySeconds
|
||||||
|
? inputPlusHostedCheckoutOauthDelaySeconds.value
|
||||||
|
: latestState?.plusHostedCheckoutOauthDelaySeconds
|
||||||
|
),
|
||||||
paypalEmail: String(currentPayPalAccount?.email || latestState?.paypalEmail || '').trim(),
|
paypalEmail: String(currentPayPalAccount?.email || latestState?.paypalEmail || '').trim(),
|
||||||
paypalPassword: String(currentPayPalAccount?.password || latestState?.paypalPassword || ''),
|
paypalPassword: String(currentPayPalAccount?.password || latestState?.paypalPassword || ''),
|
||||||
currentPayPalAccountId: String(latestState?.currentPayPalAccountId || '').trim(),
|
currentPayPalAccountId: String(latestState?.currentPayPalAccountId || '').trim(),
|
||||||
@@ -9143,6 +9196,7 @@ function updatePhoneVerificationSettingsUI() {
|
|||||||
|
|
||||||
function updatePlusModeUI() {
|
function updatePlusModeUI() {
|
||||||
const paypalValue = typeof PLUS_PAYMENT_METHOD_PAYPAL !== 'undefined' ? PLUS_PAYMENT_METHOD_PAYPAL : 'paypal';
|
const paypalValue = typeof PLUS_PAYMENT_METHOD_PAYPAL !== 'undefined' ? PLUS_PAYMENT_METHOD_PAYPAL : 'paypal';
|
||||||
|
const paypalHostedValue = typeof PLUS_PAYMENT_METHOD_PAYPAL_HOSTED !== 'undefined' ? PLUS_PAYMENT_METHOD_PAYPAL_HOSTED : 'paypal-hosted';
|
||||||
const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay';
|
const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay';
|
||||||
const gpcValue = typeof PLUS_PAYMENT_METHOD_GPC_HELPER !== 'undefined' ? PLUS_PAYMENT_METHOD_GPC_HELPER : 'gpc-helper';
|
const gpcValue = typeof PLUS_PAYMENT_METHOD_GPC_HELPER !== 'undefined' ? PLUS_PAYMENT_METHOD_GPC_HELPER : 'gpc-helper';
|
||||||
const oauthStrategyValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH !== 'undefined'
|
const oauthStrategyValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH !== 'undefined'
|
||||||
@@ -9259,6 +9313,7 @@ function updatePlusModeUI() {
|
|||||||
const selectedMethod = typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod?.value
|
const selectedMethod = typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod?.value
|
||||||
? normalizePlusPaymentMethod(selectPlusPaymentMethod.value)
|
? normalizePlusPaymentMethod(selectPlusPaymentMethod.value)
|
||||||
: method;
|
: method;
|
||||||
|
const hostedRowsVisible = enabled && selectedMethod === paypalHostedValue;
|
||||||
const gpcRowsVisible = enabled && selectedMethod === gpcValue;
|
const gpcRowsVisible = enabled && selectedMethod === gpcValue;
|
||||||
const canShowGpcModeSelector = gpcRowsVisible;
|
const canShowGpcModeSelector = gpcRowsVisible;
|
||||||
const localSmsControlsVisible = gpcRowsVisible && !isGpcAutoMode;
|
const localSmsControlsVisible = gpcRowsVisible && !isGpcAutoMode;
|
||||||
@@ -9277,6 +9332,8 @@ function updatePlusModeUI() {
|
|||||||
? `GPC ${isGpcAutoMode ? '自动' : '手动'}订阅链路`
|
? `GPC ${isGpcAutoMode ? '自动' : '手动'}订阅链路`
|
||||||
: method === gopayValue
|
: method === gopayValue
|
||||||
? 'GoPay 印尼订阅链路'
|
? 'GoPay 印尼订阅链路'
|
||||||
|
: method === paypalHostedValue
|
||||||
|
? 'PayPal 无卡直绑链路'
|
||||||
: 'PayPal 订阅链路';
|
: 'PayPal 订阅链路';
|
||||||
}
|
}
|
||||||
if (typeof plusPaymentMethodCaption !== 'undefined' && plusPaymentMethodCaption && method === gpcValue && gpcAutoModeBlocked) {
|
if (typeof plusPaymentMethodCaption !== 'undefined' && plusPaymentMethodCaption && method === gpcValue && gpcAutoModeBlocked) {
|
||||||
@@ -9335,12 +9392,35 @@ function updatePlusModeUI() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof plusAccountAccessStrategyCaption !== 'undefined' && plusAccountAccessStrategyCaption) {
|
if (typeof plusAccountAccessStrategyCaption !== 'undefined' && plusAccountAccessStrategyCaption) {
|
||||||
plusAccountAccessStrategyCaption.textContent = !enabled || !canEditPlusAccountAccessStrategy
|
plusAccountAccessStrategyCaption.textContent = !enabled
|
||||||
? '当前来源仅支持 OAuth'
|
? '当前来源仅支持 OAuth'
|
||||||
: describePlusAccountAccessStrategy(
|
: ((effectivePlusAccountAccessStrategy !== oauthStrategyValue || canEditPlusAccountAccessStrategy)
|
||||||
|
? describePlusAccountAccessStrategy(
|
||||||
effectivePlusAccountAccessStrategy,
|
effectivePlusAccountAccessStrategy,
|
||||||
effectiveTargetId
|
effectiveTargetId
|
||||||
);
|
)
|
||||||
|
: '当前来源仅支持 OAuth');
|
||||||
|
}
|
||||||
|
if (enabled && effectivePlusAccountAccessStrategy === sub2apiSessionStrategyValue) {
|
||||||
|
[
|
||||||
|
typeof rowSub2ApiUrl !== 'undefined' ? rowSub2ApiUrl : null,
|
||||||
|
typeof rowSub2ApiEmail !== 'undefined' ? rowSub2ApiEmail : null,
|
||||||
|
typeof rowSub2ApiPassword !== 'undefined' ? rowSub2ApiPassword : null,
|
||||||
|
typeof rowSub2ApiGroup !== 'undefined' ? rowSub2ApiGroup : null,
|
||||||
|
typeof rowSub2ApiAccountPriority !== 'undefined' ? rowSub2ApiAccountPriority : null,
|
||||||
|
typeof rowSub2ApiDefaultProxy !== 'undefined' ? rowSub2ApiDefaultProxy : null,
|
||||||
|
].forEach((row) => {
|
||||||
|
if (row) row.style.display = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (enabled && effectivePlusAccountAccessStrategy === cpaSessionStrategyValue) {
|
||||||
|
[
|
||||||
|
typeof rowVpsUrl !== 'undefined' ? rowVpsUrl : null,
|
||||||
|
typeof rowVpsPassword !== 'undefined' ? rowVpsPassword : null,
|
||||||
|
typeof rowLocalCpaStep9Mode !== 'undefined' ? rowLocalCpaStep9Mode : null,
|
||||||
|
].forEach((row) => {
|
||||||
|
if (row) row.style.display = '';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
[
|
[
|
||||||
typeof rowPayPalAccount !== 'undefined' ? rowPayPalAccount : null,
|
typeof rowPayPalAccount !== 'undefined' ? rowPayPalAccount : null,
|
||||||
@@ -9350,6 +9430,16 @@ function updatePlusModeUI() {
|
|||||||
}
|
}
|
||||||
row.style.display = enabled && selectedMethod === paypalValue ? '' : 'none';
|
row.style.display = enabled && selectedMethod === paypalValue ? '' : 'none';
|
||||||
});
|
});
|
||||||
|
[
|
||||||
|
typeof rowHostedCheckoutVerificationUrl !== 'undefined' ? rowHostedCheckoutVerificationUrl : null,
|
||||||
|
typeof rowHostedCheckoutPhone !== 'undefined' ? rowHostedCheckoutPhone : null,
|
||||||
|
typeof rowPlusHostedCheckoutOauthDelay !== 'undefined' ? rowPlusHostedCheckoutOauthDelay : null,
|
||||||
|
].forEach((row) => {
|
||||||
|
if (!row) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
row.style.display = hostedRowsVisible ? '' : 'none';
|
||||||
|
});
|
||||||
[
|
[
|
||||||
typeof rowGpcHelperApi !== 'undefined' ? rowGpcHelperApi : null,
|
typeof rowGpcHelperApi !== 'undefined' ? rowGpcHelperApi : null,
|
||||||
typeof rowGpcHelperCardKey !== 'undefined' ? rowGpcHelperCardKey : null,
|
typeof rowGpcHelperCardKey !== 'undefined' ? rowGpcHelperCardKey : null,
|
||||||
@@ -10507,6 +10597,17 @@ function applySettingsState(state) {
|
|||||||
if (typeof inputGoPayPin !== 'undefined' && inputGoPayPin) {
|
if (typeof inputGoPayPin !== 'undefined' && inputGoPayPin) {
|
||||||
inputGoPayPin.value = state?.gopayPin || '';
|
inputGoPayPin.value = state?.gopayPin || '';
|
||||||
}
|
}
|
||||||
|
if (typeof inputHostedCheckoutVerificationUrl !== 'undefined' && inputHostedCheckoutVerificationUrl) {
|
||||||
|
inputHostedCheckoutVerificationUrl.value = String(state?.hostedCheckoutVerificationUrl || '').trim();
|
||||||
|
}
|
||||||
|
if (typeof inputHostedCheckoutPhone !== 'undefined' && inputHostedCheckoutPhone) {
|
||||||
|
inputHostedCheckoutPhone.value = String(state?.hostedCheckoutPhoneNumber || '').trim();
|
||||||
|
}
|
||||||
|
if (typeof inputPlusHostedCheckoutOauthDelaySeconds !== 'undefined' && inputPlusHostedCheckoutOauthDelaySeconds) {
|
||||||
|
inputPlusHostedCheckoutOauthDelaySeconds.value = String(
|
||||||
|
normalizePlusHostedCheckoutOauthDelaySeconds(state?.plusHostedCheckoutOauthDelaySeconds)
|
||||||
|
);
|
||||||
|
}
|
||||||
inputVpsUrl.value = state?.vpsUrl || '';
|
inputVpsUrl.value = state?.vpsUrl || '';
|
||||||
inputVpsPassword.value = state?.vpsPassword || '';
|
inputVpsPassword.value = state?.vpsPassword || '';
|
||||||
setLocalCpaStep9Mode(state?.localCpaStep9Mode);
|
setLocalCpaStep9Mode(state?.localCpaStep9Mode);
|
||||||
@@ -14660,6 +14761,9 @@ inputVpsPassword.addEventListener('blur', () => {
|
|||||||
scheduleSettingsAutoSave();
|
scheduleSettingsAutoSave();
|
||||||
});
|
});
|
||||||
input?.addEventListener('blur', () => {
|
input?.addEventListener('blur', () => {
|
||||||
|
if (input === inputPlusHostedCheckoutOauthDelaySeconds) {
|
||||||
|
input.value = String(normalizePlusHostedCheckoutOauthDelaySeconds(input.value));
|
||||||
|
}
|
||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -14721,29 +14825,6 @@ inputPlusModeEnabled?.addEventListener('change', () => {
|
|||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
|
|
||||||
selectPlusPaymentMethod?.addEventListener('change', () => {
|
|
||||||
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(selectPlusPaymentMethod.value);
|
|
||||||
updatePlusModeUI();
|
|
||||||
const stepDefinitionState = typeof resolveStepDefinitionCapabilityState === 'function'
|
|
||||||
? resolveStepDefinitionCapabilityState({
|
|
||||||
...(latestState || {}),
|
|
||||||
plusModeEnabled: Boolean(inputPlusModeEnabled?.checked),
|
|
||||||
signupMethod: getSelectedSignupMethod(),
|
|
||||||
}, {
|
|
||||||
signupMethod: getSelectedSignupMethod(),
|
|
||||||
})
|
|
||||||
: {
|
|
||||||
plusModeEnabled: Boolean(inputPlusModeEnabled?.checked),
|
|
||||||
signupMethod: getSelectedSignupMethod(),
|
|
||||||
};
|
|
||||||
syncStepDefinitionsForMode(stepDefinitionState.plusModeEnabled, selectPlusPaymentMethod.value, {
|
|
||||||
render: true,
|
|
||||||
signupMethod: stepDefinitionState.signupMethod,
|
|
||||||
});
|
|
||||||
markSettingsDirty(true);
|
|
||||||
saveSettings({ silent: true }).catch(() => { });
|
|
||||||
});
|
|
||||||
|
|
||||||
btnGpcCardKeyPurchase?.addEventListener('click', () => {
|
btnGpcCardKeyPurchase?.addEventListener('click', () => {
|
||||||
openExternalUrl('https://pay.ldxp.cn/shop/gpc');
|
openExternalUrl('https://pay.ldxp.cn/shop/gpc');
|
||||||
});
|
});
|
||||||
@@ -14842,6 +14923,7 @@ btnTestKiroRs?.addEventListener('click', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
selectPlusPaymentMethod?.addEventListener('change', () => {
|
selectPlusPaymentMethod?.addEventListener('change', () => {
|
||||||
|
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(selectPlusPaymentMethod.value);
|
||||||
updatePlusModeUI();
|
updatePlusModeUI();
|
||||||
const stepDefinitionState = typeof resolveStepDefinitionCapabilityState === 'function'
|
const stepDefinitionState = typeof resolveStepDefinitionCapabilityState === 'function'
|
||||||
? resolveStepDefinitionCapabilityState({
|
? resolveStepDefinitionCapabilityState({
|
||||||
@@ -14878,6 +14960,9 @@ selectPlusPaymentMethod?.addEventListener('change', () => {
|
|||||||
inputGoPayPhone,
|
inputGoPayPhone,
|
||||||
inputGoPayOtp,
|
inputGoPayOtp,
|
||||||
inputGoPayPin,
|
inputGoPayPin,
|
||||||
|
inputHostedCheckoutVerificationUrl,
|
||||||
|
inputHostedCheckoutPhone,
|
||||||
|
inputPlusHostedCheckoutOauthDelaySeconds,
|
||||||
].forEach((input) => {
|
].forEach((input) => {
|
||||||
input?.addEventListener('input', () => {
|
input?.addEventListener('input', () => {
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
|
|||||||
@@ -230,6 +230,7 @@ return {
|
|||||||
assert.equal(api.normalizePersistentSettingValue('phoneSignupReloginAfterBindEmailEnabled', 0), false);
|
assert.equal(api.normalizePersistentSettingValue('phoneSignupReloginAfterBindEmailEnabled', 0), false);
|
||||||
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'gopay'), 'gopay');
|
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'gopay'), 'gopay');
|
||||||
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'gpc-helper'), 'gpc-helper');
|
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'gpc-helper'), 'gpc-helper');
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'paypal-hosted'), 'paypal-hosted');
|
||||||
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'paypal'), 'paypal');
|
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'paypal'), 'paypal');
|
||||||
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'unknown'), 'paypal');
|
assert.equal(api.normalizePersistentSettingValue('plusPaymentMethod', 'unknown'), 'paypal');
|
||||||
assert.equal(api.normalizePersistentSettingValue('plusAccountAccessStrategy', 'sub2api_codex_session'), 'sub2api_codex_session');
|
assert.equal(api.normalizePersistentSettingValue('plusAccountAccessStrategy', 'sub2api_codex_session'), 'sub2api_codex_session');
|
||||||
|
|||||||
@@ -127,6 +127,35 @@ return { shouldReportReadyForFrame };
|
|||||||
assert.equal(api.shouldReportReadyForFrame('unknown-source', true), false);
|
assert.equal(api.shouldReportReadyForFrame('unknown-source', true), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('simulateClick logs the button text captured before click side effects', () => {
|
||||||
|
const bundle = [extractFunction('simulateClick')].join('\n');
|
||||||
|
const logs = [];
|
||||||
|
const consoleMessages = [];
|
||||||
|
const api = new Function('logs', 'console', 'location', `
|
||||||
|
function throwIfStopped() {}
|
||||||
|
const LOG_PREFIX = '[test]';
|
||||||
|
function log(message) { logs.push(message); }
|
||||||
|
${bundle}
|
||||||
|
return { simulateClick };
|
||||||
|
`)(logs, { log: (...args) => consoleMessages.push(args.join(' ')) }, { pathname: '/checkout' });
|
||||||
|
|
||||||
|
const button = {
|
||||||
|
tagName: 'BUTTON',
|
||||||
|
textContent: '订阅',
|
||||||
|
getAttribute: () => '',
|
||||||
|
click() {
|
||||||
|
this.textContent = '正在处理';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
api.simulateClick(button);
|
||||||
|
|
||||||
|
assert.equal(button.textContent, '正在处理');
|
||||||
|
assert.equal(logs.at(-1), '已点击(click) [BUTTON] "订阅"');
|
||||||
|
assert.match(consoleMessages.at(-1), /BUTTON 订阅/);
|
||||||
|
assert.doesNotMatch(logs.at(-1), /正在处理/);
|
||||||
|
});
|
||||||
|
|
||||||
test('getRuntimeScriptSource follows injected source overrides after utils is already loaded', () => {
|
test('getRuntimeScriptSource follows injected source overrides after utils is already loaded', () => {
|
||||||
const bundle = [extractFunction('getRuntimeScriptSource')].join('\n');
|
const bundle = [extractFunction('getRuntimeScriptSource')].join('\n');
|
||||||
const api = new Function('window', 'SCRIPT_SOURCE', `
|
const api = new Function('window', 'SCRIPT_SOURCE', `
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ test('flow capability registry exposes editable Plus account access strategies f
|
|||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
capabilityState.availablePlusAccountAccessStrategies,
|
capabilityState.availablePlusAccountAccessStrategies,
|
||||||
['oauth', 'sub2api_codex_session']
|
['oauth', 'cpa_codex_session', 'sub2api_codex_session']
|
||||||
);
|
);
|
||||||
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'sub2api_codex_session');
|
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'sub2api_codex_session');
|
||||||
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, '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(
|
assert.deepEqual(
|
||||||
capabilityState.availablePlusAccountAccessStrategies,
|
capabilityState.availablePlusAccountAccessStrategies,
|
||||||
['oauth', 'cpa_codex_session']
|
['oauth', 'cpa_codex_session', 'sub2api_codex_session']
|
||||||
);
|
);
|
||||||
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'cpa_codex_session');
|
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'cpa_codex_session');
|
||||||
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, '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');
|
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 account access strategies independent from the current target', () => {
|
||||||
const api = loadApi();
|
const api = loadApi();
|
||||||
const registry = api.createFlowCapabilityRegistry();
|
const registry = api.createFlowCapabilityRegistry();
|
||||||
|
|
||||||
@@ -258,9 +258,34 @@ test('flow capability registry forces OAuth when the current target only support
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(capabilityState.availablePlusAccountAccessStrategies, ['oauth']);
|
assert.deepEqual(
|
||||||
|
capabilityState.availablePlusAccountAccessStrategies,
|
||||||
|
['oauth', 'cpa_codex_session', 'sub2api_codex_session']
|
||||||
|
);
|
||||||
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'cpa_codex_session');
|
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'cpa_codex_session');
|
||||||
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'oauth');
|
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'cpa_codex_session');
|
||||||
assert.equal(capabilityState.canEditPlusAccountAccessStrategy, false);
|
assert.equal(capabilityState.canEditPlusAccountAccessStrategy, true);
|
||||||
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'oauth');
|
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'cpa_codex_session');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('flow capability registry forces SUB2API session import only for contribution mode Plus runs', () => {
|
||||||
|
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.equal(capabilityState.requestedPlusAccountAccessStrategy, 'cpa_codex_session');
|
||||||
|
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'sub2api_codex_session');
|
||||||
|
assert.equal(capabilityState.canEditPlusAccountAccessStrategy, false);
|
||||||
|
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'sub2api_codex_session');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -87,6 +87,30 @@ test('settings schema normalizes view input into canonical nested namespaces', (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('settings schema lets explicit flat step range override stale canonical range', () => {
|
||||||
|
const { settingsSchema } = loadApis();
|
||||||
|
const schema = settingsSchema.createSettingsSchema();
|
||||||
|
const oldState = schema.normalizeSettingsState({
|
||||||
|
activeFlowId: 'openai',
|
||||||
|
stepExecutionRangeByFlow: {
|
||||||
|
openai: { enabled: true, fromStep: 3, toStep: 6 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalized = schema.normalizeSettingsState({
|
||||||
|
settingsState: oldState,
|
||||||
|
stepExecutionRangeByFlow: {
|
||||||
|
openai: { enabled: false, fromStep: 3, toStep: 6 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(normalized.flows.openai.autoRun.stepExecutionRange, {
|
||||||
|
enabled: false,
|
||||||
|
fromStep: 3,
|
||||||
|
toStep: 6,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('settings schema can project canonical state into a read view without legacy rebuild helpers', () => {
|
test('settings schema can project canonical state into a read view without legacy rebuild helpers', () => {
|
||||||
const { settingsSchema } = loadApis();
|
const { settingsSchema } = loadApis();
|
||||||
const schema = settingsSchema.createSettingsSchema();
|
const schema = settingsSchema.createSettingsSchema();
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ test('GoPay utils normalize manual OTP input', () => {
|
|||||||
|
|
||||||
test('GoPay utils keeps GPC helper payment method distinct', () => {
|
test('GoPay utils keeps GPC helper payment method distinct', () => {
|
||||||
const api = loadGoPayUtils();
|
const api = loadGoPayUtils();
|
||||||
|
assert.equal(api.normalizePlusPaymentMethod('paypal-hosted'), 'paypal-hosted');
|
||||||
|
assert.equal(api.normalizePlusPaymentMethod('paypal_direct'), 'paypal-hosted');
|
||||||
assert.equal(api.normalizePlusPaymentMethod('gpc-helper'), 'gpc-helper');
|
assert.equal(api.normalizePlusPaymentMethod('gpc-helper'), 'gpc-helper');
|
||||||
assert.equal(api.normalizePlusPaymentMethod('gopay'), 'gopay');
|
assert.equal(api.normalizePlusPaymentMethod('gopay'), 'gopay');
|
||||||
assert.equal(api.normalizePlusPaymentMethod('unknown'), 'paypal');
|
assert.equal(api.normalizePlusPaymentMethod('unknown'), 'paypal');
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const test = require('node:test');
|
const test = require('node:test');
|
||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
|
const vm = require('node:vm');
|
||||||
|
|
||||||
const source = fs.readFileSync('content/paypal-flow.js', 'utf8');
|
const source = fs.readFileSync('content/paypal-flow.js', 'utf8');
|
||||||
|
|
||||||
@@ -308,3 +309,381 @@ test('PayPal email submit refills a prefilled email before clicking next', async
|
|||||||
awaiting: 'password_page',
|
awaiting: 'password_page',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function createHostedPayPalHarness(options = {}) {
|
||||||
|
const events = [];
|
||||||
|
const attrs = new Map();
|
||||||
|
const elementsById = new Map();
|
||||||
|
let elements = [];
|
||||||
|
let listener = null;
|
||||||
|
const body = { innerText: '', textContent: '' };
|
||||||
|
const location = {
|
||||||
|
href: 'https://www.paypal.com/checkoutweb/signup',
|
||||||
|
host: 'www.paypal.com',
|
||||||
|
pathname: '/checkoutweb/signup',
|
||||||
|
};
|
||||||
|
|
||||||
|
function createDomElement({
|
||||||
|
tagName = 'DIV',
|
||||||
|
id = '',
|
||||||
|
type = '',
|
||||||
|
name = '',
|
||||||
|
text = '',
|
||||||
|
value = '',
|
||||||
|
attrs: initialAttrs = {},
|
||||||
|
options: selectOptions = [],
|
||||||
|
} = {}) {
|
||||||
|
const attrMap = new Map(Object.entries(initialAttrs));
|
||||||
|
const element = {
|
||||||
|
nodeType: 1,
|
||||||
|
tagName,
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
textContent: text,
|
||||||
|
innerText: text,
|
||||||
|
value,
|
||||||
|
checked: false,
|
||||||
|
disabled: false,
|
||||||
|
hidden: false,
|
||||||
|
options: selectOptions,
|
||||||
|
parentElement: null,
|
||||||
|
style: { display: 'block', visibility: 'visible', opacity: '1' },
|
||||||
|
getAttribute(key) {
|
||||||
|
if (key === 'id') return this.id;
|
||||||
|
if (key === 'type') return this.type;
|
||||||
|
if (key === 'name') return this.name;
|
||||||
|
if (key === 'placeholder') return attrMap.get('placeholder') || '';
|
||||||
|
return attrMap.has(key) ? attrMap.get(key) : null;
|
||||||
|
},
|
||||||
|
setAttribute(key, nextValue) {
|
||||||
|
attrMap.set(key, String(nextValue));
|
||||||
|
},
|
||||||
|
dispatchEvent() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
focus() {},
|
||||||
|
blur() {},
|
||||||
|
click() {
|
||||||
|
events.push({ type: 'native-click', id: this.id, text: this.textContent });
|
||||||
|
},
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { left: 10, top: 10, width: 180, height: 44 };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (id) elementsById.set(id, element);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
const countrySelect = createDomElement({ tagName: 'SELECT', id: 'country', value: 'US' });
|
||||||
|
const emailInput = createDomElement({ tagName: 'INPUT', id: 'email', type: 'email', name: 'email' });
|
||||||
|
const phoneInput = createDomElement({ tagName: 'INPUT', id: 'phone', type: 'tel', name: 'phone' });
|
||||||
|
const cardNumberInput = createDomElement({ tagName: 'INPUT', id: 'cardNumber', type: 'text' });
|
||||||
|
const cardExpiryInput = createDomElement({ tagName: 'INPUT', id: 'cardExpiry', type: 'text' });
|
||||||
|
const cardCvvInput = createDomElement({ tagName: 'INPUT', id: 'cardCvv', type: 'text' });
|
||||||
|
const passwordInput = createDomElement({ tagName: 'INPUT', id: 'password', type: 'password' });
|
||||||
|
const firstNameInput = createDomElement({ tagName: 'INPUT', id: 'firstName', type: 'text' });
|
||||||
|
const lastNameInput = createDomElement({ tagName: 'INPUT', id: 'lastName', type: 'text' });
|
||||||
|
const billingLine1Input = createDomElement({ tagName: 'INPUT', id: 'billingLine1', type: 'text' });
|
||||||
|
const billingCityInput = createDomElement({ tagName: 'INPUT', id: 'billingCity', type: 'text' });
|
||||||
|
const billingPostalCodeInput = createDomElement({ tagName: 'INPUT', id: 'billingPostalCode', type: 'text' });
|
||||||
|
const billingStateSelect = createDomElement({
|
||||||
|
tagName: 'SELECT',
|
||||||
|
id: 'billingState',
|
||||||
|
value: '',
|
||||||
|
options: [
|
||||||
|
{ textContent: 'New York', label: 'New York', value: 'NY' },
|
||||||
|
{ textContent: 'California', label: 'California', value: 'CA' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const submitButton = createDomElement({
|
||||||
|
tagName: 'BUTTON',
|
||||||
|
id: 'hostedSubmit',
|
||||||
|
text: 'Agree & Create Account',
|
||||||
|
attrs: { 'data-testid': 'submit-button' },
|
||||||
|
});
|
||||||
|
const createAccountButton = createDomElement({
|
||||||
|
tagName: 'BUTTON',
|
||||||
|
id: 'createAccountButton',
|
||||||
|
text: 'Agree & Create Account',
|
||||||
|
attrs: { 'data-testid': 'createAccountButton' },
|
||||||
|
});
|
||||||
|
const nextButton = createDomElement({
|
||||||
|
tagName: 'BUTTON',
|
||||||
|
id: 'btnNext',
|
||||||
|
text: '下一页',
|
||||||
|
});
|
||||||
|
|
||||||
|
function setElements(nextElements) {
|
||||||
|
elements = nextElements;
|
||||||
|
elementsById.clear();
|
||||||
|
for (const element of nextElements) {
|
||||||
|
if (element.id) elementsById.set(element.id, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showGuestCheckout() {
|
||||||
|
location.href = 'https://www.paypal.com/checkoutweb/signup';
|
||||||
|
location.host = 'www.paypal.com';
|
||||||
|
location.pathname = '/checkoutweb/signup';
|
||||||
|
body.innerText = 'Pay with debit or credit card';
|
||||||
|
body.textContent = body.innerText;
|
||||||
|
setElements([
|
||||||
|
countrySelect,
|
||||||
|
emailInput,
|
||||||
|
phoneInput,
|
||||||
|
cardNumberInput,
|
||||||
|
cardExpiryInput,
|
||||||
|
cardCvvInput,
|
||||||
|
passwordInput,
|
||||||
|
firstNameInput,
|
||||||
|
lastNameInput,
|
||||||
|
billingLine1Input,
|
||||||
|
billingCityInput,
|
||||||
|
billingPostalCodeInput,
|
||||||
|
billingStateSelect,
|
||||||
|
submitButton,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCreateAccount() {
|
||||||
|
location.href = 'https://www.paypal.com/checkoutweb/create-account';
|
||||||
|
location.host = 'www.paypal.com';
|
||||||
|
location.pathname = '/checkoutweb/create-account';
|
||||||
|
body.innerText = 'Create your PayPal account. Agree & Create Account';
|
||||||
|
body.textContent = body.innerText;
|
||||||
|
setElements([createAccountButton]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPayEmail() {
|
||||||
|
location.href = 'https://www.paypal.com/pay?token=demo';
|
||||||
|
location.host = 'www.paypal.com';
|
||||||
|
location.pathname = '/pay';
|
||||||
|
body.innerText = '请输入您的电子邮箱地址。 下一页 或 创建账户';
|
||||||
|
body.textContent = body.innerText;
|
||||||
|
setElements([emailInput, nextButton, createAccountButton]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
console: { log() {}, warn() {}, error() {}, info() {} },
|
||||||
|
location,
|
||||||
|
window: {},
|
||||||
|
Event: class TestEvent { constructor(type) { this.type = type; } },
|
||||||
|
MouseEvent: class TestMouseEvent { constructor(type) { this.type = type; } },
|
||||||
|
PointerEvent: class TestPointerEvent { constructor(type) { this.type = type; } },
|
||||||
|
document: {
|
||||||
|
readyState: 'complete',
|
||||||
|
body,
|
||||||
|
documentElement: {
|
||||||
|
getAttribute(name) {
|
||||||
|
return attrs.get(name) || null;
|
||||||
|
},
|
||||||
|
setAttribute(name, nextValue) {
|
||||||
|
attrs.set(name, String(nextValue));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getElementById(id) {
|
||||||
|
return elementsById.get(id) || null;
|
||||||
|
},
|
||||||
|
querySelector(selector) {
|
||||||
|
const text = String(selector || '');
|
||||||
|
if (text.includes('createAccountButton') || text.includes('create-account-button')) {
|
||||||
|
return elements.includes(createAccountButton) ? createAccountButton : null;
|
||||||
|
}
|
||||||
|
if (text.includes('submit-button') || text.includes('hosted-payment-submit-button')) {
|
||||||
|
return elements.includes(submitButton) ? submitButton : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
querySelectorAll(selector) {
|
||||||
|
const text = String(selector || '');
|
||||||
|
if (text === 'input') return elements.filter((element) => element.tagName === 'INPUT');
|
||||||
|
if (text === 'input[type="email"]') return elements.filter((element) => element.type === 'email');
|
||||||
|
if (text === 'input[type="password"]') return elements.filter((element) => element.type === 'password');
|
||||||
|
if (text.includes('button') || text.includes('[role="button"]')) {
|
||||||
|
return elements.filter((element) => element.tagName === 'BUTTON');
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
chrome: {
|
||||||
|
runtime: {
|
||||||
|
onMessage: {
|
||||||
|
addListener(fn) {
|
||||||
|
listener = fn;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CodexOperationDelay: {
|
||||||
|
async performOperationWithDelay(metadata, operation) {
|
||||||
|
events.push({ type: 'operation', metadata });
|
||||||
|
const result = await operation();
|
||||||
|
events.push({ type: 'delay', metadata });
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resetStopState() {},
|
||||||
|
isStopError() { return false; },
|
||||||
|
throwIfStopped() {},
|
||||||
|
sleep() { return Promise.resolve(); },
|
||||||
|
fillInput(element, value) {
|
||||||
|
if (element === phoneInput && typeof options.renderPhone === 'function') {
|
||||||
|
element.value = options.renderPhone(value);
|
||||||
|
} else {
|
||||||
|
element.value = value;
|
||||||
|
}
|
||||||
|
events.push({ type: 'fill', id: element.id, value: element.value });
|
||||||
|
},
|
||||||
|
simulateClick(element) {
|
||||||
|
events.push({ type: 'click', id: element.id, text: element.textContent });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
context.window = context;
|
||||||
|
context.window.getComputedStyle = (element) => element?.style || { display: 'block', visibility: 'visible', opacity: '1' };
|
||||||
|
|
||||||
|
vm.createContext(context);
|
||||||
|
vm.runInContext(source, context);
|
||||||
|
assert.equal(typeof listener, 'function');
|
||||||
|
|
||||||
|
async function send(message) {
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
listener(message, {}, resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
events,
|
||||||
|
send,
|
||||||
|
showPayEmail,
|
||||||
|
showCreateAccount,
|
||||||
|
showGuestCheckout,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('PayPal hosted guest checkout verifies configured local phone before submit', async () => {
|
||||||
|
const harness = createHostedPayPalHarness({
|
||||||
|
renderPhone: (value) => `+1 ${value}`,
|
||||||
|
});
|
||||||
|
harness.showGuestCheckout();
|
||||||
|
|
||||||
|
const result = await harness.send({
|
||||||
|
type: 'PAYPAL_RUN_HOSTED_CHECKOUT_STEP',
|
||||||
|
source: 'test',
|
||||||
|
payload: {
|
||||||
|
expectedStage: 'guest_checkout',
|
||||||
|
email: 'guest@example.com',
|
||||||
|
phone: '4155551234',
|
||||||
|
cardNumber: '4147200000000000',
|
||||||
|
cardExpiry: '12 / 29',
|
||||||
|
cardCvv: '123',
|
||||||
|
password: 'Aa1!example',
|
||||||
|
firstName: 'James',
|
||||||
|
lastName: 'Smith',
|
||||||
|
address: {
|
||||||
|
street: '1 Main St',
|
||||||
|
city: 'New York',
|
||||||
|
state: 'New York',
|
||||||
|
zip: '10001',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.equal(result.submitted, true);
|
||||||
|
assert.equal(result.phoneMatched, true);
|
||||||
|
assert.equal(result.payloadPhoneDigits, '4155551234');
|
||||||
|
assert.equal(result.renderedPhoneDigits, '14155551234');
|
||||||
|
assert.equal(harness.events.some((event) => event.type === 'click' && event.id === 'hostedSubmit'), true);
|
||||||
|
assert.deepEqual(
|
||||||
|
JSON.parse(JSON.stringify(harness.events.filter((event) => event.type === 'operation').map((event) => event.metadata))),
|
||||||
|
[{ stepKey: 'paypal-hosted-card', kind: 'click', label: 'hosted-paypal-card-submit' }]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('PayPal hosted guest checkout blocks submit when rendered phone differs from config', async () => {
|
||||||
|
const harness = createHostedPayPalHarness({
|
||||||
|
renderPhone: () => '9999999999',
|
||||||
|
});
|
||||||
|
harness.showGuestCheckout();
|
||||||
|
|
||||||
|
const result = await harness.send({
|
||||||
|
type: 'PAYPAL_RUN_HOSTED_CHECKOUT_STEP',
|
||||||
|
source: 'test',
|
||||||
|
payload: {
|
||||||
|
expectedStage: 'guest_checkout',
|
||||||
|
phone: '4155551234',
|
||||||
|
cardNumber: '4147200000000000',
|
||||||
|
cardExpiry: '12 / 29',
|
||||||
|
cardCvv: '123',
|
||||||
|
password: 'Aa1!example',
|
||||||
|
address: { street: '1 Main St', city: 'New York', state: 'New York', zip: '10001' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.match(result.error, /电话不一致/);
|
||||||
|
assert.equal(harness.events.some((event) => event.type === 'click' && event.id === 'hostedSubmit'), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('PayPal hosted /pay email page fills email and clicks Next instead of Create Account', async () => {
|
||||||
|
const harness = createHostedPayPalHarness();
|
||||||
|
harness.showPayEmail();
|
||||||
|
|
||||||
|
const state = await harness.send({
|
||||||
|
type: 'PAYPAL_HOSTED_GET_STATE',
|
||||||
|
source: 'test',
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
assert.equal(state.ok, true);
|
||||||
|
assert.equal(state.hostedStage, 'pay_login');
|
||||||
|
assert.equal(state.hasHostedEmailInput, true);
|
||||||
|
|
||||||
|
const result = await harness.send({
|
||||||
|
type: 'PAYPAL_RUN_HOSTED_CHECKOUT_STEP',
|
||||||
|
source: 'test',
|
||||||
|
payload: {
|
||||||
|
expectedStage: 'pay_login',
|
||||||
|
email: 'guest@example.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.equal(result.stage, 'pay_login');
|
||||||
|
assert.equal(result.submitted, true);
|
||||||
|
assert.equal(harness.events.some((event) => event.type === 'fill' && event.id === 'email' && event.value === 'guest@example.com'), true);
|
||||||
|
assert.equal(harness.events.some((event) => event.type === 'click' && event.id === 'btnNext'), true);
|
||||||
|
assert.equal(harness.events.some((event) => event.type === 'click' && event.id === 'createAccountButton'), false);
|
||||||
|
assert.deepEqual(
|
||||||
|
JSON.parse(JSON.stringify(harness.events.filter((event) => event.type === 'operation').map((event) => event.metadata))),
|
||||||
|
[{ stepKey: 'paypal-hosted-email', kind: 'click', label: 'hosted-paypal-email-next' }]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('PayPal hosted create account page is detected and handled as its own step', async () => {
|
||||||
|
const harness = createHostedPayPalHarness();
|
||||||
|
harness.showCreateAccount();
|
||||||
|
|
||||||
|
const state = await harness.send({
|
||||||
|
type: 'PAYPAL_HOSTED_GET_STATE',
|
||||||
|
source: 'test',
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
assert.equal(state.ok, true);
|
||||||
|
assert.equal(state.hostedStage, 'create_account');
|
||||||
|
assert.equal(state.createAccountReady, true);
|
||||||
|
|
||||||
|
const result = await harness.send({
|
||||||
|
type: 'PAYPAL_RUN_HOSTED_CHECKOUT_STEP',
|
||||||
|
source: 'test',
|
||||||
|
payload: { expectedStage: 'create_account' },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.equal(result.stage, 'create_account');
|
||||||
|
assert.equal(result.submitted, true);
|
||||||
|
assert.equal(harness.events.some((event) => event.type === 'click' && event.id === 'createAccountButton'), true);
|
||||||
|
assert.deepEqual(
|
||||||
|
JSON.parse(JSON.stringify(harness.events.filter((event) => event.type === 'operation').map((event) => event.metadata))),
|
||||||
|
[{ stepKey: 'paypal-hosted-create-account', kind: 'click', label: 'hosted-paypal-create-account' }]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -115,19 +115,21 @@ return {
|
|||||||
`)();
|
`)();
|
||||||
}
|
}
|
||||||
|
|
||||||
test('sidepanel keeps requested Plus account strategy while OAuth-only targets force the effective value', () => {
|
test('sidepanel keeps requested Plus account strategy while contribution mode forces SUB2API', () => {
|
||||||
const api = buildHarness(
|
const api = buildHarness(
|
||||||
`{
|
`{
|
||||||
canShowPlusSettings: true,
|
canShowPlusSettings: true,
|
||||||
runtimeLocks: { plusModeEnabled: true },
|
runtimeLocks: { plusModeEnabled: true, accountContribution: true },
|
||||||
canEditPlusAccountAccessStrategy: false,
|
canEditPlusAccountAccessStrategy: false,
|
||||||
effectivePlusAccountAccessStrategy: 'oauth',
|
availablePlusAccountAccessStrategies: ['sub2api_codex_session'],
|
||||||
|
effectivePlusAccountAccessStrategy: 'sub2api_codex_session',
|
||||||
}`,
|
}`,
|
||||||
`{
|
`{
|
||||||
activeFlowId: 'openai',
|
activeFlowId: 'openai',
|
||||||
panelMode: 'cpa',
|
panelMode: 'cpa',
|
||||||
plusPaymentMethod: 'paypal',
|
plusPaymentMethod: 'paypal',
|
||||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
accountContributionEnabled: true,
|
||||||
|
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -135,10 +137,10 @@ test('sidepanel keeps requested Plus account strategy while OAuth-only targets f
|
|||||||
|
|
||||||
assert.equal(api.rowPlusAccountAccessStrategy.style.display, '');
|
assert.equal(api.rowPlusAccountAccessStrategy.style.display, '');
|
||||||
assert.equal(api.selectPlusAccountAccessStrategy.disabled, true);
|
assert.equal(api.selectPlusAccountAccessStrategy.disabled, true);
|
||||||
assert.equal(api.selectPlusAccountAccessStrategy.dataset.requestedValue, 'sub2api_codex_session');
|
assert.equal(api.selectPlusAccountAccessStrategy.dataset.requestedValue, 'cpa_codex_session');
|
||||||
assert.equal(api.selectPlusAccountAccessStrategy.value, 'oauth');
|
assert.equal(api.selectPlusAccountAccessStrategy.value, 'sub2api_codex_session');
|
||||||
assert.equal(api.getRequestedPlusAccountAccessStrategy(), 'sub2api_codex_session');
|
assert.equal(api.getRequestedPlusAccountAccessStrategy(), 'cpa_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', () => {
|
test('sidepanel enables SUB2API session strategy selection when the current Plus target supports it', () => {
|
||||||
@@ -283,6 +285,7 @@ return {
|
|||||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||||
signupMethod: 'email',
|
signupMethod: 'email',
|
||||||
phoneSignupReloginAfterBindEmailEnabled: false,
|
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||||
|
accountContributionEnabled: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -294,6 +297,7 @@ return {
|
|||||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||||
signupMethod: 'email',
|
signupMethod: 'email',
|
||||||
phoneSignupReloginAfterBindEmailEnabled: false,
|
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);
|
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();
|
const attrs = new Map();
|
||||||
let listener = null;
|
let listener = null;
|
||||||
const fetchCalls = [];
|
const fetchCalls = [];
|
||||||
@@ -84,7 +84,7 @@ function createPlusCheckoutMessageHarness({ checkoutSessionId = 'cs_test_123' }
|
|||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
status: 200,
|
status: 200,
|
||||||
json: async () => ({ checkout_session_id: checkoutSessionId }),
|
json: async () => (checkoutResponse || { checkout_session_id: checkoutSessionId }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw new Error(`unexpected fetch url: ${url}`);
|
throw new Error(`unexpected fetch url: ${url}`);
|
||||||
@@ -153,6 +153,40 @@ test('CREATE_PLUS_CHECKOUT uses ID/IDR and openai_llc merchant path for GoPay',
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('CREATE_PLUS_CHECKOUT uses hosted US/USD checkout for PayPal no-card binding', async () => {
|
||||||
|
const hostedUrl = 'https://pay.openai.com/c/pay/cs_hosted_123';
|
||||||
|
const harness = createPlusCheckoutMessageHarness({
|
||||||
|
checkoutSessionId: 'cs_hosted',
|
||||||
|
checkoutResponse: {
|
||||||
|
checkout_session_id: 'cs_hosted',
|
||||||
|
next_action: {
|
||||||
|
redirect_to_url: {
|
||||||
|
url: hostedUrl,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await harness.send({
|
||||||
|
type: 'CREATE_PLUS_CHECKOUT',
|
||||||
|
source: 'test',
|
||||||
|
payload: { paymentMethod: 'paypal-hosted' },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.equal(result.checkoutUrl, 'https://chatgpt.com/checkout/openai_llc/cs_hosted');
|
||||||
|
assert.equal(result.hostedCheckoutUrl, hostedUrl);
|
||||||
|
assert.equal(result.preferredCheckoutUrl, hostedUrl);
|
||||||
|
assert.equal(result.country, 'US');
|
||||||
|
assert.equal(result.currency, 'USD');
|
||||||
|
|
||||||
|
const checkoutCall = harness.fetchCalls.find((call) => call.url === 'https://chatgpt.com/backend-api/payments/checkout');
|
||||||
|
assert.ok(checkoutCall);
|
||||||
|
const payload = JSON.parse(checkoutCall.options.body);
|
||||||
|
assert.equal(payload.checkout_ui_mode, 'hosted');
|
||||||
|
assert.deepEqual(payload.billing_details, { country: 'US', currency: 'USD' });
|
||||||
|
});
|
||||||
|
|
||||||
function extractFunction(name) {
|
function extractFunction(name) {
|
||||||
const plainStart = source.indexOf(`function ${name}(`);
|
const plainStart = source.indexOf(`function ${name}(`);
|
||||||
const asyncStart = source.indexOf(`async function ${name}(`);
|
const asyncStart = source.indexOf(`async function ${name}(`);
|
||||||
@@ -532,6 +566,49 @@ return { findSubscribeButton };
|
|||||||
assert.equal(api.findSubscribeButton(), submitButton);
|
assert.equal(api.findSubscribeButton(), submitButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('getSubscribeButtonState does not treat processing subscribe text as clickable', () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('normalizeText'),
|
||||||
|
extractFunction('getActionText'),
|
||||||
|
extractFunction('getSearchText'),
|
||||||
|
extractFunction('getFieldText'),
|
||||||
|
extractFunction('getCombinedSearchText'),
|
||||||
|
extractFunction('isEnabledControl'),
|
||||||
|
extractFunction('isBusySubscribeButton'),
|
||||||
|
'const SUBSCRIBE_READY_TEXT_PATTERN = /\\u8ba2\\u9605|\\u7ee7\\u7eed|\\u786e\\u8ba4|\\u652f\\u4ed8|subscribe|continue|confirm|pay|\\u8d2d\\u4e70\\s*ChatGPT\\s*Plus|start\\s*subscription|place\\s*order/i;',
|
||||||
|
'const SUBSCRIBE_PROCESSING_TEXT_PATTERN = /\\u6b63\\u5728\\u5904\\u7406|\\u5904\\u7406\\u4e2d|\\u8bf7\\u7a0d\\u5019|\\u52a0\\u8f7d\\u4e2d|loading|processing|submitting/i;',
|
||||||
|
extractFunction('getSubscribeButtonState'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const processingButton = createElement({
|
||||||
|
tagName: 'BUTTON',
|
||||||
|
text: '订阅 正在处理',
|
||||||
|
attrs: { type: 'submit' },
|
||||||
|
});
|
||||||
|
const readyButton = createElement({
|
||||||
|
tagName: 'BUTTON',
|
||||||
|
text: '订阅',
|
||||||
|
attrs: { type: 'submit' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
${bundle}
|
||||||
|
return { getSubscribeButtonState };
|
||||||
|
`)();
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
{
|
||||||
|
ready: api.getSubscribeButtonState(processingButton).ready,
|
||||||
|
status: api.getSubscribeButtonState(processingButton).status,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ready: false,
|
||||||
|
status: 'processing',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert.equal(api.getSubscribeButtonState(readyButton).ready, true);
|
||||||
|
});
|
||||||
|
|
||||||
test('humanLikeClick submits a detached submit button through its form attribute', async () => {
|
test('humanLikeClick submits a detached submit button through its form attribute', async () => {
|
||||||
const bundle = [
|
const bundle = [
|
||||||
'function throwIfStopped() {}',
|
'function throwIfStopped() {}',
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ function createExecutorHarness({
|
|||||||
getState = null,
|
getState = null,
|
||||||
queryTabsInAutomationWindow = null,
|
queryTabsInAutomationWindow = null,
|
||||||
markCurrentRegistrationAccountUsed = async () => {},
|
markCurrentRegistrationAccountUsed = async () => {},
|
||||||
|
onClickSubscribe = null,
|
||||||
probeIpProxyExit = null,
|
probeIpProxyExit = null,
|
||||||
onSetState = null,
|
onSetState = null,
|
||||||
sleepWithStop = null,
|
sleepWithStop = null,
|
||||||
@@ -137,8 +138,15 @@ function createExecutorHarness({
|
|||||||
return stateByFrame[frameId] || { hasPayPal: false, paypalCandidates: [] };
|
return stateByFrame[frameId] || { hasPayPal: false, paypalCandidates: [] };
|
||||||
}
|
}
|
||||||
if (message.type === 'PLUS_CHECKOUT_CLICK_SUBSCRIBE') {
|
if (message.type === 'PLUS_CHECKOUT_CLICK_SUBSCRIBE') {
|
||||||
|
if (typeof onClickSubscribe === 'function') {
|
||||||
|
const clickResult = await onClickSubscribe({ checkoutTab, events, frameId, message, tabId });
|
||||||
|
if (clickResult !== undefined) {
|
||||||
|
return clickResult;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
checkoutTab.url = submitRedirectUrl;
|
checkoutTab.url = submitRedirectUrl;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return createSuccessfulBillingResult();
|
return createSuccessfulBillingResult();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -242,6 +250,57 @@ test('Plus checkout billing uses the current checkout tab when step 6 did not re
|
|||||||
assert.equal(events.logs.some((entry) => /当前已在 Plus Checkout 页面/.test(entry.message)), true);
|
assert.equal(events.logs.some((entry) => /当前已在 Plus Checkout 页面/.test(entry.message)), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Plus checkout billing waits on processing subscribe text before clicking a ready subscribe button again', async () => {
|
||||||
|
const originalNow = Date.now;
|
||||||
|
let now = 0;
|
||||||
|
let clickCalls = 0;
|
||||||
|
Date.now = () => now;
|
||||||
|
try {
|
||||||
|
const { events, executor } = createExecutorHarness({
|
||||||
|
frames: [{ frameId: 0, url: 'https://chatgpt.com/checkout/openai_ie/cs_test' }],
|
||||||
|
stateByFrame: {
|
||||||
|
0: {
|
||||||
|
hasPayPal: true,
|
||||||
|
paypalCandidates: [{ tag: 'button', text: 'PayPal' }],
|
||||||
|
billingFieldsVisible: true,
|
||||||
|
hasSubscribeButton: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onClickSubscribe: async ({ checkoutTab }) => {
|
||||||
|
clickCalls += 1;
|
||||||
|
if (clickCalls === 1) {
|
||||||
|
return {
|
||||||
|
clicked: false,
|
||||||
|
subscribeButtonStatus: 'processing',
|
||||||
|
subscribeButtonText: '订阅正在处理',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
checkoutTab.url = 'https://www.paypal.com/checkoutnow';
|
||||||
|
return {
|
||||||
|
clicked: true,
|
||||||
|
subscribeButtonStatus: 'clicked',
|
||||||
|
subscribeButtonText: '订阅',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
sleepWithStop: async (ms) => {
|
||||||
|
events.sleeps.push(ms);
|
||||||
|
now += ms;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executePlusCheckoutBilling({});
|
||||||
|
|
||||||
|
const subscribeMessages = events.messages.filter((entry) => entry.message.type === 'PLUS_CHECKOUT_CLICK_SUBSCRIBE');
|
||||||
|
assert.equal(subscribeMessages.length, 2);
|
||||||
|
assert.equal(subscribeMessages.some((entry) => entry.message.payload.allowBusySubscribeButton !== undefined), false);
|
||||||
|
assert.equal(events.sleeps.filter((ms) => ms === 500).length >= 20, true);
|
||||||
|
assert.equal(events.logs.some((entry) => /本轮未点击/.test(entry.message)), true);
|
||||||
|
assert.equal(events.completed[0].step, 'plus-checkout-billing');
|
||||||
|
} finally {
|
||||||
|
Date.now = originalNow;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('Plus checkout billing searches checkout tabs inside the locked automation window', async () => {
|
test('Plus checkout billing searches checkout tabs inside the locked automation window', async () => {
|
||||||
const queries = [];
|
const queries = [];
|
||||||
const { checkoutTab, executor } = createExecutorHarness({
|
const { checkoutTab, executor } = createExecutorHarness({
|
||||||
|
|||||||
@@ -288,6 +288,272 @@ test('GoPay plus checkout create forwards gopay payment method to the checkout c
|
|||||||
assert.deepStrictEqual(events[0]?.payload, { paymentMethod: 'gopay' });
|
assert.deepStrictEqual(events[0]?.payload, { paymentMethod: 'gopay' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('PayPal no-card binding create opens and submits hosted OpenAI checkout before completing', async () => {
|
||||||
|
const events = [];
|
||||||
|
let currentUrl = 'https://chatgpt.com/';
|
||||||
|
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: 55, url: payload.url, status: 'complete' };
|
||||||
|
},
|
||||||
|
update: async (tabId, payload) => {
|
||||||
|
currentUrl = payload.url;
|
||||||
|
events.push({ type: 'tab-update', tabId, payload });
|
||||||
|
return { id: tabId, url: currentUrl, status: 'complete' };
|
||||||
|
},
|
||||||
|
get: async (tabId) => ({ id: tabId, url: currentUrl, status: 'complete' }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeNodeFromBackground: async (step, payload) => {
|
||||||
|
events.push({ type: 'complete', step, payload });
|
||||||
|
},
|
||||||
|
ensureContentScriptReadyOnTabUntilStopped: async (source, tabId, options) => {
|
||||||
|
events.push({ type: 'ready', source, tabId, options });
|
||||||
|
},
|
||||||
|
fetch: async (url) => {
|
||||||
|
events.push({ type: 'fetch', url });
|
||||||
|
assert.equal(url, 'https://www.meiguodizhi.com/api/v1/dz');
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({
|
||||||
|
address: {
|
||||||
|
Address: '1 Main St',
|
||||||
|
City: 'New York',
|
||||||
|
State: 'New York',
|
||||||
|
Zip_Code: '10001',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getState: async () => {
|
||||||
|
events.push({ type: 'get-state' });
|
||||||
|
return {
|
||||||
|
hostedCheckoutPhoneNumber: '4155551234',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
registerTab: async (source, tabId) => {
|
||||||
|
events.push({ type: 'register', source, tabId });
|
||||||
|
},
|
||||||
|
sendTabMessageUntilStopped: async (tabId, source, message) => {
|
||||||
|
events.push({ type: 'tab-message', tabId, source, message });
|
||||||
|
if (message.type === 'CREATE_PLUS_CHECKOUT') {
|
||||||
|
return {
|
||||||
|
checkoutUrl: 'https://chatgpt.com/checkout/openai_llc/cs_hosted',
|
||||||
|
preferredCheckoutUrl: 'https://pay.openai.com/c/pay/cs_hosted',
|
||||||
|
hostedCheckoutUrl: 'https://pay.openai.com/c/pay/cs_hosted',
|
||||||
|
country: 'US',
|
||||||
|
currency: 'USD',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (message.type === 'RUN_PAYPAL_HOSTED_OPENAI_CHECKOUT_STEP') {
|
||||||
|
currentUrl = 'https://www.paypal.com/pay?token=BA-hosted';
|
||||||
|
return { clicked: true };
|
||||||
|
}
|
||||||
|
throw new Error(`unexpected message type ${message.type}`);
|
||||||
|
},
|
||||||
|
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({
|
||||||
|
plusPaymentMethod: 'paypal-hosted',
|
||||||
|
plusHostedCheckoutOauthDelaySeconds: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
events.find((event) => event.type === 'tab-message' && event.message.type === 'CREATE_PLUS_CHECKOUT')?.message?.payload,
|
||||||
|
{ paymentMethod: 'paypal-hosted' }
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
events.find((event) => event.type === 'tab-update')?.payload?.url,
|
||||||
|
'https://pay.openai.com/c/pay/cs_hosted'
|
||||||
|
);
|
||||||
|
const statePayload = events.filter((event) => event.type === 'set-state').at(-1)?.payload || {};
|
||||||
|
assert.equal(statePayload.plusCheckoutSource, 'paypal-hosted');
|
||||||
|
assert.equal(statePayload.plusCheckoutCountry, 'US');
|
||||||
|
assert.equal(statePayload.plusCheckoutCurrency, 'USD');
|
||||||
|
assert.equal(statePayload.plusReturnUrl, '');
|
||||||
|
assert.equal(events.some((event) => event.type === 'tab-message' && event.message.type === 'FILL_PLUS_BILLING_AND_SUBMIT'), false);
|
||||||
|
assert.equal(events.some((event) => event.type === 'tab-message' && event.message.type === 'RUN_PAYPAL_HOSTED_OPENAI_CHECKOUT_STEP'), true);
|
||||||
|
assert.equal(
|
||||||
|
events.find((event) => event.type === 'tab-message' && event.message.type === 'RUN_PAYPAL_HOSTED_OPENAI_CHECKOUT_STEP')?.message?.payload?.address?.street,
|
||||||
|
'1 Main St'
|
||||||
|
);
|
||||||
|
assert.deepStrictEqual(events.find((event) => event.type === 'complete'), {
|
||||||
|
type: 'complete',
|
||||||
|
step: 'plus-checkout-create',
|
||||||
|
payload: {
|
||||||
|
plusCheckoutCountry: 'US',
|
||||||
|
plusCheckoutCurrency: 'USD',
|
||||||
|
plusCheckoutSource: 'paypal-hosted',
|
||||||
|
plusCheckoutUrl: 'https://www.paypal.com/pay?token=BA-hosted',
|
||||||
|
plusReturnUrl: '',
|
||||||
|
plusHostedCheckoutCompleted: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('PayPal no-card binding OpenAI checkout node submits hosted page and completes after success transition', async () => {
|
||||||
|
const events = [];
|
||||||
|
let currentUrl = 'https://pay.openai.com/c/pay/cs_hosted';
|
||||||
|
const executor = api.createPlusCheckoutCreateExecutor({
|
||||||
|
addLog: async (message, level = 'info') => {
|
||||||
|
events.push({ type: 'log', message, level });
|
||||||
|
},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
get: async (tabId) => ({ id: tabId, url: currentUrl, status: 'complete' }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeNodeFromBackground: async (step, payload) => {
|
||||||
|
events.push({ type: 'complete', step, payload });
|
||||||
|
},
|
||||||
|
ensureContentScriptReadyOnTabUntilStopped: async (source, tabId, options) => {
|
||||||
|
events.push({ type: 'ready', source, tabId, options });
|
||||||
|
},
|
||||||
|
fetch: async (url) => {
|
||||||
|
events.push({ type: 'fetch', url });
|
||||||
|
assert.equal(url, 'https://www.meiguodizhi.com/api/v1/dz');
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({
|
||||||
|
address: {
|
||||||
|
Address: '1 Main St',
|
||||||
|
City: 'New York',
|
||||||
|
State: 'New York',
|
||||||
|
Zip_Code: '10001',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getState: async () => ({
|
||||||
|
hostedCheckoutPhoneNumber: '(415) 555-1234',
|
||||||
|
}),
|
||||||
|
registerTab: async (source, tabId) => {
|
||||||
|
events.push({ type: 'register', source, tabId });
|
||||||
|
},
|
||||||
|
sendTabMessageUntilStopped: async (tabId, source, message) => {
|
||||||
|
events.push({ type: 'tab-message', tabId, source, message });
|
||||||
|
if (message.type === 'RUN_PAYPAL_HOSTED_OPENAI_CHECKOUT_STEP') {
|
||||||
|
currentUrl = 'https://chatgpt.com/backend-api/payments/success?session_id=cs_hosted';
|
||||||
|
return { clicked: true };
|
||||||
|
}
|
||||||
|
if (message.type === 'PLUS_CHECKOUT_GET_STATE') {
|
||||||
|
return { hostedVerificationVisible: false };
|
||||||
|
}
|
||||||
|
throw new Error(`unexpected message type ${message.type}`);
|
||||||
|
},
|
||||||
|
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.executePayPalHostedOpenAiCheckout({
|
||||||
|
plusCheckoutTabId: 55,
|
||||||
|
plusPaymentMethod: 'paypal-hosted',
|
||||||
|
hostedCheckoutPhoneNumber: '2125550000',
|
||||||
|
plusHostedCheckoutOauthDelaySeconds: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const profileState = events.find((event) => event.type === 'set-state' && event.payload.plusHostedCheckoutGuestProfile)?.payload || {};
|
||||||
|
assert.equal(profileState.plusHostedCheckoutGuestProfile.phone, '4155551234');
|
||||||
|
assert.equal(profileState.plusHostedCheckoutPhoneDigits, '4155551234');
|
||||||
|
assert.equal(
|
||||||
|
events.find((event) => event.type === 'tab-message' && event.message.type === 'RUN_PAYPAL_HOSTED_OPENAI_CHECKOUT_STEP')?.message?.payload?.address?.street,
|
||||||
|
'1 Main St'
|
||||||
|
);
|
||||||
|
assert.deepStrictEqual(events.find((event) => event.type === 'complete'), {
|
||||||
|
type: 'complete',
|
||||||
|
step: 'paypal-hosted-openai-checkout',
|
||||||
|
payload: {
|
||||||
|
plusCheckoutUrl: 'https://chatgpt.com/backend-api/payments/success?session_id=cs_hosted',
|
||||||
|
plusCheckoutSource: 'paypal-hosted',
|
||||||
|
plusReturnUrl: 'https://chatgpt.com/backend-api/payments/success?session_id=cs_hosted',
|
||||||
|
plusHostedCheckoutCompleted: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('PayPal hosted email node completes when Next navigation drops the content response', async () => {
|
||||||
|
const events = [];
|
||||||
|
let currentUrl = 'https://www.paypal.com/checkoutweb/pay?token=EC-test';
|
||||||
|
const executor = api.createPlusCheckoutCreateExecutor({
|
||||||
|
addLog: async (message, level = 'info', options = {}) => events.push({ type: 'log', message, level, options }),
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
get: async (tabId) => ({ id: tabId, url: currentUrl, status: 'complete' }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeNodeFromBackground: async (step, payload) => events.push({ type: 'complete', step, payload }),
|
||||||
|
ensureContentScriptReadyOnTabUntilStopped: async (source, tabId, options) => events.push({ type: 'ready', source, tabId, options }),
|
||||||
|
getState: async () => ({
|
||||||
|
hostedCheckoutPhoneNumber: '(415) 555-1234',
|
||||||
|
}),
|
||||||
|
registerTab: async (source, tabId) => events.push({ type: 'register', source, tabId }),
|
||||||
|
sendTabMessageUntilStopped: async (tabId, source, message) => {
|
||||||
|
events.push({ type: 'tab-message', tabId, source, message });
|
||||||
|
if (message.type === 'PAYPAL_RUN_HOSTED_CHECKOUT_STEP') {
|
||||||
|
currentUrl = 'https://www.paypal.com/checkoutweb/signup?ba_token=BA-test&token=EC-test';
|
||||||
|
return new Promise(() => {});
|
||||||
|
}
|
||||||
|
if (message.type === 'PAYPAL_HOSTED_GET_STATE') {
|
||||||
|
return {
|
||||||
|
hostedStage: currentUrl.includes('/signup')
|
||||||
|
? 'guest_checkout'
|
||||||
|
: 'pay_login',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error(`unexpected message type ${message.type}`);
|
||||||
|
},
|
||||||
|
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.executePayPalHostedEmail({
|
||||||
|
plusCheckoutTabId: 85661333,
|
||||||
|
plusHostedCheckoutGuestProfile: {
|
||||||
|
email: 'guest@example.com',
|
||||||
|
phone: '4155551234',
|
||||||
|
address: { street: '1 Main St', city: 'New York', state: 'New York', zip: '10001' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(currentUrl.includes('/signup'), true);
|
||||||
|
assert.equal(
|
||||||
|
events.some((event) => event.type === 'complete' && event.step === 'paypal-hosted-email'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
events.some((event) => event.type === 'log' && /已检测到 PayPal 进入后续页面(guest_checkout)/.test(event.message)),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
events.some((event) => event.type === 'tab-message' && event.message.type === 'PAYPAL_RUN_HOSTED_CHECKOUT_STEP'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('Plus checkout content routes billing operations through the operation delay gate', async () => {
|
test('Plus checkout content routes billing operations through the operation delay gate', async () => {
|
||||||
const { checkoutEvents, send } = createCheckoutContentHarness();
|
const { checkoutEvents, send } = createCheckoutContentHarness();
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ return {
|
|||||||
plusAccountAccessStrategy: 'oauth',
|
plusAccountAccessStrategy: 'oauth',
|
||||||
signupMethod: 'email',
|
signupMethod: 'email',
|
||||||
phoneSignupReloginAfterBindEmailEnabled: false,
|
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||||
|
accountContributionEnabled: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
assert.deepEqual(api.calls[1], { type: 'render', stepIds: [88] });
|
assert.deepEqual(api.calls[1], { type: 'render', stepIds: [88] });
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ return {
|
|||||||
assert.deepEqual(api.getStepIds(), [7]);
|
assert.deepEqual(api.getStepIds(), [7]);
|
||||||
assert.deepEqual(api.calls[0], {
|
assert.deepEqual(api.calls[0], {
|
||||||
type: 'getSteps',
|
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] });
|
assert.deepEqual(api.calls[1], { type: 'render', stepIds: [7] });
|
||||||
});
|
});
|
||||||
@@ -188,6 +188,67 @@ return { updatePlusModeUI, selectPlusPaymentMethod, rowPayPalAccount };
|
|||||||
assert.equal(api.rowPayPalAccount.style.display, '');
|
assert.equal(api.rowPayPalAccount.style.display, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sidepanel Plus UI separates PayPal account mode from PayPal no-card binding mode', () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('normalizePlusPaymentMethod'),
|
||||||
|
extractFunction('normalizePlusAccountAccessStrategy'),
|
||||||
|
extractFunction('getSelectedPlusPaymentMethod'),
|
||||||
|
extractFunction('getRequestedPlusAccountAccessStrategy'),
|
||||||
|
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||||
|
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||||
|
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||||
|
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||||
|
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||||
|
extractFunction('hasGpcAutoModePermissionField'),
|
||||||
|
extractFunction('isGpcAutoModePermissionDenied'),
|
||||||
|
extractFunction('normalizeGpcOtpChannelValue'),
|
||||||
|
extractFunction('updatePlusModeUI'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
let latestState = { plusPaymentMethod: 'paypal-hosted' };
|
||||||
|
let currentPlusPaymentMethod = 'paypal-hosted';
|
||||||
|
let currentPlusAccountAccessStrategy = 'oauth';
|
||||||
|
const inputPlusModeEnabled = { checked: true };
|
||||||
|
const selectPlusPaymentMethod = { value: 'paypal-hosted', style: { display: 'none' } };
|
||||||
|
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||||
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
|
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 plusPaymentMethodCaption = { textContent: '' };
|
||||||
|
const rowPayPalAccount = { style: { display: '' } };
|
||||||
|
const rowHostedCheckoutVerificationUrl = { style: { display: 'none' } };
|
||||||
|
const rowHostedCheckoutPhone = { style: { display: 'none' } };
|
||||||
|
const rowPlusHostedCheckoutOauthDelay = { style: { display: 'none' } };
|
||||||
|
${bundle}
|
||||||
|
return {
|
||||||
|
updatePlusModeUI,
|
||||||
|
selectPlusPaymentMethod,
|
||||||
|
rowPayPalAccount,
|
||||||
|
plusPaymentMethodCaption,
|
||||||
|
rows: { rowHostedCheckoutVerificationUrl, rowHostedCheckoutPhone, rowPlusHostedCheckoutOauthDelay },
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
api.updatePlusModeUI();
|
||||||
|
|
||||||
|
assert.equal(api.rowPayPalAccount.style.display, 'none');
|
||||||
|
assert.equal(api.rows.rowHostedCheckoutVerificationUrl.style.display, '');
|
||||||
|
assert.equal(api.rows.rowHostedCheckoutPhone.style.display, '');
|
||||||
|
assert.equal(api.rows.rowPlusHostedCheckoutOauthDelay.style.display, '');
|
||||||
|
assert.match(api.plusPaymentMethodCaption.textContent, /无卡直绑/);
|
||||||
|
|
||||||
|
api.selectPlusPaymentMethod.value = 'paypal';
|
||||||
|
api.updatePlusModeUI();
|
||||||
|
|
||||||
|
assert.equal(api.rowPayPalAccount.style.display, '');
|
||||||
|
assert.equal(api.rows.rowHostedCheckoutVerificationUrl.style.display, 'none');
|
||||||
|
assert.equal(api.rows.rowHostedCheckoutPhone.style.display, 'none');
|
||||||
|
assert.equal(api.rows.rowPlusHostedCheckoutOauthDelay.style.display, 'none');
|
||||||
|
});
|
||||||
|
|
||||||
test('sidepanel Plus UI can hide Plus controls when the shared flow capability registry disables them', () => {
|
test('sidepanel Plus UI can hide Plus controls when the shared flow capability registry disables them', () => {
|
||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('normalizePlusPaymentMethod'),
|
extractFunction('normalizePlusPaymentMethod'),
|
||||||
@@ -303,7 +364,7 @@ return {
|
|||||||
assert.deepEqual(api.getStepIds(), [13]);
|
assert.deepEqual(api.getStepIds(), [13]);
|
||||||
assert.deepEqual(api.calls[0], {
|
assert.deepEqual(api.calls[0], {
|
||||||
type: 'getSteps',
|
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 },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
phoneSignupReloginAfterBindEmailEnabled: true,
|
phoneSignupReloginAfterBindEmailEnabled: true,
|
||||||
});
|
});
|
||||||
const plusSteps = api.getSteps({ plusModeEnabled: true });
|
const plusSteps = api.getSteps({ plusModeEnabled: true });
|
||||||
|
const hostedSteps = api.getSteps({ plusModeEnabled: true, plusPaymentMethod: 'paypal-hosted' });
|
||||||
const plusPhoneSteps = api.getSteps({ plusModeEnabled: true, signupMethod: 'phone' });
|
const plusPhoneSteps = api.getSteps({ plusModeEnabled: true, signupMethod: 'phone' });
|
||||||
const plusPhoneReloginSteps = api.getSteps({
|
const plusPhoneReloginSteps = api.getSteps({
|
||||||
plusModeEnabled: true,
|
plusModeEnabled: true,
|
||||||
@@ -211,6 +212,34 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
assert.equal(plusSteps[5].title, '创建 Plus Checkout');
|
assert.equal(plusSteps[5].title, '创建 Plus Checkout');
|
||||||
assert.equal(plusSteps[7].title, 'PayPal 登录与授权');
|
assert.equal(plusSteps[7].title, 'PayPal 登录与授权');
|
||||||
|
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
hostedSteps.map((step) => step.key),
|
||||||
|
[
|
||||||
|
'open-chatgpt',
|
||||||
|
'submit-signup-email',
|
||||||
|
'fill-password',
|
||||||
|
'fetch-signup-code',
|
||||||
|
'fill-profile',
|
||||||
|
'plus-checkout-create',
|
||||||
|
'paypal-hosted-email',
|
||||||
|
'paypal-hosted-card',
|
||||||
|
'paypal-hosted-create-account',
|
||||||
|
'paypal-hosted-review',
|
||||||
|
'oauth-login',
|
||||||
|
'fetch-login-code',
|
||||||
|
'confirm-oauth',
|
||||||
|
'platform-verify',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert.equal(hostedSteps.some((step) => step.key === 'plus-checkout-billing'), false);
|
||||||
|
assert.equal(hostedSteps.some((step) => step.key === 'paypal-approve'), false);
|
||||||
|
assert.equal(hostedSteps.some((step) => step.key === 'plus-checkout-return'), false);
|
||||||
|
assert.equal(hostedSteps.some((step) => step.key === 'paypal-hosted-openai-checkout'), false);
|
||||||
|
assert.equal(hostedSteps.some((step) => step.key === 'paypal-hosted-verification'), false);
|
||||||
|
assert.equal(hostedSteps.find((step) => step.key === 'paypal-hosted-card')?.title, '无卡直绑填写 PayPal 资料');
|
||||||
|
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, plusPaymentMethod: 'paypal-hosted' }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);
|
||||||
|
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'paypal-hosted' }), 14);
|
||||||
|
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
goPaySteps.map((step) => step.key),
|
goPaySteps.map((step) => step.key),
|
||||||
[
|
[
|
||||||
@@ -279,6 +308,16 @@ test('Plus session strategy swaps the OAuth tail for a single SUB2API import nod
|
|||||||
previousNodeId: 'plus-checkout-return',
|
previousNodeId: 'plus-checkout-return',
|
||||||
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'paypal-hosted',
|
||||||
|
options: {
|
||||||
|
plusModeEnabled: true,
|
||||||
|
plusPaymentMethod: 'paypal-hosted',
|
||||||
|
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||||
|
},
|
||||||
|
previousNodeId: 'paypal-hosted-review',
|
||||||
|
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'gopay',
|
label: 'gopay',
|
||||||
options: {
|
options: {
|
||||||
@@ -360,6 +399,16 @@ test('Plus session strategy swaps the OAuth tail for a single CPA import node',
|
|||||||
previousNodeId: 'plus-checkout-return',
|
previousNodeId: 'plus-checkout-return',
|
||||||
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'paypal-hosted',
|
||||||
|
options: {
|
||||||
|
plusModeEnabled: true,
|
||||||
|
plusPaymentMethod: 'paypal-hosted',
|
||||||
|
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||||
|
},
|
||||||
|
previousNodeId: 'paypal-hosted-review',
|
||||||
|
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'gopay',
|
label: 'gopay',
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
Reference in New Issue
Block a user