Fix PayPal hosted checkout flow
This commit is contained in:
@@ -10618,7 +10618,6 @@ const AUTO_RUN_BACKGROUND_COMPLETED_STEP_KEYS = new Set([
|
|||||||
'plus-checkout-create',
|
'plus-checkout-create',
|
||||||
'paypal-hosted-openai-checkout',
|
'paypal-hosted-openai-checkout',
|
||||||
'paypal-hosted-email',
|
'paypal-hosted-email',
|
||||||
'paypal-hosted-verification',
|
|
||||||
'paypal-hosted-card',
|
'paypal-hosted-card',
|
||||||
'paypal-hosted-create-account',
|
'paypal-hosted-create-account',
|
||||||
'paypal-hosted-review',
|
'paypal-hosted-review',
|
||||||
@@ -11723,7 +11722,6 @@ const AUTO_RUN_NODE_DELAYS = Object.freeze({
|
|||||||
'plus-checkout-create': 3000,
|
'plus-checkout-create': 3000,
|
||||||
'paypal-hosted-openai-checkout': 2000,
|
'paypal-hosted-openai-checkout': 2000,
|
||||||
'paypal-hosted-email': 2000,
|
'paypal-hosted-email': 2000,
|
||||||
'paypal-hosted-verification': 2000,
|
|
||||||
'paypal-hosted-card': 2000,
|
'paypal-hosted-card': 2000,
|
||||||
'paypal-hosted-create-account': 2000,
|
'paypal-hosted-create-account': 2000,
|
||||||
'paypal-hosted-review': 2000,
|
'paypal-hosted-review': 2000,
|
||||||
@@ -13744,7 +13742,6 @@ const stepExecutorsByKey = {
|
|||||||
'plus-checkout-create': (state) => plusCheckoutCreateExecutor.executePlusCheckoutCreate(state),
|
'plus-checkout-create': (state) => plusCheckoutCreateExecutor.executePlusCheckoutCreate(state),
|
||||||
'paypal-hosted-openai-checkout': (state) => plusCheckoutCreateExecutor.executePayPalHostedOpenAiCheckout(state),
|
'paypal-hosted-openai-checkout': (state) => plusCheckoutCreateExecutor.executePayPalHostedOpenAiCheckout(state),
|
||||||
'paypal-hosted-email': (state) => plusCheckoutCreateExecutor.executePayPalHostedEmail(state),
|
'paypal-hosted-email': (state) => plusCheckoutCreateExecutor.executePayPalHostedEmail(state),
|
||||||
'paypal-hosted-verification': (state) => plusCheckoutCreateExecutor.executePayPalHostedVerification(state),
|
|
||||||
'paypal-hosted-card': (state) => plusCheckoutCreateExecutor.executePayPalHostedCard(state),
|
'paypal-hosted-card': (state) => plusCheckoutCreateExecutor.executePayPalHostedCard(state),
|
||||||
'paypal-hosted-create-account': (state) => plusCheckoutCreateExecutor.executePayPalHostedCreateAccount(state),
|
'paypal-hosted-create-account': (state) => plusCheckoutCreateExecutor.executePayPalHostedCreateAccount(state),
|
||||||
'paypal-hosted-review': (state) => plusCheckoutCreateExecutor.executePayPalHostedReview(state),
|
'paypal-hosted-review': (state) => plusCheckoutCreateExecutor.executePayPalHostedReview(state),
|
||||||
|
|||||||
@@ -23,24 +23,21 @@
|
|||||||
const PAYPAL_HOSTED_STAGE_OUTSIDE = 'outside_paypal';
|
const PAYPAL_HOSTED_STAGE_OUTSIDE = 'outside_paypal';
|
||||||
const PAYPAL_HOSTED_STAGE_LOGIN = 'pay_login';
|
const PAYPAL_HOSTED_STAGE_LOGIN = 'pay_login';
|
||||||
const PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT = 'guest_checkout';
|
const PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT = 'guest_checkout';
|
||||||
const PAYPAL_HOSTED_STAGE_VERIFICATION = 'verification';
|
|
||||||
const PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT = 'create_account';
|
const PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT = 'create_account';
|
||||||
const PAYPAL_HOSTED_STAGE_REVIEW = 'review_consent';
|
const PAYPAL_HOSTED_STAGE_REVIEW = 'review_consent';
|
||||||
const PAYPAL_HOSTED_STAGE_APPROVAL = 'approval';
|
const PAYPAL_HOSTED_STAGE_APPROVAL = 'approval';
|
||||||
const PAYPAL_HOSTED_STAGE_UNKNOWN = 'unknown';
|
const PAYPAL_HOSTED_STAGE_UNKNOWN = 'unknown';
|
||||||
const PAYPAL_HOSTED_STEP_OPENAI_CHECKOUT = 'paypal-hosted-openai-checkout';
|
const PAYPAL_HOSTED_STEP_OPENAI_CHECKOUT = 'paypal-hosted-openai-checkout';
|
||||||
const PAYPAL_HOSTED_STEP_EMAIL = 'paypal-hosted-email';
|
const PAYPAL_HOSTED_STEP_EMAIL = 'paypal-hosted-email';
|
||||||
const PAYPAL_HOSTED_STEP_VERIFICATION = 'paypal-hosted-verification';
|
|
||||||
const PAYPAL_HOSTED_STEP_CARD = 'paypal-hosted-card';
|
const PAYPAL_HOSTED_STEP_CARD = 'paypal-hosted-card';
|
||||||
const PAYPAL_HOSTED_STEP_CREATE_ACCOUNT = 'paypal-hosted-create-account';
|
const PAYPAL_HOSTED_STEP_CREATE_ACCOUNT = 'paypal-hosted-create-account';
|
||||||
const PAYPAL_HOSTED_STEP_REVIEW = 'paypal-hosted-review';
|
const PAYPAL_HOSTED_STEP_REVIEW = 'paypal-hosted-review';
|
||||||
const PAYPAL_HOSTED_STEP_META = Object.freeze({
|
const PAYPAL_HOSTED_STEP_META = Object.freeze({
|
||||||
[PAYPAL_HOSTED_STEP_OPENAI_CHECKOUT]: { step: 6, label: '创建 PayPal 无卡直绑 Checkout' },
|
[PAYPAL_HOSTED_STEP_OPENAI_CHECKOUT]: { step: 6, label: '创建 PayPal 无卡直绑 Checkout' },
|
||||||
[PAYPAL_HOSTED_STEP_EMAIL]: { step: 7, label: '无卡直绑 PayPal 邮箱页' },
|
[PAYPAL_HOSTED_STEP_EMAIL]: { step: 7, label: '无卡直绑 PayPal 邮箱页' },
|
||||||
[PAYPAL_HOSTED_STEP_VERIFICATION]: { step: 8, label: '无卡直绑 PayPal 验证码页' },
|
[PAYPAL_HOSTED_STEP_CARD]: { step: 8, label: '无卡直绑 PayPal 资料页' },
|
||||||
[PAYPAL_HOSTED_STEP_CARD]: { step: 9, label: '无卡直绑 PayPal 资料页' },
|
[PAYPAL_HOSTED_STEP_CREATE_ACCOUNT]: { step: 9, label: '无卡直绑 PayPal 创建确认页' },
|
||||||
[PAYPAL_HOSTED_STEP_CREATE_ACCOUNT]: { step: 10, label: '无卡直绑 PayPal 创建确认页' },
|
[PAYPAL_HOSTED_STEP_REVIEW]: { step: 10, label: '无卡直绑 PayPal 授权复核页' },
|
||||||
[PAYPAL_HOSTED_STEP_REVIEW]: { step: 11, label: '无卡直绑 PayPal 授权复核页' },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function createPlusCheckoutCreateExecutor(deps = {}) {
|
function createPlusCheckoutCreateExecutor(deps = {}) {
|
||||||
@@ -454,13 +451,13 @@
|
|||||||
async function fetchHostedVerificationCode(verificationUrl = '') {
|
async function fetchHostedVerificationCode(verificationUrl = '') {
|
||||||
const url = String(verificationUrl || '').trim();
|
const url = String(verificationUrl || '').trim();
|
||||||
if (!url) {
|
if (!url) {
|
||||||
throw new Error('未配置 PayPal 无卡直绑验证码接口。');
|
throw new Error('未配置 OpenAI Checkout 验证码接口。');
|
||||||
}
|
}
|
||||||
const fetcher = typeof fetchImpl === 'function'
|
const fetcher = typeof fetchImpl === 'function'
|
||||||
? fetchImpl
|
? fetchImpl
|
||||||
: (typeof fetch === 'function' ? fetch.bind(globalThis) : null);
|
: (typeof fetch === 'function' ? fetch.bind(globalThis) : null);
|
||||||
if (typeof fetcher !== 'function') {
|
if (typeof fetcher !== 'function') {
|
||||||
throw new Error('当前运行环境不支持 fetch,无法获取无卡直绑验证码。');
|
throw new Error('当前运行环境不支持 fetch,无法获取 OpenAI Checkout 验证码。');
|
||||||
}
|
}
|
||||||
const separator = url.includes('?') ? '&' : '?';
|
const separator = url.includes('?') ? '&' : '?';
|
||||||
const response = await fetcher(`${url}${separator}t=${Date.now()}`, {
|
const response = await fetcher(`${url}${separator}t=${Date.now()}`, {
|
||||||
@@ -487,17 +484,17 @@
|
|||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
try {
|
try {
|
||||||
const code = await fetchHostedVerificationCode(verificationUrl);
|
const code = await fetchHostedVerificationCode(verificationUrl);
|
||||||
await addLog(`步骤 6:已获取无卡直绑验证码(${attempt}/${HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS})。`, 'info');
|
await addLog(`步骤 6:已获取 OpenAI Checkout 验证码(${attempt}/${HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS})。`, 'info');
|
||||||
return code;
|
return code;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
lastError = error;
|
lastError = error;
|
||||||
await addLog(`步骤 6:无卡直绑验证码暂不可用(${attempt}/${HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS}):${error?.message || error}`, 'warn');
|
await addLog(`步骤 6:OpenAI Checkout 验证码暂不可用(${attempt}/${HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS}):${error?.message || error}`, 'warn');
|
||||||
if (attempt < HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS) {
|
if (attempt < HOSTED_CHECKOUT_VERIFICATION_POLL_ATTEMPTS) {
|
||||||
await sleepWithStop(HOSTED_CHECKOUT_VERIFICATION_POLL_INTERVAL_MS);
|
await sleepWithStop(HOSTED_CHECKOUT_VERIFICATION_POLL_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw lastError || new Error('无卡直绑验证码轮询失败。');
|
throw lastError || new Error('OpenAI Checkout 验证码轮询失败。');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runHostedOpenAiCheckout(tabId, profile, config) {
|
async function runHostedOpenAiCheckout(tabId, profile, config) {
|
||||||
@@ -596,16 +593,14 @@
|
|||||||
switch (stage) {
|
switch (stage) {
|
||||||
case PAYPAL_HOSTED_STAGE_LOGIN:
|
case PAYPAL_HOSTED_STAGE_LOGIN:
|
||||||
return 1;
|
return 1;
|
||||||
case PAYPAL_HOSTED_STAGE_VERIFICATION:
|
|
||||||
return 2;
|
|
||||||
case PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT:
|
case PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT:
|
||||||
return 3;
|
return 2;
|
||||||
case PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT:
|
case PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT:
|
||||||
return 4;
|
return 3;
|
||||||
case PAYPAL_HOSTED_STAGE_REVIEW:
|
case PAYPAL_HOSTED_STAGE_REVIEW:
|
||||||
return 5;
|
return 4;
|
||||||
case PAYPAL_HOSTED_STAGE_OUTSIDE:
|
case PAYPAL_HOSTED_STAGE_OUTSIDE:
|
||||||
return 6;
|
return 5;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -668,6 +663,51 @@
|
|||||||
throw new Error(`${label}等待超时。`);
|
throw new Error(`${label}等待超时。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runHostedPayPalStepAndWaitForStageChange(tabId, payload = {}, previousStage = '', options = {}) {
|
||||||
|
const normalizedPreviousStage = String(previousStage || payload.expectedStage || '').trim();
|
||||||
|
const label = String(options.label || 'PayPal 无卡直绑页面跳转').trim();
|
||||||
|
const predicate = typeof options.predicate === 'function'
|
||||||
|
? options.predicate
|
||||||
|
: (stateInfo) => stateInfo?.hostedStage && stateInfo.hostedStage !== normalizedPreviousStage;
|
||||||
|
const stageChangePromise = waitForHostedPayPalStage(tabId, predicate, {
|
||||||
|
label,
|
||||||
|
timeoutMs: options.timeoutMs || HOSTED_CHECKOUT_TRANSITION_TIMEOUT_MS,
|
||||||
|
intervalMs: options.intervalMs || 500,
|
||||||
|
}).then(
|
||||||
|
(nextState) => ({ type: 'stage-change', nextState }),
|
||||||
|
(error) => ({ type: 'stage-error', error })
|
||||||
|
);
|
||||||
|
const actionPromise = runHostedPayPalStep(tabId, payload).then(
|
||||||
|
(result) => ({ type: 'action', result }),
|
||||||
|
(error) => ({ type: 'action-error', error })
|
||||||
|
);
|
||||||
|
|
||||||
|
const first = await Promise.race([actionPromise, stageChangePromise]);
|
||||||
|
if (first.type === 'stage-change') {
|
||||||
|
return {
|
||||||
|
result: null,
|
||||||
|
nextState: first.nextState,
|
||||||
|
completedByStageChange: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (first.type === 'action-error') {
|
||||||
|
throw first.error;
|
||||||
|
}
|
||||||
|
if (first.type === 'stage-error') {
|
||||||
|
throw first.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stageOutcome = await stageChangePromise;
|
||||||
|
if (stageOutcome.type === 'stage-change') {
|
||||||
|
return {
|
||||||
|
result: first.result,
|
||||||
|
nextState: stageOutcome.nextState,
|
||||||
|
completedByStageChange: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw stageOutcome.error;
|
||||||
|
}
|
||||||
|
|
||||||
function resolveCheckoutTargetUrl(result = {}, paymentMethod = PLUS_PAYMENT_METHOD_PAYPAL) {
|
function resolveCheckoutTargetUrl(result = {}, paymentMethod = PLUS_PAYMENT_METHOD_PAYPAL) {
|
||||||
if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) {
|
if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) {
|
||||||
return String(
|
return String(
|
||||||
@@ -799,7 +839,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pageState = await getHostedPayPalState(tabId);
|
const pageState = await getHostedPayPalState(tabId);
|
||||||
if (isHostedStageAtOrAfter(pageState.hostedStage, PAYPAL_HOSTED_STAGE_VERIFICATION)
|
if (isHostedStageAtOrAfter(pageState.hostedStage, PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT)
|
||||||
&& pageState.hostedStage !== PAYPAL_HOSTED_STAGE_LOGIN) {
|
&& pageState.hostedStage !== PAYPAL_HOSTED_STAGE_LOGIN) {
|
||||||
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:当前 PayPal 已进入后续页面(${pageState.hostedStage}),邮箱节点直接完成。`, 'info');
|
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:当前 PayPal 已进入后续页面(${pageState.hostedStage}),邮箱节点直接完成。`, 'info');
|
||||||
await completeHostedStep(stepKey, tabId, {
|
await completeHostedStep(stepKey, tabId, {
|
||||||
@@ -812,61 +852,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在填写 PayPal 无卡直绑邮箱。`, 'info');
|
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在填写 PayPal 无卡直绑邮箱。`, 'info');
|
||||||
await runHostedPayPalStep(tabId, {
|
const { nextState, completedByStageChange } = await runHostedPayPalStepAndWaitForStageChange(tabId, {
|
||||||
expectedStage: PAYPAL_HOSTED_STAGE_LOGIN,
|
expectedStage: PAYPAL_HOSTED_STAGE_LOGIN,
|
||||||
email: profile.email,
|
email: profile.email,
|
||||||
});
|
}, PAYPAL_HOSTED_STAGE_LOGIN, { label: `步骤 ${stepNumber}:等待 PayPal 邮箱页跳转` });
|
||||||
const nextState = await waitForHostedPayPalStage(
|
if (completedByStageChange) {
|
||||||
tabId,
|
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:已检测到 PayPal 进入后续页面(${nextState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN}),邮箱节点直接完成。`, 'info');
|
||||||
(stateInfo) => stateInfo?.hostedStage && stateInfo.hostedStage !== PAYPAL_HOSTED_STAGE_LOGIN,
|
|
||||||
{ label: `步骤 ${stepNumber}:等待 PayPal 邮箱页跳转` }
|
|
||||||
);
|
|
||||||
await completeHostedStep(stepKey, tabId, {
|
|
||||||
plusHostedCheckoutLastStage: nextState.hostedStage || '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function executePayPalHostedVerification(state = {}) {
|
|
||||||
const stepKey = PAYPAL_HOSTED_STEP_VERIFICATION;
|
|
||||||
const stepNumber = getHostedStepNumber(stepKey);
|
|
||||||
const tabId = await resolveHostedCheckoutTabId(state, stepKey);
|
|
||||||
if (await completeHostedStepIfSuccessful(stepKey, tabId, state)) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
await waitForHostedUrlAfterAction(
|
|
||||||
tabId,
|
|
||||||
(url) => isPayPalUrl(url) || isHostedCheckoutSuccessUrl(url),
|
|
||||||
{ label: `步骤 ${stepNumber}:等待 PayPal 验证码页` }
|
|
||||||
);
|
|
||||||
if (await completeHostedStepIfSuccessful(stepKey, tabId, state)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageState = await getHostedPayPalState(tabId);
|
|
||||||
if (isHostedStageAtOrAfter(pageState.hostedStage, PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT)
|
|
||||||
&& pageState.hostedStage !== PAYPAL_HOSTED_STAGE_VERIFICATION) {
|
|
||||||
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:当前 PayPal 已进入后续页面(${pageState.hostedStage}),验证码节点直接完成。`, 'info');
|
|
||||||
await completeHostedStep(stepKey, tabId, {
|
|
||||||
plusHostedCheckoutLastStage: pageState.hostedStage,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pageState.hostedStage !== PAYPAL_HOSTED_STAGE_VERIFICATION) {
|
|
||||||
throw new Error(`步骤 ${stepNumber}:当前不是 PayPal 验证码页(当前状态:${pageState.hostedStage || PAYPAL_HOSTED_STAGE_UNKNOWN})。`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = await getHostedCheckoutRuntimeConfig(state);
|
|
||||||
const verificationCode = await pollHostedVerificationCode(config.verificationUrl);
|
|
||||||
await addHostedStepLog(stepKey, `步骤 ${stepNumber}:正在填写 PayPal 无卡直绑验证码。`, 'info');
|
|
||||||
await runHostedPayPalStep(tabId, {
|
|
||||||
expectedStage: PAYPAL_HOSTED_STAGE_VERIFICATION,
|
|
||||||
verificationCode,
|
|
||||||
});
|
|
||||||
const nextState = await waitForHostedPayPalStage(
|
|
||||||
tabId,
|
|
||||||
(stateInfo) => stateInfo?.hostedStage && stateInfo.hostedStage !== PAYPAL_HOSTED_STAGE_VERIFICATION,
|
|
||||||
{ label: `步骤 ${stepNumber}:等待 PayPal 验证码页跳转` }
|
|
||||||
);
|
|
||||||
await completeHostedStep(stepKey, tabId, {
|
await completeHostedStep(stepKey, tabId, {
|
||||||
plusHostedCheckoutLastStage: nextState.hostedStage || '',
|
plusHostedCheckoutLastStage: nextState.hostedStage || '',
|
||||||
});
|
});
|
||||||
@@ -1459,7 +1451,6 @@
|
|||||||
executePlusCheckoutCreate,
|
executePlusCheckoutCreate,
|
||||||
executePayPalHostedOpenAiCheckout,
|
executePayPalHostedOpenAiCheckout,
|
||||||
executePayPalHostedEmail,
|
executePayPalHostedEmail,
|
||||||
executePayPalHostedVerification,
|
|
||||||
executePayPalHostedCard,
|
executePayPalHostedCard,
|
||||||
executePayPalHostedCreateAccount,
|
executePayPalHostedCreateAccount,
|
||||||
executePayPalHostedReview,
|
executePayPalHostedReview,
|
||||||
|
|||||||
+1
-48
@@ -7,14 +7,12 @@ const PAYPAL_HOSTED_DEFAULT_PHONE = '1234567890';
|
|||||||
const PAYPAL_HOSTED_STAGE_OUTSIDE = 'outside_paypal';
|
const PAYPAL_HOSTED_STAGE_OUTSIDE = 'outside_paypal';
|
||||||
const PAYPAL_HOSTED_STAGE_LOGIN = 'pay_login';
|
const PAYPAL_HOSTED_STAGE_LOGIN = 'pay_login';
|
||||||
const PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT = 'guest_checkout';
|
const PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT = 'guest_checkout';
|
||||||
const PAYPAL_HOSTED_STAGE_VERIFICATION = 'verification';
|
|
||||||
const PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT = 'create_account';
|
const PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT = 'create_account';
|
||||||
const PAYPAL_HOSTED_STAGE_REVIEW = 'review_consent';
|
const PAYPAL_HOSTED_STAGE_REVIEW = 'review_consent';
|
||||||
const PAYPAL_HOSTED_STAGE_APPROVAL = 'approval';
|
const PAYPAL_HOSTED_STAGE_APPROVAL = 'approval';
|
||||||
const PAYPAL_HOSTED_STAGE_UNKNOWN = 'unknown';
|
const PAYPAL_HOSTED_STAGE_UNKNOWN = 'unknown';
|
||||||
const PAYPAL_HOSTED_STEP_KEYS = {
|
const PAYPAL_HOSTED_STEP_KEYS = {
|
||||||
[PAYPAL_HOSTED_STAGE_LOGIN]: 'paypal-hosted-email',
|
[PAYPAL_HOSTED_STAGE_LOGIN]: 'paypal-hosted-email',
|
||||||
[PAYPAL_HOSTED_STAGE_VERIFICATION]: 'paypal-hosted-verification',
|
|
||||||
[PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT]: 'paypal-hosted-card',
|
[PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT]: 'paypal-hosted-card',
|
||||||
[PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT]: 'paypal-hosted-create-account',
|
[PAYPAL_HOSTED_STAGE_CREATE_ACCOUNT]: 'paypal-hosted-create-account',
|
||||||
[PAYPAL_HOSTED_STAGE_REVIEW]: 'paypal-hosted-review',
|
[PAYPAL_HOSTED_STAGE_REVIEW]: 'paypal-hosted-review',
|
||||||
@@ -249,15 +247,6 @@ function getPayPalPathname() {
|
|||||||
return String(location?.pathname || '').trim();
|
return String(location?.pathname || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function findHostedVerificationInputs() {
|
|
||||||
return Array.from({ length: 6 }, (_, index) => document.getElementById(`ci-ciBasic-${index}`))
|
|
||||||
.filter((input) => input && isVisibleElement(input) && isEnabledControl(input));
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasHostedVerificationInputs() {
|
|
||||||
return findHostedVerificationInputs().length >= 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isHostedLoginPage() {
|
function isHostedLoginPage() {
|
||||||
return getPayPalPathname() === '/pay' || Boolean(document.getElementById('email'));
|
return getPayPalPathname() === '/pay' || Boolean(document.getElementById('email'));
|
||||||
}
|
}
|
||||||
@@ -323,9 +312,6 @@ function detectPayPalHostedStage() {
|
|||||||
if (!/paypal\./i.test(String(location?.host || ''))) {
|
if (!/paypal\./i.test(String(location?.host || ''))) {
|
||||||
return PAYPAL_HOSTED_STAGE_OUTSIDE;
|
return PAYPAL_HOSTED_STAGE_OUTSIDE;
|
||||||
}
|
}
|
||||||
if (hasHostedVerificationInputs()) {
|
|
||||||
return PAYPAL_HOSTED_STAGE_VERIFICATION;
|
|
||||||
}
|
|
||||||
if (isHostedGuestCheckoutPage()) {
|
if (isHostedGuestCheckoutPage()) {
|
||||||
return PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT;
|
return PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT;
|
||||||
}
|
}
|
||||||
@@ -419,7 +405,6 @@ async function clickHostedSubmitButton(options = {}) {
|
|||||||
return {
|
return {
|
||||||
clicked: true,
|
clicked: true,
|
||||||
buttonText: lastButtonText,
|
buttonText: lastButtonText,
|
||||||
verificationRequired: hasHostedVerificationInputs(),
|
|
||||||
attempt,
|
attempt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -447,11 +432,9 @@ async function clickHostedEmailNextButton() {
|
|||||||
}, async () => {
|
}, async () => {
|
||||||
simulateClick(button);
|
simulateClick(button);
|
||||||
});
|
});
|
||||||
await sleep(1000);
|
|
||||||
return {
|
return {
|
||||||
clicked: true,
|
clicked: true,
|
||||||
buttonText,
|
buttonText,
|
||||||
verificationRequired: hasHostedVerificationInputs(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,30 +546,7 @@ async function submitHostedLogin(payload = {}) {
|
|||||||
stage: PAYPAL_HOSTED_STAGE_LOGIN,
|
stage: PAYPAL_HOSTED_STAGE_LOGIN,
|
||||||
submitted: true,
|
submitted: true,
|
||||||
generatedEmail: email,
|
generatedEmail: email,
|
||||||
verificationRequired: Boolean(clickResult.verificationRequired),
|
clicked: Boolean(clickResult.clicked),
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fillHostedVerificationCode(payload = {}) {
|
|
||||||
await waitForDocumentComplete();
|
|
||||||
const code = String(payload.verificationCode || payload.code || '').replace(/\D+/g, '').slice(0, 6);
|
|
||||||
if (code.length !== 6) {
|
|
||||||
throw new Error('PayPal hosted checkout 验证码无效。');
|
|
||||||
}
|
|
||||||
const inputs = findHostedVerificationInputs();
|
|
||||||
if (inputs.length < 6) {
|
|
||||||
throw new Error('PayPal hosted checkout 当前页面未显示验证码输入框。');
|
|
||||||
}
|
|
||||||
await performPayPalOperationWithDelay({
|
|
||||||
stepKey: getHostedStepKey(PAYPAL_HOSTED_STAGE_VERIFICATION),
|
|
||||||
kind: 'fill',
|
|
||||||
label: 'hosted-paypal-verification-code',
|
|
||||||
}, async () => {
|
|
||||||
inputs.forEach((input, index) => fillInput(input, code[index] || ''));
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
stage: PAYPAL_HOSTED_STAGE_VERIFICATION,
|
|
||||||
codeSubmitted: true,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,7 +591,6 @@ async function fillHostedGuestCheckout(payload = {}) {
|
|||||||
return {
|
return {
|
||||||
stage: PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT,
|
stage: PAYPAL_HOSTED_STAGE_GUEST_CHECKOUT,
|
||||||
submitted: true,
|
submitted: true,
|
||||||
verificationRequired: Boolean(clickResult.verificationRequired),
|
|
||||||
payloadPhone: values.phone,
|
payloadPhone: values.phone,
|
||||||
...phoneCheck,
|
...phoneCheck,
|
||||||
};
|
};
|
||||||
@@ -672,11 +631,6 @@ async function runPayPalHostedCheckoutStep(payload = {}) {
|
|||||||
approveReady: Boolean(findApproveButton()),
|
approveReady: Boolean(findApproveButton()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (stage === PAYPAL_HOSTED_STAGE_VERIFICATION) {
|
|
||||||
return payload.verificationCode || payload.code
|
|
||||||
? fillHostedVerificationCode(payload)
|
|
||||||
: { stage, requiresVerificationCode: true };
|
|
||||||
}
|
|
||||||
if (stage === PAYPAL_HOSTED_STAGE_LOGIN) {
|
if (stage === PAYPAL_HOSTED_STAGE_LOGIN) {
|
||||||
return submitHostedLogin(payload);
|
return submitHostedLogin(payload);
|
||||||
}
|
}
|
||||||
@@ -703,7 +657,6 @@ function inspectPayPalHostedState() {
|
|||||||
url: location.href,
|
url: location.href,
|
||||||
readyState: document.readyState,
|
readyState: document.readyState,
|
||||||
hostedStage: stage,
|
hostedStage: stage,
|
||||||
verificationInputsVisible: hasHostedVerificationInputs(),
|
|
||||||
hasGuestCardFields: Boolean(document.getElementById('cardNumber')),
|
hasGuestCardFields: Boolean(document.getElementById('cardNumber')),
|
||||||
hasHostedEmailInput: Boolean(document.getElementById('email') || findEmailInput()),
|
hasHostedEmailInput: Boolean(document.getElementById('email') || findEmailInput()),
|
||||||
createAccountReady: Boolean(createAccountButton && isVisibleElement(createAccountButton) && isEnabledControl(createAccountButton)),
|
createAccountReady: Boolean(createAccountButton && isVisibleElement(createAccountButton) && isEnabledControl(createAccountButton)),
|
||||||
|
|||||||
@@ -252,11 +252,22 @@ async function clickHostedSubmitButton() {
|
|||||||
});
|
});
|
||||||
document.activeElement?.blur?.();
|
document.activeElement?.blur?.();
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
|
const buttonTextBeforeClick = getActionText(button) || '订阅';
|
||||||
|
log(`Plus Checkout:准备点击“${buttonTextBeforeClick}”提交 OpenAI Checkout。`);
|
||||||
simulateClick(button);
|
simulateClick(button);
|
||||||
await sleep(1200);
|
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 {
|
return {
|
||||||
clicked: true,
|
clicked: true,
|
||||||
buttonText: getActionText(button),
|
buttonText: getActionText(button),
|
||||||
|
buttonTextBeforeClick,
|
||||||
|
buttonTextAfterClick,
|
||||||
hostedVerificationVisible: hasHostedOpenAiVerificationDialog(),
|
hostedVerificationVisible: hasHostedOpenAiVerificationDialog(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+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) || ''}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+10
-11
@@ -36,10 +36,9 @@
|
|||||||
const PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS = [
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS = [
|
||||||
...PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS.slice(0, 6),
|
...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: 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-verification', title: '无卡直绑填写 PayPal 验证码', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-hosted-verification' },
|
{ 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-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-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' },
|
||||||
{ id: 11, order: 110, 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 = [
|
||||||
@@ -239,23 +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, 12, 120, SIGNUP_METHOD_EMAIL);
|
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(
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_SUB2API_SESSION_STEP_DEFINITIONS = createHostedCheckoutSteps(
|
||||||
PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS,
|
PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS,
|
||||||
12,
|
11,
|
||||||
120,
|
110,
|
||||||
SIGNUP_METHOD_EMAIL,
|
SIGNUP_METHOD_EMAIL,
|
||||||
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION }
|
||||||
);
|
);
|
||||||
const PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS = createHostedCheckoutSteps(
|
const PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS = createHostedCheckoutSteps(
|
||||||
PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS,
|
PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS,
|
||||||
12,
|
11,
|
||||||
120,
|
110,
|
||||||
SIGNUP_METHOD_EMAIL,
|
SIGNUP_METHOD_EMAIL,
|
||||||
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION }
|
{ plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION }
|
||||||
);
|
);
|
||||||
const PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS = createHostedCheckoutSteps(PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS, 12, 120, SIGNUP_METHOD_PHONE);
|
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, 12, 120, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true });
|
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,
|
||||||
|
|||||||
@@ -203,7 +203,6 @@
|
|||||||
commands: [
|
commands: [
|
||||||
'paypal-approve',
|
'paypal-approve',
|
||||||
'paypal-hosted-email',
|
'paypal-hosted-email',
|
||||||
'paypal-hosted-verification',
|
|
||||||
'paypal-hosted-card',
|
'paypal-hosted-card',
|
||||||
'paypal-hosted-create-account',
|
'paypal-hosted-create-account',
|
||||||
'paypal-hosted-review',
|
'paypal-hosted-review',
|
||||||
|
|||||||
@@ -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', `
|
||||||
|
|||||||
@@ -494,6 +494,66 @@ test('PayPal no-card binding OpenAI checkout node submits hosted page and comple
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
|||||||
@@ -222,7 +222,6 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
'fill-profile',
|
'fill-profile',
|
||||||
'plus-checkout-create',
|
'plus-checkout-create',
|
||||||
'paypal-hosted-email',
|
'paypal-hosted-email',
|
||||||
'paypal-hosted-verification',
|
|
||||||
'paypal-hosted-card',
|
'paypal-hosted-card',
|
||||||
'paypal-hosted-create-account',
|
'paypal-hosted-create-account',
|
||||||
'paypal-hosted-review',
|
'paypal-hosted-review',
|
||||||
@@ -236,9 +235,10 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
assert.equal(hostedSteps.some((step) => step.key === 'paypal-approve'), 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 === '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-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.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, 15]);
|
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' }), 15);
|
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'paypal-hosted' }), 14);
|
||||||
|
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
goPaySteps.map((step) => step.key),
|
goPaySteps.map((step) => step.key),
|
||||||
@@ -316,7 +316,7 @@ test('Plus session strategy swaps the OAuth tail for a single SUB2API import nod
|
|||||||
plusAccountAccessStrategy: 'sub2api_codex_session',
|
plusAccountAccessStrategy: 'sub2api_codex_session',
|
||||||
},
|
},
|
||||||
previousNodeId: 'paypal-hosted-review',
|
previousNodeId: 'paypal-hosted-review',
|
||||||
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'gopay',
|
label: 'gopay',
|
||||||
@@ -407,7 +407,7 @@ test('Plus session strategy swaps the OAuth tail for a single CPA import node',
|
|||||||
plusAccountAccessStrategy: 'cpa_codex_session',
|
plusAccountAccessStrategy: 'cpa_codex_session',
|
||||||
},
|
},
|
||||||
previousNodeId: 'paypal-hosted-review',
|
previousNodeId: 'paypal-hosted-review',
|
||||||
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
expectedStepIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'gopay',
|
label: 'gopay',
|
||||||
|
|||||||
Reference in New Issue
Block a user