fix: harden GoPay and 5sim PR paths

This commit is contained in:
QLHazyCoder
2026-05-03 15:14:31 +08:00
parent 29093e08db
commit 61e9b40d85
8 changed files with 134 additions and 35 deletions
+4 -3
View File
@@ -84,6 +84,7 @@ const PLUS_GOPAY_STEP_IDS = PLUS_GOPAY_STEP_DEFINITIONS
.map((definition) => Number(definition?.id))
.filter(Number.isFinite)
.sort((left, right) => left - right);
const PLUS_STEP_IDS = PLUS_PAYPAL_STEP_IDS;
const LAST_STEP_ID = Math.max(
NORMAL_STEP_IDS[NORMAL_STEP_IDS.length - 1] || 10,
PLUS_PAYPAL_STEP_IDS[PLUS_PAYPAL_STEP_IDS.length - 1] || 10,
@@ -445,7 +446,7 @@ function getStepDefinitionsForState(state = {}) {
if (!isPlusModeState(state)) {
return NORMAL_STEP_DEFINITIONS;
}
return normalizePlusPaymentMethod(state?.plusPaymentMethod) === 'gopay'
return normalizePlusPaymentMethod(state?.plusPaymentMethod) === PLUS_PAYMENT_METHOD_GOPAY
? PLUS_GOPAY_STEP_DEFINITIONS
: PLUS_PAYPAL_STEP_DEFINITIONS;
}
@@ -454,7 +455,7 @@ function getStepIdsForState(state = {}) {
if (!isPlusModeState(state)) {
return NORMAL_STEP_IDS;
}
return normalizePlusPaymentMethod(state?.plusPaymentMethod) === 'gopay'
return normalizePlusPaymentMethod(state?.plusPaymentMethod) === PLUS_PAYMENT_METHOD_GOPAY
? PLUS_GOPAY_STEP_IDS
: PLUS_PAYPAL_STEP_IDS;
}
@@ -10054,7 +10055,7 @@ function getStepRegistryForState(state = {}) {
if (!isPlusModeState(state)) {
return normalStepRegistry;
}
return normalizePlusPaymentMethod(state?.plusPaymentMethod) === 'gopay'
return normalizePlusPaymentMethod(state?.plusPaymentMethod) === PLUS_PAYMENT_METHOD_GOPAY
? plusGoPayStepRegistry
: plusPayPalStepRegistry;
}
+14 -4
View File
@@ -275,6 +275,13 @@
return Math.max(500, Math.min(30000, parsed));
}
function assertFiveSimMaxPriceCompatibleWithOperator(operator, maxPriceLimit) {
const normalizedOperator = normalizeFiveSimCountryCode(operator, DEFAULT_FIVE_SIM_OPERATOR);
if (maxPriceLimit !== null && maxPriceLimit !== undefined && normalizedOperator !== DEFAULT_FIVE_SIM_OPERATOR) {
throw new Error('5sim maxPrice only works when operator is "any"; clear the price limit or switch operator to any before buying a number.');
}
}
function normalizeHeroSmsPriceLimit(value) {
if (value === undefined || value === null || String(value).trim() === '') {
return null;
@@ -1382,15 +1389,18 @@
throw new Error('5sim API key is missing. Save it in the side panel before running the phone flow.');
}
const configuredMaxPrice = normalizeHeroSmsPriceLimit(state.fiveSimMaxPrice);
const operator = normalizeFiveSimCountryCode(state.fiveSimOperator, DEFAULT_FIVE_SIM_OPERATOR);
const maxPriceLimit = configuredMaxPrice !== null
? configuredMaxPrice
: normalizeHeroSmsPriceLimit(state.heroSmsMaxPrice);
assertFiveSimMaxPriceCompatibleWithOperator(operator, maxPriceLimit);
return {
provider,
apiKey,
baseUrl: normalizeUrl(state.fiveSimBaseUrl, DEFAULT_FIVE_SIM_BASE_URL).replace(/\/+$/, ''),
operator: normalizeFiveSimCountryCode(state.fiveSimOperator, DEFAULT_FIVE_SIM_OPERATOR),
operator,
product: normalizeFiveSimCountryCode(state.fiveSimProduct, DEFAULT_FIVE_SIM_PRODUCT),
maxPriceLimit: configuredMaxPrice !== null
? configuredMaxPrice
: normalizeHeroSmsPriceLimit(state.heroSmsMaxPrice),
maxPriceLimit,
countryCandidates: resolveFiveSimCountryCandidates(state),
};
}
+3
View File
@@ -914,6 +914,9 @@
if (!target?.found || !rect || !Number.isFinite(rect.centerX) || !Number.isFinite(rect.centerY)) {
return { clicked: false, reason: 'target_not_found', clickTarget: target?.target || '' };
}
if (Number.isInteger(frameId)) {
return { clicked: false, reason: 'debugger_click_skipped_for_frame_target', clickTarget: target.target || '' };
}
await clickWithDebugger(tabId, rect);
return { clicked: true, clickTarget: target.target || '' };
}
+10
View File
@@ -574,6 +574,14 @@
return /not\s+enough\s+(?:user\s+)?balance|not\s+enough\s+rating|unauthorized|invalid\s+token|banned|bad\s+(?:country|operator)|no\s+product|server\s+offline/i.test(text);
}
function assertMaxPriceCompatibleWithOperator(state = {}) {
const maxPrice = normalizeFiveSimMaxPrice(state.fiveSimMaxPrice);
const operator = normalizeFiveSimOperator(state.fiveSimOperator);
if (maxPrice && operator !== DEFAULT_OPERATOR) {
throw new Error('5sim maxPrice only works when operator is "any"; clear the price limit or switch operator to any before buying a number.');
}
}
async function buyActivationWithPrice(state = {}, countryConfig, maxPrice, deps = {}) {
const config = resolveConfig(state, deps);
const operator = normalizeFiveSimOperator(state.fiveSimOperator);
@@ -606,6 +614,8 @@
}
async function requestActivation(state = {}, options = {}, deps = {}) {
assertMaxPriceCompatibleWithOperator(state);
const allCountryCandidates = resolveCountryCandidates(state);
const blockedCountryIds = new Set(
(Array.isArray(options?.blockedCountryIds) ? options.blockedCountryIds : [])
+5
View File
@@ -8,6 +8,11 @@ test('background imports step registry and shared step definitions', () => {
assert.match(source, /data\/step-definitions\.js/);
assert.match(source, /MultiPageStepDefinitions\?\.getSteps/);
assert.match(source, /getStepRegistryForState\(state\)/);
assert.match(source, /PLUS_PAYPAL_STEP_DEFINITIONS/);
assert.match(source, /PLUS_GOPAY_STEP_DEFINITIONS/);
assert.match(source, /plusPayPalStepRegistry/);
assert.match(source, /plusGoPayStepRegistry/);
assert.match(source, /normalizePlusPaymentMethod\(state\?\.plusPaymentMethod\) === PLUS_PAYMENT_METHOD_GOPAY/);
assert.match(source, /activeStepRegistry\.executeStep\(step,\s*\{/);
assert.match(source, /background\/steps\/create-plus-checkout\.js/);
assert.match(source, /background\/steps\/fill-plus-checkout\.js/);
+24
View File
@@ -175,6 +175,30 @@ test('5sim provider prefers buy-compatible products price over operator detail p
);
});
test('5sim provider rejects maxPrice with custom operator before buying', async () => {
const requests = [];
const provider = api.createProvider({
fetchImpl: async (url) => {
requests.push(url);
throw new Error(`unexpected request ${url}`);
},
sleepWithStop: async () => {},
throwIfStopped: () => {},
});
await assert.rejects(
() => provider.requestActivation({
fiveSimApiKey: 'demo-key',
fiveSimCountryId: 'vietnam',
fiveSimCountryLabel: '瓒婂崡 (Vietnam)',
fiveSimMaxPrice: '12',
fiveSimOperator: 'virtual21',
}),
/maxPrice only works when operator is "any"/
);
assert.deepStrictEqual(requests, []);
});
test('5sim provider reports raw buy payload when HTTP 200 response has no activation', async () => {
const provider = api.createProvider({
fetchImpl: async (url) => {
+9
View File
@@ -69,6 +69,15 @@ test('GoPay approve handles final payment details iframe as an action frame', ()
assert.match(source, /最终 Bayar 确认/);
});
test('GoPay debugger click does not reuse iframe-relative rects as top-level coordinates', () => {
const body = extractFunction('clickGoPayTargetWithDebugger');
assert.match(body, /Number\.isInteger\(frameId\)/);
assert.match(body, /debugger_click_skipped_for_frame_target/);
assert.ok(
body.indexOf('debugger_click_skipped_for_frame_target') < body.indexOf('clickWithDebugger(tabId, rect)')
);
});
test('GoPay approve treats merchant validate-pin iframe as PIN entry frame', () => {
assert.match(source, /GOPAY_PIN_FRAME_URL_PATTERN/);
assert.match(source, /payment\\\/validate-pin/);
+37
View File
@@ -969,6 +969,43 @@ test('phone verification helper acquires a number from 5sim with fallback countr
assert.equal(requests[3].pathname, '/v1/user/buy/activation/england/any/openai');
});
test('phone verification helper rejects 5sim maxPrice with custom operator before buying', async () => {
const requests = [];
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
fetchImpl: async (url) => {
requests.push(url);
throw new Error(`Unexpected 5sim request: ${url}`);
},
getState: async () => ({
phoneSmsProvider: '5sim',
fiveSimApiKey: 'five-token',
fiveSimCountryOrder: ['vietnam'],
fiveSimOperator: 'virtual21',
fiveSimMaxPrice: '0.1',
heroSmsActivationRetryRounds: 1,
}),
sendToContentScriptResilient: async () => ({}),
setState: async () => {},
sleepWithStop: async () => {},
throwIfStopped: () => {},
});
await assert.rejects(
() => helpers.requestPhoneActivation({
phoneSmsProvider: '5sim',
fiveSimApiKey: 'five-token',
fiveSimCountryOrder: ['vietnam'],
fiveSimOperator: 'virtual21',
fiveSimMaxPrice: '0.1',
heroSmsActivationRetryRounds: 1,
}),
/maxPrice only works when operator is "any"/
);
assert.deepStrictEqual(requests, []);
});
test('phone verification helper honors price-priority ordering for 5sim countries', async () => {
const requests = [];
const helpers = api.createPhoneVerificationHelpers({