fix: reconcile GoPay steps and phone provider flows

- 吸收 PR #193/#194 后补齐 GoPay 订阅确认步骤注册,保持 GoPay Plus 为订阅确认后进入 OAuth 的 11 步流程。

- 修复手机号接码 provider 合并冲突,恢复 5sim/NexSMS/provider 顺序、复用池、价格阶梯和国家回退逻辑。

- 影响范围:共享步骤定义、background 步骤注册、手机号验证 Step 9、相关单元测试。
This commit is contained in:
QLHazyCoder
2026-05-04 04:27:49 +08:00
parent d4b170a5d7
commit 16beb69db8
5 changed files with 298 additions and 86 deletions
+1
View File
@@ -9906,6 +9906,7 @@ const stepExecutorsByKey = {
'clear-login-cookies': () => step6Executor.executeStep6(),
'plus-checkout-create': (state) => plusCheckoutCreateExecutor.executePlusCheckoutCreate(state),
'plus-checkout-billing': (state) => plusCheckoutBillingExecutor.executePlusCheckoutBilling(state),
'gopay-subscription-confirm': (state) => goPayManualConfirmExecutor.executeGoPayManualConfirm(state),
'paypal-approve': (state) => normalizePlusPaymentMethod(state?.plusPaymentMethod) === PLUS_PAYMENT_METHOD_GOPAY
? goPayApproveExecutor.executeGoPayApprove(state)
: payPalApproveExecutor.executePayPalApprove(state),
+285 -73
View File
@@ -71,8 +71,19 @@
const DEFAULT_PHONE_ACTIVATION_RETRY_DELAY_MS = 2000;
const HERO_SMS_ACQUIRE_PRIORITY_COUNTRY = 'country';
const HERO_SMS_ACQUIRE_PRIORITY_PRICE = 'price';
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
const PHONE_SMS_PROVIDER_FIVE_SIM = '5sim';
const HERO_SMS_ACQUIRE_PRIORITY_PRICE_HIGH = 'price_high';
const PHONE_SMS_PROVIDER_HERO = 'hero-sms';
const PHONE_SMS_PROVIDER_5SIM = '5sim';
const PHONE_SMS_PROVIDER_HERO_SMS = PHONE_SMS_PROVIDER_HERO;
const PHONE_SMS_PROVIDER_FIVE_SIM = PHONE_SMS_PROVIDER_5SIM;
const PHONE_SMS_PROVIDER_NEXSMS = 'nexsms';
const DEFAULT_PHONE_SMS_PROVIDER = PHONE_SMS_PROVIDER_HERO;
const DEFAULT_PHONE_SMS_PROVIDER_ORDER = Object.freeze([
PHONE_SMS_PROVIDER_HERO,
PHONE_SMS_PROVIDER_5SIM,
PHONE_SMS_PROVIDER_NEXSMS,
]);
const MAX_PHONE_REUSABLE_POOL = 12;
const PHONE_CODE_TIMEOUT_ERROR_PREFIX = 'PHONE_CODE_TIMEOUT::';
const PHONE_RESTART_STEP7_ERROR_PREFIX = 'PHONE_RESTART_STEP7::';
const PHONE_RESEND_THROTTLED_ERROR_PREFIX = 'PHONE_RESEND_THROTTLED::';
@@ -666,19 +677,19 @@
return normalizedMap;
}
function normalizePhoneSmsProvider(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized === PHONE_SMS_PROVIDER_FIVE_SIM
? PHONE_SMS_PROVIDER_FIVE_SIM
: PHONE_SMS_PROVIDER_HERO_SMS;
}
function getActivationProviderId(activation = {}, state = {}) {
return normalizePhoneSmsProvider(activation?.provider || state?.phoneSmsProvider);
}
function getPhoneSmsProviderLabel(providerId) {
return normalizePhoneSmsProvider(providerId) === PHONE_SMS_PROVIDER_FIVE_SIM ? '5sim' : 'HeroSMS';
const provider = normalizePhoneSmsProvider(providerId);
if (provider === PHONE_SMS_PROVIDER_FIVE_SIM) {
return '5sim';
}
if (provider === PHONE_SMS_PROVIDER_NEXSMS) {
return 'NexSMS';
}
return 'HeroSMS';
}
function formatStep9Reason(reason = '') {
@@ -717,7 +728,7 @@
const rootScope = typeof self !== 'undefined' ? self : globalThis;
const factory = createFiveSimProvider || rootScope.PhoneSmsFiveSimProvider?.createProvider;
if (typeof factory !== 'function') {
throw new Error('5sim 平台适配器未加载。');
return null;
}
return factory({
addLog,
@@ -895,14 +906,29 @@
const provider = normalizePhoneSmsProvider(rawProvider);
const rawCountryId = record.countryId ?? record.country;
const fallbackCountryId = provider === PHONE_SMS_PROVIDER_FIVE_SIM ? 'england' : HERO_SMS_COUNTRY_ID;
const expiresAt = normalizeTimestampMs(record.expiresAt);
const serviceCode = String(
record.serviceCode
|| (
provider === PHONE_SMS_PROVIDER_FIVE_SIM
? DEFAULT_FIVE_SIM_PRODUCT
: (provider === PHONE_SMS_PROVIDER_NEXSMS ? DEFAULT_NEX_SMS_SERVICE_CODE : HERO_SMS_SERVICE_CODE)
)
).trim();
const countryId = provider === PHONE_SMS_PROVIDER_FIVE_SIM
? normalizeFiveSimCountryId(record.countryCode ?? rawCountryId, fallbackCountryId)
: (
provider === PHONE_SMS_PROVIDER_NEXSMS
? normalizeNexSmsCountryId(rawCountryId, 0)
: normalizeCountryId(rawCountryId, fallbackCountryId)
);
return {
activationId,
phoneNumber,
provider,
serviceCode: String(record.serviceCode || HERO_SMS_SERVICE_CODE).trim() || HERO_SMS_SERVICE_CODE,
countryId: provider === PHONE_SMS_PROVIDER_FIVE_SIM
? normalizeFiveSimCountryId(rawCountryId, fallbackCountryId)
: normalizeCountryId(rawCountryId, fallbackCountryId),
serviceCode,
countryId,
...(provider === PHONE_SMS_PROVIDER_FIVE_SIM ? { countryCode: countryId } : {}),
...(countryLabel ? { countryLabel } : {}),
successfulUses: normalizeUseCount(record.successfulUses),
maxUses: Math.max(1, Math.floor(Number(record.maxUses) || DEFAULT_PHONE_NUMBER_MAX_USES)),
@@ -2788,7 +2814,9 @@
async function requestPhoneActivation(state = {}, options = {}) {
if (normalizePhoneSmsProvider(state?.phoneSmsProvider) === PHONE_SMS_PROVIDER_FIVE_SIM) {
const provider = getFiveSimProviderForState(state);
return provider.requestActivation(state, options);
if (provider) {
return provider.requestActivation(state, options);
}
}
const config = resolvePhoneConfig(state);
if (config.provider === PHONE_SMS_PROVIDER_5SIM) {
@@ -3100,7 +3128,9 @@
}
if (getActivationProviderId(normalizedActivation, state) === PHONE_SMS_PROVIDER_FIVE_SIM) {
const provider = getFiveSimProviderForState(state);
return provider.reuseActivation(state, normalizedActivation);
if (provider) {
return provider.reuseActivation(state, normalizedActivation);
}
}
const config = resolvePhoneConfig(state);
@@ -3184,8 +3214,10 @@
async function completePhoneActivation(state = {}, activation) {
if (getActivationProviderId(activation, state) === PHONE_SMS_PROVIDER_FIVE_SIM) {
const provider = getFiveSimProviderForState(state);
await provider.finishActivation(state, activation);
return;
if (provider) {
await provider.finishActivation(state, activation);
return;
}
}
await setPhoneActivationStatus(state, activation, 6, 'HeroSMS setStatus(6)');
}
@@ -3194,8 +3226,10 @@
try {
if (getActivationProviderId(activation, state) === PHONE_SMS_PROVIDER_FIVE_SIM) {
const provider = getFiveSimProviderForState(state);
await provider.cancelActivation(state, activation);
return;
if (provider) {
await provider.cancelActivation(state, activation);
return;
}
}
await setPhoneActivationStatus(state, activation, 8, 'HeroSMS setStatus(8)');
} catch (_) {
@@ -3207,8 +3241,10 @@
try {
if (getActivationProviderId(activation, state) === PHONE_SMS_PROVIDER_FIVE_SIM) {
const provider = getFiveSimProviderForState(state);
await provider.banActivation(state, activation);
return;
if (provider) {
await provider.banActivation(state, activation);
return;
}
}
await setPhoneActivationStatus(state, activation, 8, 'HeroSMS setStatus(8)');
} catch (_) {
@@ -3239,7 +3275,9 @@
}
if (getActivationProviderId(normalizedActivation, state) === PHONE_SMS_PROVIDER_FIVE_SIM) {
const provider = getFiveSimProviderForState(state);
return provider.pollActivationCode(state, normalizedActivation, options);
if (provider) {
return provider.pollActivationCode(state, normalizedActivation, options);
}
}
const statusAction = resolveActivationStatusAction(normalizedActivation);
@@ -3460,7 +3498,14 @@
function resolveCountryCandidatesForProvider(state = {}, providerId = normalizePhoneSmsProvider(state?.phoneSmsProvider)) {
if (normalizePhoneSmsProvider(providerId) === PHONE_SMS_PROVIDER_FIVE_SIM) {
return getFiveSimProviderForState(state).resolveCountryCandidates(state);
const provider = getFiveSimProviderForState(state);
if (provider) {
return provider.resolveCountryCandidates(state);
}
return resolveFiveSimCountryCandidates(state);
}
if (normalizePhoneSmsProvider(providerId) === PHONE_SMS_PROVIDER_NEXSMS) {
return resolveNexSmsCountryCandidates(state);
}
return resolveCountryCandidates(state);
}
@@ -3541,6 +3586,12 @@
});
if (result?.error) {
if (isPhoneNumberUsedError(result.error)) {
return {
invalidCode: true,
errorText: String(result.error || ''),
};
}
throw new Error(result.error);
}
return result || {};
@@ -3677,59 +3728,212 @@
}
async function acquirePhoneActivation(state = {}, options = {}) {
const activeProviderId = normalizePhoneSmsProvider(state?.phoneSmsProvider);
const countryCandidates = resolveCountryCandidatesForProvider(state, activeProviderId);
const normalizeBlockedCountryId = (value) => (
activeProviderId === PHONE_SMS_PROVIDER_FIVE_SIM
? normalizeFiveSimCountryId(value, '')
: normalizeCountryId(value, 0)
const provider = normalizePhoneSmsProvider(state?.phoneSmsProvider || DEFAULT_PHONE_SMS_PROVIDER);
const providerOrder = resolvePhoneProviderOrder(state, provider);
const countryCandidates = resolveCountryCandidatesForProvider(state, provider);
if (
(provider === PHONE_SMS_PROVIDER_5SIM || provider === PHONE_SMS_PROVIDER_NEXSMS)
&& !countryCandidates.length
) {
throw new Error(`Step 9: ${provider === PHONE_SMS_PROVIDER_5SIM ? '5sim' : 'NexSMS'} countries are empty. Please select at least one country in 接码设置。`);
}
const normalizeCountryKey = (value) => (
provider === PHONE_SMS_PROVIDER_5SIM
? normalizeFiveSimCountryCode(value, '')
: (
provider === PHONE_SMS_PROVIDER_NEXSMS
? String(normalizeNexSmsCountryId(value, -1))
: String(normalizeCountryId(value, 0))
)
);
const blockedCountryIds = new Set(
(Array.isArray(options?.blockedCountryIds) ? options.blockedCountryIds : [])
.map(normalizeBlockedCountryId)
.filter(Boolean)
.map((value) => normalizeCountryKey(value))
.filter((id) => (
provider === PHONE_SMS_PROVIDER_NEXSMS
? (id !== '' && id !== null && id !== undefined)
: Boolean(id && id !== '0')
))
);
const allowedCountryIds = new Set(
countryCandidates
.map((entry) => normalizeBlockedCountryId(entry.id))
.filter((id) => id && !blockedCountryIds.has(id))
.map((entry) => normalizeCountryKey(entry.id || entry.code))
.filter((id) => (
provider === PHONE_SMS_PROVIDER_NEXSMS
? (id !== '' && id !== null && id !== undefined && !blockedCountryIds.has(id))
: Boolean(id && id !== '0' && !blockedCountryIds.has(id))
))
);
const preferredCountryLabel = countryCandidates[0]?.label || (activeProviderId === PHONE_SMS_PROVIDER_FIVE_SIM ? 'England' : HERO_SMS_COUNTRY_LABEL);
const resolveCountryLabelById = (countryId) => (
countryCandidates.find((entry) => normalizeBlockedCountryId(entry.id) === normalizeBlockedCountryId(countryId))?.label
|| preferredCountryLabel
const preferredCountryLabel = countryCandidates[0]?.label || (
provider === PHONE_SMS_PROVIDER_5SIM
? ''
: (
provider === PHONE_SMS_PROVIDER_NEXSMS
? ''
: HERO_SMS_COUNTRY_LABEL
)
);
const reuseEnabled = isPhoneSmsReuseEnabled(state);
const reusableActivation = normalizeActivation(state[REUSABLE_PHONE_ACTIVATION_STATE_KEY]);
if (
reuseEnabled
&&
reusableActivation
&& reusableActivation.provider === activeProviderId
&& !blockedCountryIds.has(normalizeBlockedCountryId(reusableActivation.countryId))
&& allowedCountryIds.has(normalizeBlockedCountryId(reusableActivation.countryId))
&& reusableActivation.successfulUses < reusableActivation.maxUses
) {
const resolveCountryLabelById = (countryId) => {
const normalizedCountryKey = normalizeCountryKey(countryId);
return countryCandidates.find((entry) => normalizeCountryKey(entry.id || entry.code) === normalizedCountryKey)?.label
|| preferredCountryLabel;
};
const scopedStateForProvider = (providerName) => ({
...state,
phoneSmsProvider: normalizePhoneSmsProvider(providerName),
});
const preferredActivation = normalizeActivation(state[PREFERRED_PHONE_ACTIVATION_STATE_KEY]);
let failedPreferredActivation = null;
const canTryPreferredActivation = (
!Boolean(options?.skipPreferredActivation)
&& preferredActivation
&& (provider === PHONE_SMS_PROVIDER_HERO || provider === PHONE_SMS_PROVIDER_5SIM)
&& preferredActivation.provider === provider
&& !blockedCountryIds.has(normalizeCountryKey(preferredActivation.countryId))
&& allowedCountryIds.has(normalizeCountryKey(preferredActivation.countryId))
&& preferredActivation.successfulUses < preferredActivation.maxUses
);
if (canTryPreferredActivation) {
try {
const reactivated = await reactivatePhoneActivation(state, preferredActivation);
await addLog(
`步骤 9复用 ${resolveCountryLabelById(reactivated.countryId)} 号码 ${reactivated.phoneNumber}(第 ${reactivated.successfulUses + 1}/${reactivated.maxUses} 次)`,
`步骤 9优先复用手动选择号码 ${reactivated.phoneNumber}${reactivated.countryId ? `${resolveCountryLabelById(reactivated.countryId)}` : ''}`,
'info'
);
await resetPhoneNoSupplyFailureStreak(state);
return reactivated;
} catch (error) {
await addLog(`步骤 9:复用号码 ${reusableActivation.phoneNumber} 失败,将改为获取新号码。${error.message}`, 'warn');
await clearReusableActivation();
failedPreferredActivation = preferredActivation;
await removeReusableActivationFromPool(preferredActivation, { state }).catch(() => {});
await addLog(
`步骤 9:手动选择号码 ${preferredActivation.phoneNumber} 不可用,将改为获取新号码。${error.message}`,
'warn'
);
}
}
const reuseEnabled = isPhoneSmsReuseEnabled(state);
const reusableActivation = normalizeActivation(state[REUSABLE_PHONE_ACTIVATION_STATE_KEY]);
const reusableActivationPool = readReusableActivationPoolFromState(state);
const reusableCandidates = [];
const seenReusableKeys = new Set();
const pushReusableCandidate = (candidate) => {
const normalizedCandidate = normalizeActivation(candidate);
if (!normalizedCandidate) {
return;
}
const candidateKey = buildActivationIdentityKey(normalizedCandidate);
if (!candidateKey || seenReusableKeys.has(candidateKey)) {
return;
}
seenReusableKeys.add(candidateKey);
reusableCandidates.push(normalizedCandidate);
};
pushReusableCandidate(reusableActivation);
reusableActivationPool.forEach((candidate) => pushReusableCandidate(candidate));
if (reuseEnabled && (provider === PHONE_SMS_PROVIDER_HERO || provider === PHONE_SMS_PROVIDER_5SIM)) {
for (const candidateActivation of reusableCandidates) {
if (candidateActivation.provider !== provider) {
continue;
}
if (isSameActivation(candidateActivation, failedPreferredActivation)) {
continue;
}
if (candidateActivation.successfulUses >= candidateActivation.maxUses) {
continue;
}
if (blockedCountryIds.has(normalizeCountryKey(candidateActivation.countryId))) {
continue;
}
if (!allowedCountryIds.has(normalizeCountryKey(candidateActivation.countryId))) {
continue;
}
try {
const reactivated = await reactivatePhoneActivation(state, candidateActivation);
await addLog(
`步骤 9:复用 ${resolveCountryLabelById(reactivated.countryId)} 号码 ${reactivated.phoneNumber}(第 ${reactivated.successfulUses + 1}/${reactivated.maxUses} 次)。`,
'info'
);
await resetPhoneNoSupplyFailureStreak(state);
return reactivated;
} catch (error) {
await addLog(`步骤 9:复用号码 ${candidateActivation.phoneNumber} 失败,将改为获取新号码。${error.message}`, 'warn');
await removeReusableActivationFromPool(candidateActivation, { state }).catch(() => {});
if (isSameActivation(reusableActivation, candidateActivation)) {
await clearReusableActivation();
}
}
}
}
const activation = await requestPhoneActivation(state, { blockedCountryIds: Array.from(blockedCountryIds) });
await addLog(
`步骤 9:已从 ${getPhoneSmsProviderLabel(activation.provider)} 获取 ${resolveCountryLabelById(activation.countryId)} 号码 ${activation.phoneNumber}`,
'info'
);
return activation;
let lastProviderError = null;
const providerErrors = [];
const skippedFallbackProviders = [];
for (const providerCandidate of providerOrder) {
const useBlockedCountryIds = providerCandidate === provider
? Array.from(blockedCountryIds)
: [];
const useCountryPriceFloorByCountryId = (
providerCandidate === provider
&& options?.countryPriceFloorByCountryId
&& typeof options.countryPriceFloorByCountryId === 'object'
)
? options.countryPriceFloorByCountryId
: {};
try {
const activation = await requestPhoneActivation(
scopedStateForProvider(providerCandidate),
{
blockedCountryIds: useBlockedCountryIds,
countryPriceFloorByCountryId: useCountryPriceFloorByCountryId,
}
);
const providerLabel = getPhoneSmsProviderLabel(providerCandidate);
const providerCountryLabel = providerCandidate === provider
? resolveCountryLabelById(activation.countryId)
: String(activation?.countryLabel || activation?.countryId || '').trim();
if (providerCandidate !== provider) {
await addLog(
`步骤 9:主接码平台 ${getPhoneSmsProviderLabel(provider)} 暂无可用号码,已回退到 ${providerLabel}${providerCountryLabel ? ` / ${providerCountryLabel}` : ''}`,
'warn'
);
}
await addLog(
`步骤 9:已从 ${providerLabel}${providerCountryLabel ? ` / ${providerCountryLabel}` : ''} 获取号码 ${activation.phoneNumber}`,
'info'
);
await resetPhoneNoSupplyFailureStreak(state);
return activation;
} catch (error) {
if (isStopRequestedError(error)) {
throw error;
}
const providerErrorMessage = String(error?.message || error || 'unknown error');
const providerLabel = getPhoneSmsProviderLabel(providerCandidate);
if (
providerCandidate !== provider
&& /step\s*9:\s*(?:5sim|nexsms)\s+countries\s+are\s+empty/i.test(providerErrorMessage)
) {
skippedFallbackProviders.push(`${providerLabel}: countries are empty`);
await addLog(
`步骤 9:跳过回退接码平台 ${providerLabel},因为接码设置中未选择国家。`,
'warn'
);
continue;
}
lastProviderError = error;
providerErrors.push(`${providerCandidate}: ${providerErrorMessage}`);
}
}
if (providerErrors.length) {
await logNoSupplyDiagnostics(state, providerOrder, providerErrors);
const skippedSuffix = skippedFallbackProviders.length
? ` | skipped fallback providers: ${skippedFallbackProviders.join('; ')}`
: '';
throw new Error(`Step 9: all provider candidates failed to acquire number. ${providerErrors.join(' | ')}${skippedSuffix}`);
}
throw lastProviderError || new Error('Step 9: failed to acquire phone activation.');
}
async function markActivationReusableAfterSuccess(state, activation) {
@@ -3742,13 +3946,18 @@
await clearReusableActivation();
return;
}
const reusableProvider = normalizedActivation.provider;
const canPersistReusableActivation = reusableProvider === PHONE_SMS_PROVIDER_HERO
|| reusableProvider === PHONE_SMS_PROVIDER_5SIM;
if (!canPersistReusableActivation) {
await clearReusableActivation();
return;
}
const successfulUses = normalizedActivation.successfulUses + 1;
const nextReusableActivation = {
...normalizedActivation,
successfulUses: normalizedActivation.provider === PHONE_SMS_PROVIDER_5SIM
? 1
: successfulUses,
successfulUses,
};
await upsertReusableActivationPool(nextReusableActivation, { state });
if (!normalizeHeroSmsReuseEnabled(state?.heroSmsReuseEnabled)) {
@@ -4015,24 +4224,23 @@
);
const getCountryFailureCount = (countryId, providerId = normalizePhoneSmsProvider(state?.phoneSmsProvider)) => {
const normalizedCountryId = getCountryFailureKey(countryId, providerId);
if (!normalizedCountryId) {
const countryKey = normalizeCountryFailureKey(countryId, providerId);
if (!countryKey) {
return 0;
}
return Math.max(0, Math.floor(Number(countrySmsFailureCounts.get(countryKey)) || 0));
};
const markCountrySmsFailure = async (countryId, reason = 'sms_timeout', providerId = normalizePhoneSmsProvider(state?.phoneSmsProvider)) => {
const normalizedCountryId = getCountryFailureKey(countryId, providerId);
if (!normalizedCountryId) {
const countryKey = normalizeCountryFailureKey(countryId, providerId);
if (!countryKey) {
return;
}
const nextCount = getCountryFailureCount(countryKey) + 1;
const parsed = splitCountryFailureKey(countryKey, providerId);
const nextCount = getCountryFailureCount(parsed.countryKey, parsed.provider) + 1;
countrySmsFailureCounts.set(countryKey, nextCount);
if (nextCount >= PHONE_SMS_FAILURE_SKIP_THRESHOLD) {
const matched = resolveCountryCandidatesForProvider(state, providerId)
.find((entry) => getCountryFailureKey(entry.id, providerId) === normalizedCountryId);
const countryLabel = matched?.label || (providerId === PHONE_SMS_PROVIDER_FIVE_SIM ? normalizedCountryId : `Country #${normalizedCountryId}`);
const countryLabel = resolveCountryLabelByFailureKey(countryKey, providerId);
await addLog(
`步骤 9${countryLabel} 已累计 ${nextCount} 次短信失败(${formatStep9Reason(reason)});下次获取号码会优先尝试其它已选国家。`,
'warn'
@@ -4041,8 +4249,8 @@
};
const clearCountrySmsFailure = (countryId, providerId = normalizePhoneSmsProvider(state?.phoneSmsProvider)) => {
const normalizedCountryId = getCountryFailureKey(countryId, providerId);
if (!normalizedCountryId) {
const countryKey = normalizeCountryFailureKey(countryId, providerId);
if (!countryKey) {
return;
}
countrySmsFailureCounts.delete(countryKey);
@@ -4054,7 +4262,10 @@
state?.phoneSmsProvider || activation?.provider || DEFAULT_PHONE_SMS_PROVIDER
);
return Array.from(countrySmsFailureCounts.entries())
.filter(([, count]) => Number(count) >= PHONE_SMS_FAILURE_SKIP_THRESHOLD)
.filter(([countryKey, count]) => (
Number(count) >= PHONE_SMS_FAILURE_SKIP_THRESHOLD
|| !countryPriceFloorByKey.has(countryKey)
))
.map(([countryKey]) => splitCountryFailureKey(countryKey, activeProvider))
.filter((entry) => entry.provider === activeProvider)
.map((entry) => String(entry.countryKey || '').trim())
@@ -4462,6 +4673,7 @@
|| /^sms_timeout_after_/i.test(String(replaceReason || ''))
)
) {
await setCountryPriceFloorFromActivation(activation, replaceReason || 'sms_timeout');
await markCountrySmsFailure(activation.countryId, replaceReason || 'sms_timeout', activation.provider);
}
await markPreferredActivationExhausted(replaceReason || 'replace_number');
+7 -11
View File
@@ -1,6 +1,10 @@
(function attachStepDefinitions(root, factory) {
root.MultiPageStepDefinitions = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createStepDefinitionsModule() {
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
const PLUS_PAYMENT_STEP_KEY = 'paypal-approve';
const NORMAL_STEP_DEFINITIONS = [
{ id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网' },
{ id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱' },
@@ -37,9 +41,7 @@
{ id: 4, order: 40, key: 'fetch-signup-code', title: '获取注册验证码' },
{ id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日' },
{ id: 6, order: 60, key: 'plus-checkout-create', title: '打开 GoPay 订阅页' },
{ id: 7, order: 70, key: 'plus-checkout-billing', title: '填写 GoPay 账单并提交订阅' },
{ id: 8, order: 80, key: 'paypal-approve', title: 'GoPay 手机验证与授权' },
{ id: 9, order: 90, key: 'plus-checkout-return', title: '订阅回跳确认' },
{ id: 7, order: 70, key: 'gopay-subscription-confirm', title: '等待 GoPay 订阅确认' },
{ id: 10, order: 100, key: 'oauth-login', title: '刷新 OAuth 并登录' },
{ id: 11, order: 110, key: 'fetch-login-code', title: '获取登录验证码' },
{ id: 12, order: 120, key: 'confirm-oauth', title: '自动确认 OAuth' },
@@ -53,20 +55,14 @@
function normalizePlusPaymentMethod(value = '') {
return String(value || '').trim().toLowerCase() === PLUS_PAYMENT_METHOD_GOPAY
? PLUS_PAYMENT_METHOD_GOPAY
: 'paypal';
}
function getPlusPaymentStepTitle(options = {}) {
return normalizePlusPaymentMethod(options?.plusPaymentMethod) === PLUS_PAYMENT_METHOD_GOPAY
? 'GoPay 手机验证与授权'
: 'PayPal 登录与授权';
: PLUS_PAYMENT_METHOD_PAYPAL;
}
function getModeStepDefinitions(options = {}) {
if (!isPlusModeEnabled(options)) {
return NORMAL_STEP_DEFINITIONS;
}
return normalizePlusPaymentMethod(options?.plusPaymentMethod || options?.paymentMethod) === 'gopay'
return normalizePlusPaymentMethod(options?.plusPaymentMethod || options?.paymentMethod) === PLUS_PAYMENT_METHOD_GOPAY
? PLUS_GOPAY_STEP_DEFINITIONS
: PLUS_PAYPAL_STEP_DEFINITIONS;
}
+2
View File
@@ -16,6 +16,8 @@ test('background imports step registry and shared step definitions', () => {
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/);
assert.match(source, /background\/steps\/gopay-manual-confirm\.js/);
assert.match(source, /'gopay-subscription-confirm': \(state\) => goPayManualConfirmExecutor\.executeGoPayManualConfirm\(state\)/);
assert.match(source, /background\/steps\/paypal-approve\.js/);
assert.match(source, /background\/steps\/gopay-approve\.js/);
assert.match(source, /background\/steps\/plus-return-confirm\.js/);
+3 -2
View File
@@ -56,8 +56,9 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
assert.equal(plusSteps.some((step) => step.key === 'clear-login-cookies'), false);
assert.equal(plusSteps.some((step) => step.key === 'fetch-login-code'), true);
assert.equal(plusSteps.find((step) => step.key === 'paypal-approve')?.title, 'PayPal 登录与授权');
assert.equal(goPaySteps.find((step) => step.key === 'paypal-approve')?.title, 'GoPay 手机验证与授权');
assert.equal(api.getStepById(8, { plusModeEnabled: true, plusPaymentMethod: 'gopay' })?.title, 'GoPay 手机验证与授权');
assert.equal(goPaySteps.some((step) => step.key === 'paypal-approve'), false);
assert.equal(api.getStepById(8, { plusModeEnabled: true, plusPaymentMethod: 'gopay' }), null);
assert.equal(api.getPlusPaymentStepTitle({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), '');
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]);
assert.equal(api.getLastStepId({ plusModeEnabled: true }), 13);
assert.equal(plusSteps[5].title, '创建 Plus Checkout');