diff --git a/background.js b/background.js index 05e6e94..3b301de 100644 --- a/background.js +++ b/background.js @@ -456,6 +456,11 @@ function normalizePlusPaymentMethod(value = '') { return normalized === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_PAYMENT_METHOD_GOPAY : PLUS_PAYMENT_METHOD_PAYPAL; } +function normalizeGpcHelperPhoneMode(value = '') { + const normalized = String(value || '').trim().toLowerCase(); + return normalized === 'auto' || normalized === 'builtin' ? 'auto' : 'manual'; +} + function normalizeContributionModeSource(value = '') { const normalized = String(value || '').trim().toLowerCase(); return normalized === CONTRIBUTION_SOURCE_SUB2API @@ -634,6 +639,7 @@ const PERSISTED_SETTING_DEFAULTS = { gopayHelperApiUrl: DEFAULT_GPC_HELPER_API_URL, gopayHelperApiKey: '', gopayHelperCardKey: '', + gopayHelperPhoneMode: 'manual', gopayHelperPhoneNumber: '', gopayHelperCountryCode: '+86', gopayHelperPin: '', @@ -665,6 +671,9 @@ const PERSISTED_SETTING_DEFAULTS = { gopayHelperBalancePayload: null, gopayHelperBalanceUpdatedAt: 0, gopayHelperBalanceError: '', + gopayHelperRemainingUses: 0, + gopayHelperAutoModeEnabled: false, + gopayHelperApiKeyStatus: '', autoRunSkipFailures: false, autoRunFallbackThreadIntervalMinutes: 0, oauthFlowTimeoutEnabled: true, @@ -2330,6 +2339,10 @@ function normalizePersistentSettingValue(key, value) { return self.GoPayUtils?.normalizeGoPayPin ? self.GoPayUtils.normalizeGoPayPin(value) : String(value || ''); + case 'gopayHelperPhoneMode': + return self.GoPayUtils?.normalizeGpcHelperPhoneMode + ? self.GoPayUtils.normalizeGpcHelperPhoneMode(value) + : (String(value || '').trim().toLowerCase() === 'auto' || String(value || '').trim().toLowerCase() === 'builtin' ? 'auto' : 'manual'); case 'gopayHelperPhoneNumber': return self.GoPayUtils?.normalizeGoPayPhone ? self.GoPayUtils.normalizeGoPayPhone(value) @@ -2404,6 +2417,7 @@ function normalizePersistentSettingValue(key, value) { case 'gopayHelperFailureDetail': case 'gopayHelperBalance': case 'gopayHelperBalanceError': + case 'gopayHelperApiKeyStatus': return String(value || '').trim(); case 'gopayHelperBalancePayload': case 'gopayHelperStartPayload': @@ -2412,10 +2426,12 @@ function normalizePersistentSettingValue(key, value) { case 'gopayHelperBalanceUpdatedAt': case 'gopayHelperApiInputWaitSeconds': case 'gopayHelperOtpInvalidCount': + case 'gopayHelperRemainingUses': return Math.max(0, Number(value) || 0); case 'autoRunSkipFailures': case 'oauthFlowTimeoutEnabled': case 'gopayHelperLocalSmsHelperEnabled': + case 'gopayHelperAutoModeEnabled': case 'autoRunDelayEnabled': case 'step6CookieCleanupEnabled': case 'phoneVerificationEnabled': @@ -9953,12 +9969,27 @@ async function refreshGpcApiKeyBalance(state = {}, options = {}) { const balancePayload = self.GoPayUtils?.unwrapGpcResponse ? self.GoPayUtils.unwrapGpcResponse(payload) : (payload?.data && typeof payload === 'object' ? payload.data : payload); + const balanceData = balancePayload && typeof balancePayload === 'object' && !Array.isArray(balancePayload) + ? balancePayload + : {}; + const remainingUses = self.GoPayUtils?.getGpcBalanceRemainingUses + ? self.GoPayUtils.getGpcBalanceRemainingUses(balanceData) + : Math.max(0, Number(balanceData.remaining_uses ?? balanceData.remainingUses ?? balanceData.balance ?? balanceData.remaining) || 0); + const autoModeEnabled = self.GoPayUtils?.isGpcAutoModeEnabled + ? self.GoPayUtils.isGpcAutoModeEnabled(balanceData) + : Boolean(balanceData.auto_mode_enabled ?? balanceData.autoModeEnabled); + const apiKeyStatus = self.GoPayUtils?.getGpcApiKeyStatus + ? self.GoPayUtils.getGpcApiKeyStatus(balanceData) + : String(balanceData.status || balanceData.card_status || balanceData.cardStatus || '').trim(); const balanceText = formatGpcApiKeyBalancePayload(payload) || rawText || '未知'; const updates = { gopayHelperBalance: balanceText, - gopayHelperBalancePayload: balancePayload && typeof balancePayload === 'object' && !Array.isArray(balancePayload) ? balancePayload : { raw: String(balancePayload || '') }, + gopayHelperBalancePayload: Object.keys(balanceData).length > 0 ? balanceData : { raw: String(balancePayload || '') }, gopayHelperBalanceUpdatedAt: Date.now(), gopayHelperBalanceError: '', + gopayHelperRemainingUses: Math.max(0, Number(remainingUses) || 0), + gopayHelperAutoModeEnabled: Boolean(autoModeEnabled), + gopayHelperApiKeyStatus: apiKeyStatus, }; const flowId = String(balancePayload?.flow_id || balancePayload?.flowId || '').trim(); if (flowId) { @@ -9987,7 +10018,15 @@ async function refreshGpcApiKeyBalance(state = {}, options = {}) { : `GPC 余额查询成功:${balanceText}`, 'info' ); - return { balance: balanceText, payload, updatedAt: updates.gopayHelperBalanceUpdatedAt }; + return { + balance: balanceText, + payload, + data: updates.gopayHelperBalancePayload, + remainingUses: updates.gopayHelperRemainingUses, + autoModeEnabled: updates.gopayHelperAutoModeEnabled, + apiKeyStatus: updates.gopayHelperApiKeyStatus, + updatedAt: updates.gopayHelperBalanceUpdatedAt, + }; } const refreshGpcCardBalance = refreshGpcApiKeyBalance; diff --git a/background/steps/create-plus-checkout.js b/background/steps/create-plus-checkout.js index c22cb30..0651049 100644 --- a/background/steps/create-plus-checkout.js +++ b/background/steps/create-plus-checkout.js @@ -8,6 +8,8 @@ const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper'; const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top'; + const GPC_HELPER_PHONE_MODE_AUTO = 'auto'; + const GPC_HELPER_PHONE_MODE_MANUAL = 'manual'; function createPlusCheckoutCreateExecutor(deps = {}) { const { @@ -86,6 +88,17 @@ return cleaned; } + function normalizeGpcHelperPhoneMode(value = '') { + const rootScope = typeof self !== 'undefined' ? self : globalThis; + if (rootScope.GoPayUtils?.normalizeGpcHelperPhoneMode) { + return rootScope.GoPayUtils.normalizeGpcHelperPhoneMode(value); + } + const normalized = String(value || '').trim().toLowerCase(); + return normalized === GPC_HELPER_PHONE_MODE_AUTO || normalized === 'builtin' + ? GPC_HELPER_PHONE_MODE_AUTO + : GPC_HELPER_PHONE_MODE_MANUAL; + } + function normalizeGpcOtpChannel(value = '') { const rootScope = typeof self !== 'undefined' ? self : globalThis; if (rootScope.GoPayUtils?.normalizeGpcOtpChannel) { @@ -143,6 +156,17 @@ return buildGpcHelperApiUrl(apiUrl, '/api/gp/tasks'); } + function buildGpcBalanceUrl(apiUrl = '') { + const rootScope = typeof self !== 'undefined' ? self : globalThis; + if (rootScope.GoPayUtils?.buildGpcApiKeyBalanceUrl) { + return rootScope.GoPayUtils.buildGpcApiKeyBalanceUrl(apiUrl); + } + if (rootScope.GoPayUtils?.buildGpcCardBalanceUrl) { + return rootScope.GoPayUtils.buildGpcCardBalanceUrl(apiUrl); + } + return buildGpcHelperApiUrl(apiUrl, '/api/gp/balance'); + } + function unwrapGpcResponse(payload = {}) { const rootScope = typeof self !== 'undefined' ? self : globalThis; if (rootScope.GoPayUtils?.unwrapGpcResponse) { @@ -176,6 +200,55 @@ return payload?.data?.detail || payload?.detail || payload?.message || payload?.error || `HTTP ${status || 0}`; } + function getGpcRemainingUses(payload = {}) { + const rootScope = typeof self !== 'undefined' ? self : globalThis; + if (rootScope.GoPayUtils?.getGpcBalanceRemainingUses) { + return rootScope.GoPayUtils.getGpcBalanceRemainingUses(payload); + } + const data = unwrapGpcResponse(payload); + const numeric = Number(data?.remaining_uses ?? data?.remainingUses ?? data?.balance ?? data?.remaining); + return Number.isFinite(numeric) ? Math.max(0, Math.floor(numeric)) : null; + } + + function isGpcAutoModeEnabled(payload = {}) { + const rootScope = typeof self !== 'undefined' ? self : globalThis; + if (rootScope.GoPayUtils?.isGpcAutoModeEnabled) { + return rootScope.GoPayUtils.isGpcAutoModeEnabled(payload); + } + const data = unwrapGpcResponse(payload); + return data?.auto_mode_enabled === true || data?.autoModeEnabled === true; + } + + async function assertGpcApiKeyReadyForCreate(state = {}, phoneMode = GPC_HELPER_PHONE_MODE_MANUAL, apiKey = '') { + const apiUrl = buildGpcBalanceUrl(state?.gopayHelperApiUrl); + if (!apiUrl) { + throw new Error('创建 GPC 订单失败:缺少 API 地址。'); + } + const { response, data } = await fetchJsonWithTimeout(apiUrl, { + method: 'GET', + headers: { + Accept: 'application/json', + 'X-API-Key': apiKey, + }, + }, 30000); + if (!response?.ok || !isGpcUnifiedResponseOk(data)) { + const detail = getGpcResponseErrorDetail(data, response?.status || 0); + throw new Error(`创建 GPC 订单失败:API Key 校验失败:${detail}`); + } + const balanceData = unwrapGpcResponse(data); + const remainingUses = getGpcRemainingUses(balanceData); + const status = String(balanceData?.status || balanceData?.card_status || balanceData?.cardStatus || '').trim().toLowerCase(); + if (status && status !== 'active') { + throw new Error(`创建 GPC 订单失败:API Key 状态不可用(${status})。`); + } + if (remainingUses !== null && remainingUses <= 0) { + throw new Error('创建 GPC 订单失败:API Key 剩余次数不足。'); + } + if (phoneMode === GPC_HELPER_PHONE_MODE_AUTO && !isGpcAutoModeEnabled(balanceData)) { + throw new Error('创建 GPC 订单失败:当前 GPC API Key 未开通自动模式。'); + } + } + async function fetchJsonWithTimeout(url, options = {}, timeoutMs = 30000) { const fetcher = typeof fetchImpl === 'function' ? fetchImpl @@ -226,25 +299,31 @@ if (!apiUrl) { throw new Error('创建 GPC 订单失败:缺少 API 地址。'); } + const phoneMode = normalizeGpcHelperPhoneMode(state?.gopayHelperPhoneMode || state?.phoneMode); + const isAutoMode = phoneMode === GPC_HELPER_PHONE_MODE_AUTO; const phoneNumber = String(state?.gopayHelperPhoneNumber || '').trim(); const countryCode = normalizeHelperCountryCode(state?.gopayHelperCountryCode || '86'); const pin = String(state?.gopayHelperPin || '').trim(); const apiKey = resolveGpcHelperApiKey(state); - if (!phoneNumber) { - throw new Error('创建 GPC 订单失败:缺少手机号。'); + if (!isAutoMode && !phoneNumber) { + throw new Error('创建 GPC 订单失败:手动模式缺少手机号。'); } - if (!pin) { - throw new Error('创建 GPC 订单失败:缺少 PIN。'); + if (!isAutoMode && !pin) { + throw new Error('创建 GPC 订单失败:手动模式缺少 PIN。'); } + throwIfStopped(); + await assertGpcApiKeyReadyForCreate(state, phoneMode, apiKey); throwIfStopped(); const payload = { access_token: token, - phone_mode: 'manual', - country_code: countryCode, - phone_number: normalizeHelperPhoneNumber(phoneNumber, countryCode), - otp_channel: normalizeGpcOtpChannel(state?.gopayHelperOtpChannel), + phone_mode: phoneMode, }; + if (!isAutoMode) { + payload.country_code = countryCode; + payload.phone_number = normalizeHelperPhoneNumber(phoneNumber, countryCode); + payload.otp_channel = normalizeGpcOtpChannel(state?.gopayHelperOtpChannel); + } const orderCreatedAt = Date.now(); const { response, data } = await fetchJsonWithTimeout(apiUrl, { @@ -273,6 +352,7 @@ remoteStage: String(taskData?.remote_stage || taskData?.remoteStage || '').trim(), orderCreatedAt, responsePayload: taskData && typeof taskData === 'object' && !Array.isArray(taskData) ? taskData : null, + phoneMode: normalizeGpcHelperPhoneMode(taskData?.phone_mode || taskData?.phoneMode || phoneMode), country: 'ID', currency: 'IDR', checkoutSource: PLUS_PAYMENT_METHOD_GPC_HELPER, @@ -308,6 +388,7 @@ gopayHelperTaskStatus: result.taskStatus, gopayHelperStatusText: result.statusText, gopayHelperRemoteStage: result.remoteStage, + gopayHelperPhoneMode: result.phoneMode || normalizeGpcHelperPhoneMode(state?.gopayHelperPhoneMode || state?.phoneMode), gopayHelperTaskPayload: result.responsePayload, gopayHelperReferenceId: '', gopayHelperGoPayGuid: '', @@ -318,7 +399,7 @@ gopayHelperStartPayload: null, gopayHelperOrderCreatedAt: result.orderCreatedAt || Date.now(), }); - await addLog(`步骤 6:GPC 任务已创建(task_id: ${result.taskId}),准备继续下一步。`, 'info'); + await addLog(`步骤 6:GPC ${result.phoneMode === GPC_HELPER_PHONE_MODE_AUTO ? '自动' : '手动'}模式任务已创建(task_id: ${result.taskId}),准备继续下一步。`, 'info'); await completeStepFromBackground(6, { plusCheckoutCountry: result.country || 'ID', plusCheckoutCurrency: result.currency || 'IDR', diff --git a/background/steps/fill-plus-checkout.js b/background/steps/fill-plus-checkout.js index d294ca6..d14f137 100644 --- a/background/steps/fill-plus-checkout.js +++ b/background/steps/fill-plus-checkout.js @@ -11,7 +11,27 @@ const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper'; const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top'; + const GPC_HELPER_PHONE_MODE_AUTO = 'auto'; + const GPC_HELPER_PHONE_MODE_MANUAL = 'manual'; const GPC_TASK_POLL_INTERVAL_MS = 3000; + const GPC_REMOTE_STAGE_LABELS = { + auto_otp_wait: '等待自动 OTP', + checkout_order_start: '创建订单', + checkout_start: '创建订单', + completed: '充值完成', + gopay_validate_pin: '校验 PIN', + otp_ready: '等待 PIN', + otp_submitted_local: 'OTP 已提交', + payment_processing: '支付处理中', + pin_submitted_local: 'PIN 已提交', + sms_otp_wait: '等待短信 OTP', + whatsapp_otp_wait: '等待 WhatsApp OTP', + }; + const GPC_WAITING_FOR_LABELS = { + auto_otp: '自动 OTP', + otp: 'OTP', + pin: 'PIN', + }; const PAYMENT_METHOD_CONFIGS = { [PLUS_PAYMENT_METHOD_PAYPAL]: { id: PLUS_PAYMENT_METHOD_PAYPAL, @@ -112,6 +132,56 @@ return normalized === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_PAYMENT_METHOD_GOPAY : PLUS_PAYMENT_METHOD_PAYPAL; } + function normalizeGpcHelperPhoneMode(value = '') { + const rootScope = typeof self !== 'undefined' ? self : globalThis; + if (rootScope.GoPayUtils?.normalizeGpcHelperPhoneMode) { + return rootScope.GoPayUtils.normalizeGpcHelperPhoneMode(value); + } + const normalized = String(value || '').trim().toLowerCase(); + return normalized === GPC_HELPER_PHONE_MODE_AUTO || normalized === 'builtin' + ? GPC_HELPER_PHONE_MODE_AUTO + : GPC_HELPER_PHONE_MODE_MANUAL; + } + + function formatGpcRemoteStageLabel(stage = '') { + const normalized = String(stage || '').trim().toLowerCase(); + if (!normalized) { + return ''; + } + return GPC_REMOTE_STAGE_LABELS[normalized] || normalized; + } + + function formatGpcWaitingForLabel(waitingFor = '') { + const normalized = String(waitingFor || '').trim().toLowerCase(); + if (!normalized) { + return ''; + } + return GPC_WAITING_FOR_LABELS[normalized] || normalized.toUpperCase(); + } + + function formatGpcTaskStatusLog(task = {}) { + const statusText = String(task?.status_text || task?.statusText || '').trim(); + const status = String(task?.status || '').trim(); + const remoteStage = String(task?.remote_stage || task?.remoteStage || '').trim(); + const stageText = formatGpcRemoteStageLabel(remoteStage); + const waitingForText = formatGpcWaitingForLabel(task?.api_waiting_for || task?.apiWaitingFor || ''); + const mainText = stageText || statusText || status || '处理中'; + const parts = [`步骤 7:GPC 任务状态:${mainText}`]; + if (waitingForText && !mainText.includes(waitingForText)) { + parts.push(`,等待 ${waitingForText}`); + } + return parts.join(''); + } + + function getGpcHelperPhoneMode(state = {}, task = null) { + return normalizeGpcHelperPhoneMode( + task?.phone_mode + || task?.phoneMode + || state?.gopayHelperPhoneMode + || state?.phoneMode + ); + } + function getPaymentMethodConfig(method = PLUS_PAYMENT_METHOD_PAYPAL) { return PAYMENT_METHOD_CONFIGS[normalizePlusPaymentMethod(method)] || PAYMENT_METHOD_CONFIGS[PLUS_PAYMENT_METHOD_PAYPAL]; } @@ -299,7 +369,7 @@ task.task_id = String(task.task_id || task.taskId || '').trim(); task.status = String(task.status || '').trim().toLowerCase(); task.status_text = String(task.status_text || task.statusText || '').trim(); - task.phone_mode = String(task.phone_mode || task.phoneMode || '').trim().toLowerCase(); + task.phone_mode = normalizeGpcHelperPhoneMode(task.phone_mode || task.phoneMode || ''); task.remote_stage = String(task.remote_stage || task.remoteStage || '').trim().toLowerCase(); task.api_waiting_for = String(task.api_waiting_for || task.apiWaitingFor || '').trim().toLowerCase(); task.api_input_deadline_at = String(task.api_input_deadline_at || task.apiInputDeadlineAt || '').trim(); @@ -318,6 +388,7 @@ gopayHelperTaskId: task.task_id, gopayHelperTaskStatus: task.status, gopayHelperStatusText: task.status_text, + gopayHelperPhoneMode: task.phone_mode, gopayHelperRemoteStage: task.remote_stage, gopayHelperApiWaitingFor: task.api_waiting_for, gopayHelperApiInputDeadlineAt: task.api_input_deadline_at, @@ -675,12 +746,16 @@ }); } - function isGpcTaskOtpWait(task = {}) { - return task?.phone_mode === 'manual' && task?.api_waiting_for === 'otp'; + function isGpcTaskManualMode(task = {}, state = {}) { + return getGpcHelperPhoneMode(state, task) === GPC_HELPER_PHONE_MODE_MANUAL; } - function isGpcTaskPinWait(task = {}) { - return task?.phone_mode === 'manual' + function isGpcTaskOtpWait(task = {}, state = {}) { + return isGpcTaskManualMode(task, state) && task?.api_waiting_for === 'otp'; + } + + function isGpcTaskPinWait(task = {}, state = {}) { + return isGpcTaskManualMode(task, state) && (task?.api_waiting_for === 'pin' || task?.status === 'otp_ready'); } @@ -840,27 +915,25 @@ throw new Error('步骤 7:GPC 模式缺少 API Key。'); } + const configuredPhoneMode = normalizeGpcHelperPhoneMode(state?.gopayHelperPhoneMode || state?.phoneMode || GPC_HELPER_PHONE_MODE_MANUAL); const rawPin = String(state?.gopayHelperPin || '').trim(); const pinDigits = rawPin.replace(/[^\d]/g, ''); const pin = normalizeSixDigitPin(rawPin); - if (!pin) { + if (configuredPhoneMode === GPC_HELPER_PHONE_MODE_MANUAL && !pin) { if (taskId && apiUrl && apiKey) { await stopGpcTaskBestEffort(apiUrl, taskId, apiKey, 'PIN 配置错误'); } throw new Error(pinDigits ? '步骤 7:GPC PIN 必须是 6 位数字,请检查侧边栏配置。' - : '步骤 7:GPC 模式缺少 PIN 配置。'); + : '步骤 7:GPC 手动模式缺少 PIN 配置。'); } - await addLog(`步骤 7:GPC 模式开始轮询任务(task_id: ${taskId})...`, 'info'); + await addLog(`步骤 7:GPC ${configuredPhoneMode === GPC_HELPER_PHONE_MODE_AUTO ? '自动' : '手动'}模式开始轮询任务(task_id: ${taskId})...`, 'info'); try { while (Date.now() <= deadline) { throwIfStopped(); const task = await fetchGpcTaskStatus(apiUrl, taskId, apiKey); - const statusText = task?.status_text || task?.status || '处理中'; - const remoteStage = task?.remote_stage || ''; - const waitingFor = task?.api_waiting_for || ''; - await addLog(`步骤 7:GPC 任务状态:${statusText}${remoteStage ? `(${remoteStage})` : ''}${waitingFor ? `,等待 ${waitingFor.toUpperCase()}` : ''}`, 'info'); + await addLog(formatGpcTaskStatusLog(task), 'info'); if (task.status === 'completed') { terminalReached = true; @@ -879,7 +952,7 @@ throw buildGpcTaskEndedError(task, 'GPC 任务已结束,请重新创建任务。'); } - if (isGpcTaskOtpWait(task)) { + if (isGpcTaskOtpWait(task, state)) { if (isGpcTaskInputDeadlineExpired(task)) { throw buildGpcInputDeadlineError(task, 'OTP'); } @@ -940,7 +1013,7 @@ otpLastSubmittedAt = Date.now(); lastSubmittedOtp = normalizedOtp; await addLog('步骤 7:OTP 已提交,继续等待 GPC 任务状态更新。', 'ok'); - } else if (isGpcTaskPinWait(task) && !pinSubmitted) { + } else if (isGpcTaskPinWait(task, state) && !pinSubmitted) { if (isGpcTaskInputDeadlineExpired(task)) { throw buildGpcInputDeadlineError(task, 'PIN'); } diff --git a/data/step-definitions.js b/data/step-definitions.js index 6cc1cae..dd87867 100644 --- a/data/step-definitions.js +++ b/data/step-definitions.js @@ -58,7 +58,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: '创建 GPC 订单' }, - { id: 7, order: 70, key: 'plus-checkout-billing', title: 'GPC OTP/PIN 验证' }, + { id: 7, order: 70, key: 'plus-checkout-billing', title: '等待 GPC 任务完成' }, { 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' }, diff --git a/gopay-utils.js b/gopay-utils.js index b2c2eee..16d081f 100644 --- a/gopay-utils.js +++ b/gopay-utils.js @@ -5,6 +5,8 @@ const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper'; const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top'; + const GPC_HELPER_PHONE_MODE_AUTO = 'auto'; + const GPC_HELPER_PHONE_MODE_MANUAL = 'manual'; const ALLOWED_GPC_HELPER_REMOTE_HOST = 'gpc.qlhazycoder.top'; function normalizePlusPaymentMethod(value = '') { @@ -47,6 +49,85 @@ return String(value || '').trim().replace(/[^\d]/g, ''); } + function normalizeGpcHelperPhoneMode(value = '') { + const normalized = String(value || '').trim().toLowerCase(); + return normalized === GPC_HELPER_PHONE_MODE_AUTO || normalized === 'builtin' + ? GPC_HELPER_PHONE_MODE_AUTO + : GPC_HELPER_PHONE_MODE_MANUAL; + } + + function normalizeGpcRemainingUses(value) { + if (value === undefined || value === null || String(value).trim() === '') { + return null; + } + const numeric = Number(value); + return Number.isFinite(numeric) ? Math.max(0, Math.floor(numeric)) : null; + } + + function unwrapGpcBalancePayload(payload = {}) { + const data = unwrapGpcResponse(payload); + if (!data || typeof data !== 'object' || Array.isArray(data)) { + return data; + } + const hasBalanceFields = [ + 'remaining_uses', + 'remainingUses', + 'balance', + 'remaining', + 'uses', + 'available_uses', + 'availableUses', + 'auto_mode_enabled', + 'autoModeEnabled', + 'auto_enabled', + 'autoEnabled', + 'status', + 'card_status', + 'cardStatus', + ].some((key) => Object.prototype.hasOwnProperty.call(data, key)); + if (!hasBalanceFields && data.data && typeof data.data === 'object' && !Array.isArray(data.data)) { + return data.data; + } + return data; + } + + function getGpcBalanceRemainingUses(payload = {}) { + const data = unwrapGpcBalancePayload(payload); + if (!data || typeof data !== 'object') { + return null; + } + return normalizeGpcRemainingUses( + data.remaining_uses + ?? data.remainingUses + ?? data.balance + ?? data.remaining + ?? data.uses + ?? data.available_uses + ?? data.availableUses + ); + } + + function isGpcAutoModeEnabled(payload = {}) { + const data = unwrapGpcBalancePayload(payload); + if (!data || typeof data !== 'object') { + return false; + } + const raw = data.auto_mode_enabled ?? data.autoModeEnabled ?? data.auto_enabled ?? data.autoEnabled; + if (typeof raw === 'boolean') { + return raw; + } + const normalized = String(raw ?? '').trim().toLowerCase(); + return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'enabled'; + } + + function getGpcApiKeyStatus(payload = {}) { + const data = unwrapGpcBalancePayload(payload); + if (!data || typeof data !== 'object') { + return ''; + } + return String(data.status || data.card_status || data.cardStatus || '').trim(); + } + function normalizeGpcOtpChannel(value = '') { const normalized = String(value || '').trim().toLowerCase(); if (normalized === 'sms') { @@ -291,7 +372,7 @@ } function formatGpcBalancePayload(payload = {}) { - const data = unwrapGpcResponse(payload); + const data = unwrapGpcBalancePayload(payload); if (!data || typeof data !== 'object') { return ''; } @@ -308,6 +389,11 @@ const totalUses = data.total_uses ?? data.totalUses; const usedUses = data.used_uses ?? data.usedUses; const status = String(data.status || data.card_status || data.cardStatus || '').trim(); + const hasAutoModeField = data.auto_mode_enabled !== undefined + || data.autoModeEnabled !== undefined + || data.auto_enabled !== undefined + || data.autoEnabled !== undefined; + const autoModeEnabled = isGpcAutoModeEnabled(data); const flowId = String(data.flow_id || data.flowId || '').trim(); const parts = []; if (firstValue !== undefined) { @@ -321,6 +407,9 @@ if (status) { parts.push(`状态 ${status}`); } + if (hasAutoModeField) { + parts.push(`自动模式 ${autoModeEnabled ? '已开通' : '未开通'}`); + } if (flowId) { parts.push(`flow_id ${flowId}`); } @@ -330,6 +419,8 @@ return { DEFAULT_GOPAY_COUNTRY_CODE, DEFAULT_GPC_HELPER_API_URL, + GPC_HELPER_PHONE_MODE_AUTO, + GPC_HELPER_PHONE_MODE_MANUAL, PLUS_PAYMENT_METHOD_GPC_HELPER, PLUS_PAYMENT_METHOD_GOPAY, PLUS_PAYMENT_METHOD_PAYPAL, @@ -348,8 +439,13 @@ buildGpcTaskQueryUrl, extractGpcResponseErrorDetail, formatGpcBalancePayload, + getGpcApiKeyStatus, + getGpcBalanceRemainingUses, isGpcUnifiedResponseOk, + isGpcAutoModeEnabled, normalizeGpcHelperBaseUrl, + normalizeGpcHelperPhoneMode, + normalizeGpcRemainingUses, normalizeGpcTaskId, normalizeGoPayCountryCode, normalizeGoPayPhone, @@ -358,6 +454,7 @@ normalizeGoPayPin, normalizeGpcOtpChannel, normalizePlusPaymentMethod, + unwrapGpcBalancePayload, unwrapGpcResponse, }; }); diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 4a4e5bc..72b404a 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -305,6 +305,13 @@ 余额未获取 +