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 @@
余额未获取
+
+ GPC 模式
+
+
GPC 区号