Fix PayPal hosted checkout flow

This commit is contained in:
QLHazyCoder
2026-05-20 11:34:34 +08:00
parent 9dbbce77ad
commit 0b58e1116e
10 changed files with 328 additions and 52 deletions
+29 -4
View File
@@ -292,6 +292,9 @@ function findHostedCreateAccountButton() {
}
function isHostedCreateAccountPage() {
if (isHostedLoginPage()) {
return false;
}
if (document.getElementById('cardNumber') || document.getElementById('billingLine1')) {
return false;
}
@@ -427,6 +430,31 @@ async function clickHostedSubmitButton(options = {}) {
};
}
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);
});
await sleep(1000);
return {
clicked: true,
buttonText,
verificationRequired: hasHostedVerificationInputs(),
};
}
function normalizeHostedPhoneDigits(value = '') {
return String(value || '').replace(/\D/g, '');
}
@@ -530,10 +558,7 @@ async function submitHostedLogin(payload = {}) {
throw new Error('PayPal hosted checkout 未找到邮箱输入框。');
}
refillPayPalEmailInput(emailInput, email);
const clickResult = await clickHostedSubmitButton({
stage: PAYPAL_HOSTED_STAGE_LOGIN,
label: 'hosted-paypal-email-submit',
});
const clickResult = await clickHostedEmailNextButton();
return {
stage: PAYPAL_HOSTED_STAGE_LOGIN,
submitted: true,
+56 -2
View File
@@ -1524,6 +1524,42 @@ function isBusySubscribeButton(button) {
|| /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) {
if (!button) return null;
if (button.form) return button.form;
@@ -1710,12 +1746,23 @@ async function clickPlusSubscribe(payload = {}) {
const subscribeButton = await waitUntil(() => {
const button = findSubscribeButton();
return button && isEnabledControl(button) && !isBusySubscribeButton(button) ? button : null;
return button || null;
}, {
label: '订阅按钮',
intervalMs: 250,
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 performOperationWithDelay({ stepKey: 'plus-checkout-billing', kind: 'submit', label: 'click-subscribe' }, async () => {
@@ -1723,6 +1770,8 @@ async function clickPlusSubscribe(payload = {}) {
});
return {
clicked: true,
subscribeButtonStatus: 'clicked',
subscribeButtonText: buttonState.text,
};
}
@@ -1739,6 +1788,7 @@ async function readChatGptSessionAccessToken() {
async function inspectPlusCheckoutState(options = {}) {
const structuredAddress = getStructuredAddressFields();
const subscribeButtonState = getSubscribeButtonState(findSubscribeButton());
const state = {
url: location.href,
readyState: document.readyState,
@@ -1750,7 +1800,11 @@ async function inspectPlusCheckoutState(options = {}) {
paymentTextPreview: getPaymentTextPreview(),
cardFieldsVisible: hasCreditCardFields(),
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()),