Merge pull request #226 from supperzl/codex/gpc-auto-mode-permission
feat(gpc): add auto mode permission checks
This commit is contained in:
+41
-2
@@ -456,6 +456,11 @@ function normalizePlusPaymentMethod(value = '') {
|
|||||||
return normalized === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_PAYMENT_METHOD_GOPAY : PLUS_PAYMENT_METHOD_PAYPAL;
|
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 = '') {
|
function normalizeContributionModeSource(value = '') {
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
return normalized === CONTRIBUTION_SOURCE_SUB2API
|
return normalized === CONTRIBUTION_SOURCE_SUB2API
|
||||||
@@ -634,6 +639,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
gopayHelperApiUrl: DEFAULT_GPC_HELPER_API_URL,
|
gopayHelperApiUrl: DEFAULT_GPC_HELPER_API_URL,
|
||||||
gopayHelperApiKey: '',
|
gopayHelperApiKey: '',
|
||||||
gopayHelperCardKey: '',
|
gopayHelperCardKey: '',
|
||||||
|
gopayHelperPhoneMode: 'manual',
|
||||||
gopayHelperPhoneNumber: '',
|
gopayHelperPhoneNumber: '',
|
||||||
gopayHelperCountryCode: '+86',
|
gopayHelperCountryCode: '+86',
|
||||||
gopayHelperPin: '',
|
gopayHelperPin: '',
|
||||||
@@ -665,6 +671,9 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
gopayHelperBalancePayload: null,
|
gopayHelperBalancePayload: null,
|
||||||
gopayHelperBalanceUpdatedAt: 0,
|
gopayHelperBalanceUpdatedAt: 0,
|
||||||
gopayHelperBalanceError: '',
|
gopayHelperBalanceError: '',
|
||||||
|
gopayHelperRemainingUses: 0,
|
||||||
|
gopayHelperAutoModeEnabled: false,
|
||||||
|
gopayHelperApiKeyStatus: '',
|
||||||
autoRunSkipFailures: false,
|
autoRunSkipFailures: false,
|
||||||
autoRunFallbackThreadIntervalMinutes: 0,
|
autoRunFallbackThreadIntervalMinutes: 0,
|
||||||
oauthFlowTimeoutEnabled: true,
|
oauthFlowTimeoutEnabled: true,
|
||||||
@@ -2330,6 +2339,10 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return self.GoPayUtils?.normalizeGoPayPin
|
return self.GoPayUtils?.normalizeGoPayPin
|
||||||
? self.GoPayUtils.normalizeGoPayPin(value)
|
? self.GoPayUtils.normalizeGoPayPin(value)
|
||||||
: String(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':
|
case 'gopayHelperPhoneNumber':
|
||||||
return self.GoPayUtils?.normalizeGoPayPhone
|
return self.GoPayUtils?.normalizeGoPayPhone
|
||||||
? self.GoPayUtils.normalizeGoPayPhone(value)
|
? self.GoPayUtils.normalizeGoPayPhone(value)
|
||||||
@@ -2404,6 +2417,7 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
case 'gopayHelperFailureDetail':
|
case 'gopayHelperFailureDetail':
|
||||||
case 'gopayHelperBalance':
|
case 'gopayHelperBalance':
|
||||||
case 'gopayHelperBalanceError':
|
case 'gopayHelperBalanceError':
|
||||||
|
case 'gopayHelperApiKeyStatus':
|
||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
case 'gopayHelperBalancePayload':
|
case 'gopayHelperBalancePayload':
|
||||||
case 'gopayHelperStartPayload':
|
case 'gopayHelperStartPayload':
|
||||||
@@ -2412,10 +2426,12 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
case 'gopayHelperBalanceUpdatedAt':
|
case 'gopayHelperBalanceUpdatedAt':
|
||||||
case 'gopayHelperApiInputWaitSeconds':
|
case 'gopayHelperApiInputWaitSeconds':
|
||||||
case 'gopayHelperOtpInvalidCount':
|
case 'gopayHelperOtpInvalidCount':
|
||||||
|
case 'gopayHelperRemainingUses':
|
||||||
return Math.max(0, Number(value) || 0);
|
return Math.max(0, Number(value) || 0);
|
||||||
case 'autoRunSkipFailures':
|
case 'autoRunSkipFailures':
|
||||||
case 'oauthFlowTimeoutEnabled':
|
case 'oauthFlowTimeoutEnabled':
|
||||||
case 'gopayHelperLocalSmsHelperEnabled':
|
case 'gopayHelperLocalSmsHelperEnabled':
|
||||||
|
case 'gopayHelperAutoModeEnabled':
|
||||||
case 'autoRunDelayEnabled':
|
case 'autoRunDelayEnabled':
|
||||||
case 'step6CookieCleanupEnabled':
|
case 'step6CookieCleanupEnabled':
|
||||||
case 'phoneVerificationEnabled':
|
case 'phoneVerificationEnabled':
|
||||||
@@ -9953,12 +9969,27 @@ async function refreshGpcApiKeyBalance(state = {}, options = {}) {
|
|||||||
const balancePayload = self.GoPayUtils?.unwrapGpcResponse
|
const balancePayload = self.GoPayUtils?.unwrapGpcResponse
|
||||||
? self.GoPayUtils.unwrapGpcResponse(payload)
|
? self.GoPayUtils.unwrapGpcResponse(payload)
|
||||||
: (payload?.data && typeof payload === 'object' ? payload.data : 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 balanceText = formatGpcApiKeyBalancePayload(payload) || rawText || '未知';
|
||||||
const updates = {
|
const updates = {
|
||||||
gopayHelperBalance: balanceText,
|
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(),
|
gopayHelperBalanceUpdatedAt: Date.now(),
|
||||||
gopayHelperBalanceError: '',
|
gopayHelperBalanceError: '',
|
||||||
|
gopayHelperRemainingUses: Math.max(0, Number(remainingUses) || 0),
|
||||||
|
gopayHelperAutoModeEnabled: Boolean(autoModeEnabled),
|
||||||
|
gopayHelperApiKeyStatus: apiKeyStatus,
|
||||||
};
|
};
|
||||||
const flowId = String(balancePayload?.flow_id || balancePayload?.flowId || '').trim();
|
const flowId = String(balancePayload?.flow_id || balancePayload?.flowId || '').trim();
|
||||||
if (flowId) {
|
if (flowId) {
|
||||||
@@ -9987,7 +10018,15 @@ async function refreshGpcApiKeyBalance(state = {}, options = {}) {
|
|||||||
: `GPC 余额查询成功:${balanceText}`,
|
: `GPC 余额查询成功:${balanceText}`,
|
||||||
'info'
|
'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;
|
const refreshGpcCardBalance = refreshGpcApiKeyBalance;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||||
const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top';
|
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 = {}) {
|
function createPlusCheckoutCreateExecutor(deps = {}) {
|
||||||
const {
|
const {
|
||||||
@@ -86,6 +88,17 @@
|
|||||||
return cleaned;
|
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 = '') {
|
function normalizeGpcOtpChannel(value = '') {
|
||||||
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||||
if (rootScope.GoPayUtils?.normalizeGpcOtpChannel) {
|
if (rootScope.GoPayUtils?.normalizeGpcOtpChannel) {
|
||||||
@@ -143,6 +156,17 @@
|
|||||||
return buildGpcHelperApiUrl(apiUrl, '/api/gp/tasks');
|
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 = {}) {
|
function unwrapGpcResponse(payload = {}) {
|
||||||
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||||
if (rootScope.GoPayUtils?.unwrapGpcResponse) {
|
if (rootScope.GoPayUtils?.unwrapGpcResponse) {
|
||||||
@@ -176,6 +200,55 @@
|
|||||||
return payload?.data?.detail || payload?.detail || payload?.message || payload?.error || `HTTP ${status || 0}`;
|
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) {
|
async function fetchJsonWithTimeout(url, options = {}, timeoutMs = 30000) {
|
||||||
const fetcher = typeof fetchImpl === 'function'
|
const fetcher = typeof fetchImpl === 'function'
|
||||||
? fetchImpl
|
? fetchImpl
|
||||||
@@ -226,25 +299,31 @@
|
|||||||
if (!apiUrl) {
|
if (!apiUrl) {
|
||||||
throw new Error('创建 GPC 订单失败:缺少 API 地址。');
|
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 phoneNumber = String(state?.gopayHelperPhoneNumber || '').trim();
|
||||||
const countryCode = normalizeHelperCountryCode(state?.gopayHelperCountryCode || '86');
|
const countryCode = normalizeHelperCountryCode(state?.gopayHelperCountryCode || '86');
|
||||||
const pin = String(state?.gopayHelperPin || '').trim();
|
const pin = String(state?.gopayHelperPin || '').trim();
|
||||||
const apiKey = resolveGpcHelperApiKey(state);
|
const apiKey = resolveGpcHelperApiKey(state);
|
||||||
if (!phoneNumber) {
|
if (!isAutoMode && !phoneNumber) {
|
||||||
throw new Error('创建 GPC 订单失败:缺少手机号。');
|
throw new Error('创建 GPC 订单失败:手动模式缺少手机号。');
|
||||||
}
|
}
|
||||||
if (!pin) {
|
if (!isAutoMode && !pin) {
|
||||||
throw new Error('创建 GPC 订单失败:缺少 PIN。');
|
throw new Error('创建 GPC 订单失败:手动模式缺少 PIN。');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throwIfStopped();
|
||||||
|
await assertGpcApiKeyReadyForCreate(state, phoneMode, apiKey);
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
const payload = {
|
const payload = {
|
||||||
access_token: token,
|
access_token: token,
|
||||||
phone_mode: 'manual',
|
phone_mode: phoneMode,
|
||||||
country_code: countryCode,
|
|
||||||
phone_number: normalizeHelperPhoneNumber(phoneNumber, countryCode),
|
|
||||||
otp_channel: normalizeGpcOtpChannel(state?.gopayHelperOtpChannel),
|
|
||||||
};
|
};
|
||||||
|
if (!isAutoMode) {
|
||||||
|
payload.country_code = countryCode;
|
||||||
|
payload.phone_number = normalizeHelperPhoneNumber(phoneNumber, countryCode);
|
||||||
|
payload.otp_channel = normalizeGpcOtpChannel(state?.gopayHelperOtpChannel);
|
||||||
|
}
|
||||||
|
|
||||||
const orderCreatedAt = Date.now();
|
const orderCreatedAt = Date.now();
|
||||||
const { response, data } = await fetchJsonWithTimeout(apiUrl, {
|
const { response, data } = await fetchJsonWithTimeout(apiUrl, {
|
||||||
@@ -273,6 +352,7 @@
|
|||||||
remoteStage: String(taskData?.remote_stage || taskData?.remoteStage || '').trim(),
|
remoteStage: String(taskData?.remote_stage || taskData?.remoteStage || '').trim(),
|
||||||
orderCreatedAt,
|
orderCreatedAt,
|
||||||
responsePayload: taskData && typeof taskData === 'object' && !Array.isArray(taskData) ? taskData : null,
|
responsePayload: taskData && typeof taskData === 'object' && !Array.isArray(taskData) ? taskData : null,
|
||||||
|
phoneMode: normalizeGpcHelperPhoneMode(taskData?.phone_mode || taskData?.phoneMode || phoneMode),
|
||||||
country: 'ID',
|
country: 'ID',
|
||||||
currency: 'IDR',
|
currency: 'IDR',
|
||||||
checkoutSource: PLUS_PAYMENT_METHOD_GPC_HELPER,
|
checkoutSource: PLUS_PAYMENT_METHOD_GPC_HELPER,
|
||||||
@@ -308,6 +388,7 @@
|
|||||||
gopayHelperTaskStatus: result.taskStatus,
|
gopayHelperTaskStatus: result.taskStatus,
|
||||||
gopayHelperStatusText: result.statusText,
|
gopayHelperStatusText: result.statusText,
|
||||||
gopayHelperRemoteStage: result.remoteStage,
|
gopayHelperRemoteStage: result.remoteStage,
|
||||||
|
gopayHelperPhoneMode: result.phoneMode || normalizeGpcHelperPhoneMode(state?.gopayHelperPhoneMode || state?.phoneMode),
|
||||||
gopayHelperTaskPayload: result.responsePayload,
|
gopayHelperTaskPayload: result.responsePayload,
|
||||||
gopayHelperReferenceId: '',
|
gopayHelperReferenceId: '',
|
||||||
gopayHelperGoPayGuid: '',
|
gopayHelperGoPayGuid: '',
|
||||||
@@ -318,7 +399,7 @@
|
|||||||
gopayHelperStartPayload: null,
|
gopayHelperStartPayload: null,
|
||||||
gopayHelperOrderCreatedAt: result.orderCreatedAt || Date.now(),
|
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, {
|
await completeStepFromBackground(6, {
|
||||||
plusCheckoutCountry: result.country || 'ID',
|
plusCheckoutCountry: result.country || 'ID',
|
||||||
plusCheckoutCurrency: result.currency || 'IDR',
|
plusCheckoutCurrency: result.currency || 'IDR',
|
||||||
|
|||||||
@@ -11,7 +11,27 @@
|
|||||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||||
const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top';
|
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_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 = {
|
const PAYMENT_METHOD_CONFIGS = {
|
||||||
[PLUS_PAYMENT_METHOD_PAYPAL]: {
|
[PLUS_PAYMENT_METHOD_PAYPAL]: {
|
||||||
id: 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;
|
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) {
|
function getPaymentMethodConfig(method = PLUS_PAYMENT_METHOD_PAYPAL) {
|
||||||
return PAYMENT_METHOD_CONFIGS[normalizePlusPaymentMethod(method)] || PAYMENT_METHOD_CONFIGS[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.task_id = String(task.task_id || task.taskId || '').trim();
|
||||||
task.status = String(task.status || '').trim().toLowerCase();
|
task.status = String(task.status || '').trim().toLowerCase();
|
||||||
task.status_text = String(task.status_text || task.statusText || '').trim();
|
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.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_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();
|
task.api_input_deadline_at = String(task.api_input_deadline_at || task.apiInputDeadlineAt || '').trim();
|
||||||
@@ -318,6 +388,7 @@
|
|||||||
gopayHelperTaskId: task.task_id,
|
gopayHelperTaskId: task.task_id,
|
||||||
gopayHelperTaskStatus: task.status,
|
gopayHelperTaskStatus: task.status,
|
||||||
gopayHelperStatusText: task.status_text,
|
gopayHelperStatusText: task.status_text,
|
||||||
|
gopayHelperPhoneMode: task.phone_mode,
|
||||||
gopayHelperRemoteStage: task.remote_stage,
|
gopayHelperRemoteStage: task.remote_stage,
|
||||||
gopayHelperApiWaitingFor: task.api_waiting_for,
|
gopayHelperApiWaitingFor: task.api_waiting_for,
|
||||||
gopayHelperApiInputDeadlineAt: task.api_input_deadline_at,
|
gopayHelperApiInputDeadlineAt: task.api_input_deadline_at,
|
||||||
@@ -675,12 +746,16 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function isGpcTaskOtpWait(task = {}) {
|
function isGpcTaskManualMode(task = {}, state = {}) {
|
||||||
return task?.phone_mode === 'manual' && task?.api_waiting_for === 'otp';
|
return getGpcHelperPhoneMode(state, task) === GPC_HELPER_PHONE_MODE_MANUAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isGpcTaskPinWait(task = {}) {
|
function isGpcTaskOtpWait(task = {}, state = {}) {
|
||||||
return task?.phone_mode === 'manual'
|
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');
|
&& (task?.api_waiting_for === 'pin' || task?.status === 'otp_ready');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -840,27 +915,25 @@
|
|||||||
throw new Error('步骤 7:GPC 模式缺少 API Key。');
|
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 rawPin = String(state?.gopayHelperPin || '').trim();
|
||||||
const pinDigits = rawPin.replace(/[^\d]/g, '');
|
const pinDigits = rawPin.replace(/[^\d]/g, '');
|
||||||
const pin = normalizeSixDigitPin(rawPin);
|
const pin = normalizeSixDigitPin(rawPin);
|
||||||
if (!pin) {
|
if (configuredPhoneMode === GPC_HELPER_PHONE_MODE_MANUAL && !pin) {
|
||||||
if (taskId && apiUrl && apiKey) {
|
if (taskId && apiUrl && apiKey) {
|
||||||
await stopGpcTaskBestEffort(apiUrl, taskId, apiKey, 'PIN 配置错误');
|
await stopGpcTaskBestEffort(apiUrl, taskId, apiKey, 'PIN 配置错误');
|
||||||
}
|
}
|
||||||
throw new Error(pinDigits
|
throw new Error(pinDigits
|
||||||
? '步骤 7:GPC PIN 必须是 6 位数字,请检查侧边栏配置。'
|
? '步骤 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 {
|
try {
|
||||||
while (Date.now() <= deadline) {
|
while (Date.now() <= deadline) {
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
const task = await fetchGpcTaskStatus(apiUrl, taskId, apiKey);
|
const task = await fetchGpcTaskStatus(apiUrl, taskId, apiKey);
|
||||||
const statusText = task?.status_text || task?.status || '处理中';
|
await addLog(formatGpcTaskStatusLog(task), 'info');
|
||||||
const remoteStage = task?.remote_stage || '';
|
|
||||||
const waitingFor = task?.api_waiting_for || '';
|
|
||||||
await addLog(`步骤 7:GPC 任务状态:${statusText}${remoteStage ? `(${remoteStage})` : ''}${waitingFor ? `,等待 ${waitingFor.toUpperCase()}` : ''}`, 'info');
|
|
||||||
|
|
||||||
if (task.status === 'completed') {
|
if (task.status === 'completed') {
|
||||||
terminalReached = true;
|
terminalReached = true;
|
||||||
@@ -879,7 +952,7 @@
|
|||||||
throw buildGpcTaskEndedError(task, 'GPC 任务已结束,请重新创建任务。');
|
throw buildGpcTaskEndedError(task, 'GPC 任务已结束,请重新创建任务。');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGpcTaskOtpWait(task)) {
|
if (isGpcTaskOtpWait(task, state)) {
|
||||||
if (isGpcTaskInputDeadlineExpired(task)) {
|
if (isGpcTaskInputDeadlineExpired(task)) {
|
||||||
throw buildGpcInputDeadlineError(task, 'OTP');
|
throw buildGpcInputDeadlineError(task, 'OTP');
|
||||||
}
|
}
|
||||||
@@ -940,7 +1013,7 @@
|
|||||||
otpLastSubmittedAt = Date.now();
|
otpLastSubmittedAt = Date.now();
|
||||||
lastSubmittedOtp = normalizedOtp;
|
lastSubmittedOtp = normalizedOtp;
|
||||||
await addLog('步骤 7:OTP 已提交,继续等待 GPC 任务状态更新。', 'ok');
|
await addLog('步骤 7:OTP 已提交,继续等待 GPC 任务状态更新。', 'ok');
|
||||||
} else if (isGpcTaskPinWait(task) && !pinSubmitted) {
|
} else if (isGpcTaskPinWait(task, state) && !pinSubmitted) {
|
||||||
if (isGpcTaskInputDeadlineExpired(task)) {
|
if (isGpcTaskInputDeadlineExpired(task)) {
|
||||||
throw buildGpcInputDeadlineError(task, 'PIN');
|
throw buildGpcInputDeadlineError(task, 'PIN');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
{ id: 4, order: 40, key: 'fetch-signup-code', title: '获取注册验证码' },
|
{ id: 4, order: 40, key: 'fetch-signup-code', title: '获取注册验证码' },
|
||||||
{ id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日' },
|
{ id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日' },
|
||||||
{ id: 6, order: 60, key: 'plus-checkout-create', title: '创建 GPC 订单' },
|
{ 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: 10, order: 100, key: 'oauth-login', title: '刷新 OAuth 并登录' },
|
||||||
{ id: 11, order: 110, key: 'fetch-login-code', title: '获取登录验证码' },
|
{ id: 11, order: 110, key: 'fetch-login-code', title: '获取登录验证码' },
|
||||||
{ id: 12, order: 120, key: 'confirm-oauth', title: '自动确认 OAuth' },
|
{ id: 12, order: 120, key: 'confirm-oauth', title: '自动确认 OAuth' },
|
||||||
|
|||||||
+98
-1
@@ -5,6 +5,8 @@
|
|||||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||||
const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top';
|
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';
|
const ALLOWED_GPC_HELPER_REMOTE_HOST = 'gpc.qlhazycoder.top';
|
||||||
|
|
||||||
function normalizePlusPaymentMethod(value = '') {
|
function normalizePlusPaymentMethod(value = '') {
|
||||||
@@ -47,6 +49,85 @@
|
|||||||
return String(value || '').trim().replace(/[^\d]/g, '');
|
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 = '') {
|
function normalizeGpcOtpChannel(value = '') {
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
if (normalized === 'sms') {
|
if (normalized === 'sms') {
|
||||||
@@ -291,7 +372,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatGpcBalancePayload(payload = {}) {
|
function formatGpcBalancePayload(payload = {}) {
|
||||||
const data = unwrapGpcResponse(payload);
|
const data = unwrapGpcBalancePayload(payload);
|
||||||
if (!data || typeof data !== 'object') {
|
if (!data || typeof data !== 'object') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -308,6 +389,11 @@
|
|||||||
const totalUses = data.total_uses ?? data.totalUses;
|
const totalUses = data.total_uses ?? data.totalUses;
|
||||||
const usedUses = data.used_uses ?? data.usedUses;
|
const usedUses = data.used_uses ?? data.usedUses;
|
||||||
const status = String(data.status || data.card_status || data.cardStatus || '').trim();
|
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 flowId = String(data.flow_id || data.flowId || '').trim();
|
||||||
const parts = [];
|
const parts = [];
|
||||||
if (firstValue !== undefined) {
|
if (firstValue !== undefined) {
|
||||||
@@ -321,6 +407,9 @@
|
|||||||
if (status) {
|
if (status) {
|
||||||
parts.push(`状态 ${status}`);
|
parts.push(`状态 ${status}`);
|
||||||
}
|
}
|
||||||
|
if (hasAutoModeField) {
|
||||||
|
parts.push(`自动模式 ${autoModeEnabled ? '已开通' : '未开通'}`);
|
||||||
|
}
|
||||||
if (flowId) {
|
if (flowId) {
|
||||||
parts.push(`flow_id ${flowId}`);
|
parts.push(`flow_id ${flowId}`);
|
||||||
}
|
}
|
||||||
@@ -330,6 +419,8 @@
|
|||||||
return {
|
return {
|
||||||
DEFAULT_GOPAY_COUNTRY_CODE,
|
DEFAULT_GOPAY_COUNTRY_CODE,
|
||||||
DEFAULT_GPC_HELPER_API_URL,
|
DEFAULT_GPC_HELPER_API_URL,
|
||||||
|
GPC_HELPER_PHONE_MODE_AUTO,
|
||||||
|
GPC_HELPER_PHONE_MODE_MANUAL,
|
||||||
PLUS_PAYMENT_METHOD_GPC_HELPER,
|
PLUS_PAYMENT_METHOD_GPC_HELPER,
|
||||||
PLUS_PAYMENT_METHOD_GOPAY,
|
PLUS_PAYMENT_METHOD_GOPAY,
|
||||||
PLUS_PAYMENT_METHOD_PAYPAL,
|
PLUS_PAYMENT_METHOD_PAYPAL,
|
||||||
@@ -348,8 +439,13 @@
|
|||||||
buildGpcTaskQueryUrl,
|
buildGpcTaskQueryUrl,
|
||||||
extractGpcResponseErrorDetail,
|
extractGpcResponseErrorDetail,
|
||||||
formatGpcBalancePayload,
|
formatGpcBalancePayload,
|
||||||
|
getGpcApiKeyStatus,
|
||||||
|
getGpcBalanceRemainingUses,
|
||||||
isGpcUnifiedResponseOk,
|
isGpcUnifiedResponseOk,
|
||||||
|
isGpcAutoModeEnabled,
|
||||||
normalizeGpcHelperBaseUrl,
|
normalizeGpcHelperBaseUrl,
|
||||||
|
normalizeGpcHelperPhoneMode,
|
||||||
|
normalizeGpcRemainingUses,
|
||||||
normalizeGpcTaskId,
|
normalizeGpcTaskId,
|
||||||
normalizeGoPayCountryCode,
|
normalizeGoPayCountryCode,
|
||||||
normalizeGoPayPhone,
|
normalizeGoPayPhone,
|
||||||
@@ -358,6 +454,7 @@
|
|||||||
normalizeGoPayPin,
|
normalizeGoPayPin,
|
||||||
normalizeGpcOtpChannel,
|
normalizeGpcOtpChannel,
|
||||||
normalizePlusPaymentMethod,
|
normalizePlusPaymentMethod,
|
||||||
|
unwrapGpcBalancePayload,
|
||||||
unwrapGpcResponse,
|
unwrapGpcResponse,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -305,6 +305,13 @@
|
|||||||
<span id="display-gpc-helper-balance" class="data-value mono">余额未获取</span>
|
<span id="display-gpc-helper-balance" class="data-value mono">余额未获取</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="data-row" id="row-gpc-helper-phone-mode" style="display:none;">
|
||||||
|
<span class="data-label">GPC 模式</span>
|
||||||
|
<select id="select-gpc-helper-phone-mode" class="data-select">
|
||||||
|
<option value="manual">手动模式</option>
|
||||||
|
<option value="auto">自动模式</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="data-row" id="row-gpc-helper-country-code" style="display:none;">
|
<div class="data-row" id="row-gpc-helper-country-code" style="display:none;">
|
||||||
<span class="data-label">GPC 区号</span>
|
<span class="data-label">GPC 区号</span>
|
||||||
<select id="select-gpc-helper-country-code" class="data-select">
|
<select id="select-gpc-helper-country-code" class="data-select">
|
||||||
|
|||||||
+270
-8
@@ -193,6 +193,8 @@ const inputGpcHelperCardKey = document.getElementById('input-gpc-helper-card-key
|
|||||||
const btnToggleGpcHelperCardKey = document.getElementById('btn-toggle-gpc-helper-card-key');
|
const btnToggleGpcHelperCardKey = document.getElementById('btn-toggle-gpc-helper-card-key');
|
||||||
const btnGpcHelperBalance = document.getElementById('btn-gpc-helper-balance');
|
const btnGpcHelperBalance = document.getElementById('btn-gpc-helper-balance');
|
||||||
const displayGpcHelperBalance = document.getElementById('display-gpc-helper-balance');
|
const displayGpcHelperBalance = document.getElementById('display-gpc-helper-balance');
|
||||||
|
const rowGpcHelperPhoneMode = document.getElementById('row-gpc-helper-phone-mode');
|
||||||
|
const selectGpcHelperPhoneMode = document.getElementById('select-gpc-helper-phone-mode');
|
||||||
const rowGpcHelperCountryCode = document.getElementById('row-gpc-helper-country-code');
|
const rowGpcHelperCountryCode = document.getElementById('row-gpc-helper-country-code');
|
||||||
const selectGpcHelperCountryCode = document.getElementById('select-gpc-helper-country-code');
|
const selectGpcHelperCountryCode = document.getElementById('select-gpc-helper-country-code');
|
||||||
const rowGpcHelperPhone = document.getElementById('row-gpc-helper-phone');
|
const rowGpcHelperPhone = document.getElementById('row-gpc-helper-phone');
|
||||||
@@ -511,6 +513,8 @@ const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
|||||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||||
const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top';
|
const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top';
|
||||||
const GPC_HELPER_PORTAL_URL = 'https://gpc.qlhazycoder.top/';
|
const GPC_HELPER_PORTAL_URL = 'https://gpc.qlhazycoder.top/';
|
||||||
|
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||||
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL;
|
const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL;
|
||||||
const SIGNUP_METHOD_EMAIL = 'email';
|
const SIGNUP_METHOD_EMAIL = 'email';
|
||||||
const SIGNUP_METHOD_PHONE = 'phone';
|
const SIGNUP_METHOD_PHONE = 'phone';
|
||||||
@@ -805,6 +809,15 @@ function getStepDefinitionsForMode(plusModeEnabled = false, options = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStepIdByKeyForCurrentMode(stepKey = '') {
|
||||||
|
const normalizedKey = String(stepKey || '').trim();
|
||||||
|
if (!normalizedKey) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const match = (stepDefinitions || []).find((step) => String(step?.key || '') === normalizedKey);
|
||||||
|
return Number(match?.id) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) {
|
function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) {
|
||||||
currentPlusModeEnabled = Boolean(plusModeEnabled);
|
currentPlusModeEnabled = Boolean(plusModeEnabled);
|
||||||
const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : 'paypal';
|
const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : 'paypal';
|
||||||
@@ -2073,6 +2086,86 @@ function getSelectedPlusPaymentMethod(state = latestState) {
|
|||||||
return normalizePlusPaymentMethod(state?.plusPaymentMethod || currentPlusPaymentMethod || defaultMethod);
|
return normalizePlusPaymentMethod(state?.plusPaymentMethod || currentPlusPaymentMethod || defaultMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeGpcHelperPhoneModeValue(value = '') {
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : 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 getGpcHelperAutoModeEnabled(state = latestState) {
|
||||||
|
return Boolean(state?.gopayHelperAutoModeEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasGpcAutoModePermissionField(payload = {}) {
|
||||||
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return payload.auto_mode_enabled !== undefined
|
||||||
|
|| payload.autoModeEnabled !== undefined
|
||||||
|
|| payload.auto_enabled !== undefined
|
||||||
|
|| payload.autoEnabled !== undefined
|
||||||
|
|| (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data) && hasGpcAutoModePermissionField(payload.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGpcAutoModePermissionDenied(state = latestState) {
|
||||||
|
if (getGpcHelperAutoModeEnabled(state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return hasGpcAutoModePermissionField(state?.gopayHelperBalancePayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeGpcRemainingUsesValue(value) {
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
if (rootScope.GoPayUtils?.normalizeGpcRemainingUses) {
|
||||||
|
return rootScope.GoPayUtils.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 getGpcBalanceRemainingUsesFromResponse(response = {}) {
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
if (rootScope.GoPayUtils?.getGpcBalanceRemainingUses) {
|
||||||
|
const remaining = rootScope.GoPayUtils.getGpcBalanceRemainingUses(response?.data || response?.payload || response);
|
||||||
|
if (remaining !== null && remaining !== undefined) {
|
||||||
|
return remaining;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalizeGpcRemainingUsesValue(
|
||||||
|
response?.remainingUses
|
||||||
|
?? response?.data?.remaining_uses
|
||||||
|
?? response?.data?.remainingUses
|
||||||
|
?? response?.payload?.data?.remaining_uses
|
||||||
|
?? response?.payload?.remaining_uses
|
||||||
|
?? response?.payload?.remainingUses
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGpcAutoModeEnabledFromResponse(response = {}) {
|
||||||
|
if (typeof response?.autoModeEnabled === 'boolean') {
|
||||||
|
return response.autoModeEnabled;
|
||||||
|
}
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
if (rootScope.GoPayUtils?.isGpcAutoModeEnabled) {
|
||||||
|
return rootScope.GoPayUtils.isGpcAutoModeEnabled(response?.data || response?.payload || response);
|
||||||
|
}
|
||||||
|
return Boolean(
|
||||||
|
response?.data?.auto_mode_enabled
|
||||||
|
?? response?.data?.autoModeEnabled
|
||||||
|
?? response?.payload?.data?.auto_mode_enabled
|
||||||
|
?? response?.payload?.auto_mode_enabled
|
||||||
|
?? response?.payload?.autoModeEnabled
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeGpcOtpChannelValue(value = '') {
|
function normalizeGpcOtpChannelValue(value = '') {
|
||||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
if (rootScope.GoPayUtils?.normalizeGpcOtpChannel) {
|
if (rootScope.GoPayUtils?.normalizeGpcOtpChannel) {
|
||||||
@@ -3222,12 +3315,23 @@ function collectSettingsPayload() {
|
|||||||
: (String(latestState?.signupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email'))
|
: (String(latestState?.signupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email'))
|
||||||
);
|
);
|
||||||
const plusPaymentMethod = getSelectedPlusPaymentMethod();
|
const plusPaymentMethod = getSelectedPlusPaymentMethod();
|
||||||
|
const normalizeGpcHelperPhoneModeSafe = typeof normalizeGpcHelperPhoneModeValue === 'function'
|
||||||
|
? normalizeGpcHelperPhoneModeValue
|
||||||
|
: ((value = '') => String(value || '').trim().toLowerCase() === 'auto' || String(value || '').trim().toLowerCase() === 'builtin' ? 'auto' : 'manual');
|
||||||
|
const selectedGpcPhoneMode = normalizeGpcHelperPhoneModeSafe(
|
||||||
|
typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode
|
||||||
|
? selectGpcHelperPhoneMode.value
|
||||||
|
: (latestState?.gopayHelperPhoneMode || 'manual')
|
||||||
|
);
|
||||||
|
const effectiveGpcPhoneMode = (typeof isGpcAutoModePermissionDenied === 'function' && isGpcAutoModePermissionDenied(latestState))
|
||||||
|
? 'manual'
|
||||||
|
: selectedGpcPhoneMode;
|
||||||
const selectedGpcOtpChannel = normalizeGpcOtpChannelSafe(
|
const selectedGpcOtpChannel = normalizeGpcOtpChannelSafe(
|
||||||
typeof selectGpcHelperOtpChannel !== 'undefined' && selectGpcHelperOtpChannel
|
typeof selectGpcHelperOtpChannel !== 'undefined' && selectGpcHelperOtpChannel
|
||||||
? selectGpcHelperOtpChannel.value
|
? selectGpcHelperOtpChannel.value
|
||||||
: (latestState?.gopayHelperOtpChannel || 'whatsapp')
|
: (latestState?.gopayHelperOtpChannel || 'whatsapp')
|
||||||
);
|
);
|
||||||
const selectedGpcLocalSmsHelperEnabled = Boolean(
|
const selectedGpcLocalSmsHelperEnabled = effectiveGpcPhoneMode === 'auto' ? false : Boolean(
|
||||||
typeof inputGpcHelperLocalSmsEnabled !== 'undefined' && inputGpcHelperLocalSmsEnabled
|
typeof inputGpcHelperLocalSmsEnabled !== 'undefined' && inputGpcHelperLocalSmsEnabled
|
||||||
? inputGpcHelperLocalSmsEnabled.checked
|
? inputGpcHelperLocalSmsEnabled.checked
|
||||||
: latestState?.gopayHelperLocalSmsHelperEnabled
|
: latestState?.gopayHelperLocalSmsHelperEnabled
|
||||||
@@ -3342,6 +3446,7 @@ function collectSettingsPayload() {
|
|||||||
? String(inputGpcHelperCardKey.value || '').trim()
|
? String(inputGpcHelperCardKey.value || '').trim()
|
||||||
: String(latestState?.gopayHelperApiKey || latestState?.gopayHelperCardKey || '').trim(),
|
: String(latestState?.gopayHelperApiKey || latestState?.gopayHelperCardKey || '').trim(),
|
||||||
gopayHelperCardKey: '',
|
gopayHelperCardKey: '',
|
||||||
|
gopayHelperPhoneMode: effectiveGpcPhoneMode,
|
||||||
gopayHelperCountryCode: window.GoPayUtils?.normalizeGoPayCountryCode
|
gopayHelperCountryCode: window.GoPayUtils?.normalizeGoPayCountryCode
|
||||||
? window.GoPayUtils.normalizeGoPayCountryCode(typeof selectGpcHelperCountryCode !== 'undefined' && selectGpcHelperCountryCode ? selectGpcHelperCountryCode.value : latestState?.gopayHelperCountryCode)
|
? window.GoPayUtils.normalizeGoPayCountryCode(typeof selectGpcHelperCountryCode !== 'undefined' && selectGpcHelperCountryCode ? selectGpcHelperCountryCode.value : latestState?.gopayHelperCountryCode)
|
||||||
: (typeof selectGpcHelperCountryCode !== 'undefined' && selectGpcHelperCountryCode
|
: (typeof selectGpcHelperCountryCode !== 'undefined' && selectGpcHelperCountryCode
|
||||||
@@ -7192,6 +7297,14 @@ function updatePlusModeUI() {
|
|||||||
? Boolean(inputPlusModeEnabled.checked)
|
? Boolean(inputPlusModeEnabled.checked)
|
||||||
: false;
|
: false;
|
||||||
const method = enabled ? getSelectedPlusPaymentMethod() : defaultMethod;
|
const method = enabled ? getSelectedPlusPaymentMethod() : defaultMethod;
|
||||||
|
const gpcPhoneMode = normalizeGpcHelperPhoneModeValue(
|
||||||
|
typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode
|
||||||
|
? selectGpcHelperPhoneMode.value
|
||||||
|
: (latestState?.gopayHelperPhoneMode || 'manual')
|
||||||
|
);
|
||||||
|
const gpcAutoModeDenied = isGpcAutoModePermissionDenied(latestState);
|
||||||
|
const gpcAutoModeEnabled = getGpcHelperAutoModeEnabled(latestState);
|
||||||
|
const isGpcAutoMode = !gpcAutoModeDenied && gpcPhoneMode === GPC_HELPER_PHONE_MODE_AUTO;
|
||||||
const gpcOtpChannel = normalizeGpcOtpChannelValue(
|
const gpcOtpChannel = normalizeGpcOtpChannelValue(
|
||||||
typeof selectGpcHelperOtpChannel !== 'undefined' && selectGpcHelperOtpChannel
|
typeof selectGpcHelperOtpChannel !== 'undefined' && selectGpcHelperOtpChannel
|
||||||
? selectGpcHelperOtpChannel.value
|
? selectGpcHelperOtpChannel.value
|
||||||
@@ -7206,8 +7319,9 @@ function updatePlusModeUI() {
|
|||||||
? normalizePlusPaymentMethod(selectPlusPaymentMethod.value)
|
? normalizePlusPaymentMethod(selectPlusPaymentMethod.value)
|
||||||
: method;
|
: method;
|
||||||
const gpcRowsVisible = enabled && selectedMethod === gpcValue;
|
const gpcRowsVisible = enabled && selectedMethod === gpcValue;
|
||||||
const localSmsControlsVisible = gpcRowsVisible;
|
const canShowGpcModeSelector = gpcRowsVisible && (gpcAutoModeEnabled || !gpcAutoModeDenied);
|
||||||
const effectiveLocalSmsEnabled = localSmsEnabled;
|
const localSmsControlsVisible = gpcRowsVisible && !isGpcAutoMode;
|
||||||
|
const effectiveLocalSmsEnabled = !isGpcAutoMode && localSmsEnabled;
|
||||||
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
|
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
|
||||||
selectPlusPaymentMethod.value = method;
|
selectPlusPaymentMethod.value = method;
|
||||||
if (selectPlusPaymentMethod.style) {
|
if (selectPlusPaymentMethod.style) {
|
||||||
@@ -7216,7 +7330,7 @@ function updatePlusModeUI() {
|
|||||||
}
|
}
|
||||||
if (typeof plusPaymentMethodCaption !== 'undefined' && plusPaymentMethodCaption) {
|
if (typeof plusPaymentMethodCaption !== 'undefined' && plusPaymentMethodCaption) {
|
||||||
plusPaymentMethodCaption.textContent = method === gpcValue
|
plusPaymentMethodCaption.textContent = method === gpcValue
|
||||||
? 'GPC 订阅链路'
|
? `GPC ${isGpcAutoMode ? '自动' : '手动'}订阅链路`
|
||||||
: method === gopayValue
|
: method === gopayValue
|
||||||
? 'GoPay 印尼订阅链路'
|
? 'GoPay 印尼订阅链路'
|
||||||
: 'PayPal 订阅链路';
|
: 'PayPal 订阅链路';
|
||||||
@@ -7240,6 +7354,19 @@ function updatePlusModeUI() {
|
|||||||
[
|
[
|
||||||
typeof rowGpcHelperApi !== 'undefined' ? rowGpcHelperApi : null,
|
typeof rowGpcHelperApi !== 'undefined' ? rowGpcHelperApi : null,
|
||||||
typeof rowGpcHelperCardKey !== 'undefined' ? rowGpcHelperCardKey : null,
|
typeof rowGpcHelperCardKey !== 'undefined' ? rowGpcHelperCardKey : null,
|
||||||
|
].forEach((row) => {
|
||||||
|
if (!row) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
row.style.display = gpcRowsVisible ? '' : 'none';
|
||||||
|
});
|
||||||
|
if (typeof rowGpcHelperPhoneMode !== 'undefined' && rowGpcHelperPhoneMode) {
|
||||||
|
rowGpcHelperPhoneMode.style.display = canShowGpcModeSelector ? '' : 'none';
|
||||||
|
}
|
||||||
|
if (typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode) {
|
||||||
|
selectGpcHelperPhoneMode.value = gpcAutoModeDenied ? GPC_HELPER_PHONE_MODE_MANUAL : gpcPhoneMode;
|
||||||
|
}
|
||||||
|
[
|
||||||
typeof rowGpcHelperCountryCode !== 'undefined' ? rowGpcHelperCountryCode : null,
|
typeof rowGpcHelperCountryCode !== 'undefined' ? rowGpcHelperCountryCode : null,
|
||||||
typeof rowGpcHelperPhone !== 'undefined' ? rowGpcHelperPhone : null,
|
typeof rowGpcHelperPhone !== 'undefined' ? rowGpcHelperPhone : null,
|
||||||
typeof rowGpcHelperOtpChannel !== 'undefined' ? rowGpcHelperOtpChannel : null,
|
typeof rowGpcHelperOtpChannel !== 'undefined' ? rowGpcHelperOtpChannel : null,
|
||||||
@@ -7248,7 +7375,7 @@ function updatePlusModeUI() {
|
|||||||
if (!row) {
|
if (!row) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
row.style.display = gpcRowsVisible ? '' : 'none';
|
row.style.display = gpcRowsVisible && !isGpcAutoMode ? '' : 'none';
|
||||||
});
|
});
|
||||||
if (typeof selectGpcHelperOtpChannel !== 'undefined' && selectGpcHelperOtpChannel) {
|
if (typeof selectGpcHelperOtpChannel !== 'undefined' && selectGpcHelperOtpChannel) {
|
||||||
selectGpcHelperOtpChannel.value = gpcOtpChannel;
|
selectGpcHelperOtpChannel.value = gpcOtpChannel;
|
||||||
@@ -7464,6 +7591,101 @@ async function persistSignupPhoneInputForAction() {
|
|||||||
await persistSignupPhoneInputValue({ final: true, silent: true });
|
await persistSignupPhoneInputValue({ final: true, silent: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isGpcHelperCheckoutSelected() {
|
||||||
|
const gpcValue = typeof PLUS_PAYMENT_METHOD_GPC_HELPER !== 'undefined' ? PLUS_PAYMENT_METHOD_GPC_HELPER : 'gpc-helper';
|
||||||
|
const plusEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||||
|
? Boolean(inputPlusModeEnabled.checked)
|
||||||
|
: Boolean(latestState?.plusModeEnabled);
|
||||||
|
return plusEnabled && getSelectedPlusPaymentMethod() === gpcValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedGpcHelperPhoneMode() {
|
||||||
|
return normalizeGpcHelperPhoneModeValue(
|
||||||
|
typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode
|
||||||
|
? selectGpcHelperPhoneMode.value
|
||||||
|
: (latestState?.gopayHelperPhoneMode || GPC_HELPER_PHONE_MODE_MANUAL)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showGpcStartBlockedDialog(message) {
|
||||||
|
await openConfirmModal({
|
||||||
|
title: 'GPC 任务无法开启',
|
||||||
|
message,
|
||||||
|
confirmLabel: '知道了',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshGpcBalanceForStart() {
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: 'REFRESH_GPC_CARD_BALANCE',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: {
|
||||||
|
gopayHelperApiUrl: inputGpcHelperApi?.value || DEFAULT_GPC_HELPER_API_URL,
|
||||||
|
gopayHelperApiKey: inputGpcHelperCardKey?.value || latestState?.gopayHelperApiKey || '',
|
||||||
|
reason: 'before_start',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (response?.error) {
|
||||||
|
throw new Error(response.error);
|
||||||
|
}
|
||||||
|
const nextState = {
|
||||||
|
gopayHelperBalance: response?.balance || latestState?.gopayHelperBalance || '',
|
||||||
|
gopayHelperBalancePayload: response?.data || response?.payload?.data || response?.payload || latestState?.gopayHelperBalancePayload || null,
|
||||||
|
gopayHelperBalanceUpdatedAt: response?.updatedAt || Date.now(),
|
||||||
|
gopayHelperBalanceError: '',
|
||||||
|
gopayHelperRemainingUses: getGpcBalanceRemainingUsesFromResponse(response) ?? 0,
|
||||||
|
gopayHelperAutoModeEnabled: getGpcAutoModeEnabledFromResponse(response),
|
||||||
|
gopayHelperApiKeyStatus: response?.apiKeyStatus || response?.data?.status || response?.payload?.data?.status || response?.payload?.status || '',
|
||||||
|
};
|
||||||
|
syncLatestState(nextState);
|
||||||
|
if (displayGpcHelperBalance && nextState.gopayHelperBalance) {
|
||||||
|
displayGpcHelperBalance.textContent = nextState.gopayHelperBalance;
|
||||||
|
}
|
||||||
|
updatePlusModeUI();
|
||||||
|
return nextState;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureGpcApiKeyReadyForStart(options = {}) {
|
||||||
|
if (!isGpcHelperCheckoutSelected()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const selectedMode = getSelectedGpcHelperPhoneMode();
|
||||||
|
let balanceState;
|
||||||
|
try {
|
||||||
|
balanceState = await refreshGpcBalanceForStart();
|
||||||
|
} catch (error) {
|
||||||
|
await showGpcStartBlockedDialog(`API Key 余额校验失败:${error?.message || '未知错误'}。请先确认 API Key 是否正确。`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingUses = normalizeGpcRemainingUsesValue(balanceState.gopayHelperRemainingUses);
|
||||||
|
const apiKeyStatus = String(balanceState.gopayHelperApiKeyStatus || '').trim().toLowerCase();
|
||||||
|
if (apiKeyStatus && apiKeyStatus !== 'active') {
|
||||||
|
await showGpcStartBlockedDialog(`当前 GPC API Key 状态为 ${balanceState.gopayHelperApiKeyStatus},不能开启任务。`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (remainingUses !== null && remainingUses <= 0) {
|
||||||
|
await showGpcStartBlockedDialog('当前 GPC API Key 剩余次数不足,不能开启任务。');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedMode === GPC_HELPER_PHONE_MODE_AUTO && !balanceState.gopayHelperAutoModeEnabled) {
|
||||||
|
if (typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode) {
|
||||||
|
selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL;
|
||||||
|
}
|
||||||
|
syncLatestState({ gopayHelperPhoneMode: GPC_HELPER_PHONE_MODE_MANUAL });
|
||||||
|
updatePlusModeUI();
|
||||||
|
await saveSettings({ silent: true, force: true }).catch(() => {});
|
||||||
|
await showGpcStartBlockedDialog('当前 GPC API Key 未开通自动模式,已切回手动模式,不能以自动模式开启任务。');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.notify) {
|
||||||
|
showToast('GPC API Key 余额和权限校验通过。', 'success', 1800);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async function openPlusManualConfirmationDialog(options = {}) {
|
async function openPlusManualConfirmationDialog(options = {}) {
|
||||||
const method = String(options.method || '').trim().toLowerCase();
|
const method = String(options.method || '').trim().toLowerCase();
|
||||||
const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay';
|
const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay';
|
||||||
@@ -7987,6 +8209,9 @@ function applySettingsState(state) {
|
|||||||
if (typeof inputGpcHelperCardKey !== 'undefined' && inputGpcHelperCardKey) {
|
if (typeof inputGpcHelperCardKey !== 'undefined' && inputGpcHelperCardKey) {
|
||||||
inputGpcHelperCardKey.value = state?.gopayHelperApiKey || state?.gopayHelperCardKey || '';
|
inputGpcHelperCardKey.value = state?.gopayHelperApiKey || state?.gopayHelperCardKey || '';
|
||||||
}
|
}
|
||||||
|
if (typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode) {
|
||||||
|
selectGpcHelperPhoneMode.value = normalizeGpcHelperPhoneModeValue(state?.gopayHelperPhoneMode || 'manual');
|
||||||
|
}
|
||||||
if (typeof selectGpcHelperCountryCode !== 'undefined' && selectGpcHelperCountryCode) {
|
if (typeof selectGpcHelperCountryCode !== 'undefined' && selectGpcHelperCountryCode) {
|
||||||
const normalizedCountryCode = window.GoPayUtils?.normalizeGoPayCountryCode
|
const normalizedCountryCode = window.GoPayUtils?.normalizeGoPayCountryCode
|
||||||
? window.GoPayUtils.normalizeGoPayCountryCode(state?.gopayHelperCountryCode)
|
? window.GoPayUtils.normalizeGoPayCountryCode(state?.gopayHelperCountryCode)
|
||||||
@@ -10849,6 +11074,10 @@ stepsList?.addEventListener('click', async (event) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await persistCurrentSettingsForAction();
|
await persistCurrentSettingsForAction();
|
||||||
|
const gpcCreateStep = getStepIdByKeyForCurrentMode('plus-checkout-create') || 6;
|
||||||
|
if (step === gpcCreateStep && !(await ensureGpcApiKeyReadyForStart())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (step === 3) {
|
if (step === 3) {
|
||||||
if (inputPassword.value !== (latestState?.customPassword || '')) {
|
if (inputPassword.value !== (latestState?.customPassword || '')) {
|
||||||
await chrome.runtime.sendMessage({
|
await chrome.runtime.sendMessage({
|
||||||
@@ -11105,6 +11334,10 @@ async function startAutoRunFromCurrentSettings() {
|
|||||||
if (typeof persistCurrentSettingsForAction === 'function') {
|
if (typeof persistCurrentSettingsForAction === 'function') {
|
||||||
await persistCurrentSettingsForAction();
|
await persistCurrentSettingsForAction();
|
||||||
}
|
}
|
||||||
|
if (!(await ensureGpcApiKeyReadyForStart())) {
|
||||||
|
clearPendingAutoRunStartRunCount();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const customEmailPoolEnabled = typeof usesCustomEmailPoolGenerator === 'function'
|
const customEmailPoolEnabled = typeof usesCustomEmailPoolGenerator === 'function'
|
||||||
&& usesCustomEmailPoolGenerator();
|
&& usesCustomEmailPoolGenerator();
|
||||||
@@ -11435,7 +11668,7 @@ btnGpcHelperBalance?.addEventListener('click', async () => {
|
|||||||
type: 'REFRESH_GPC_CARD_BALANCE',
|
type: 'REFRESH_GPC_CARD_BALANCE',
|
||||||
source: 'sidepanel',
|
source: 'sidepanel',
|
||||||
payload: {
|
payload: {
|
||||||
gopayHelperApiUrl: DEFAULT_GPC_HELPER_API_URL,
|
gopayHelperApiUrl: inputGpcHelperApi?.value || DEFAULT_GPC_HELPER_API_URL,
|
||||||
gopayHelperApiKey: inputGpcHelperCardKey?.value || '',
|
gopayHelperApiKey: inputGpcHelperCardKey?.value || '',
|
||||||
gopayHelperCountryCode: selectGpcHelperCountryCode?.value || '+86',
|
gopayHelperCountryCode: selectGpcHelperCountryCode?.value || '+86',
|
||||||
reason: 'manual',
|
reason: 'manual',
|
||||||
@@ -11447,7 +11680,25 @@ btnGpcHelperBalance?.addEventListener('click', async () => {
|
|||||||
if (displayGpcHelperBalance) {
|
if (displayGpcHelperBalance) {
|
||||||
displayGpcHelperBalance.textContent = response?.balance || '余额已更新';
|
displayGpcHelperBalance.textContent = response?.balance || '余额已更新';
|
||||||
}
|
}
|
||||||
showToast('GPC 余额已更新。', 'success');
|
const nextState = {
|
||||||
|
gopayHelperBalance: response?.balance || latestState?.gopayHelperBalance || '',
|
||||||
|
gopayHelperBalancePayload: response?.data || response?.payload?.data || response?.payload || latestState?.gopayHelperBalancePayload || null,
|
||||||
|
gopayHelperBalanceUpdatedAt: response?.updatedAt || Date.now(),
|
||||||
|
gopayHelperBalanceError: '',
|
||||||
|
gopayHelperRemainingUses: getGpcBalanceRemainingUsesFromResponse(response) ?? 0,
|
||||||
|
gopayHelperAutoModeEnabled: getGpcAutoModeEnabledFromResponse(response),
|
||||||
|
gopayHelperApiKeyStatus: response?.apiKeyStatus || response?.data?.status || response?.payload?.data?.status || response?.payload?.status || '',
|
||||||
|
};
|
||||||
|
syncLatestState(nextState);
|
||||||
|
if (!nextState.gopayHelperAutoModeEnabled && getSelectedGpcHelperPhoneMode() === GPC_HELPER_PHONE_MODE_AUTO) {
|
||||||
|
selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL;
|
||||||
|
syncLatestState({ gopayHelperPhoneMode: GPC_HELPER_PHONE_MODE_MANUAL });
|
||||||
|
await saveSettings({ silent: true, force: true }).catch(() => {});
|
||||||
|
showToast('当前 API Key 未开通自动模式,已切回手动模式。', 'warn');
|
||||||
|
} else {
|
||||||
|
showToast(nextState.gopayHelperAutoModeEnabled ? 'GPC 余额已更新,自动模式可用。' : 'GPC 余额已更新,当前 API Key 只能使用手动模式。', 'success');
|
||||||
|
}
|
||||||
|
updatePlusModeUI();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast(error?.message || '查询 GPC 余额失败。', 'error');
|
showToast(error?.message || '查询 GPC 余额失败。', 'error');
|
||||||
}
|
}
|
||||||
@@ -11466,6 +11717,7 @@ selectPlusPaymentMethod?.addEventListener('change', () => {
|
|||||||
[
|
[
|
||||||
inputGpcHelperApi,
|
inputGpcHelperApi,
|
||||||
inputGpcHelperCardKey,
|
inputGpcHelperCardKey,
|
||||||
|
selectGpcHelperPhoneMode,
|
||||||
selectGpcHelperCountryCode,
|
selectGpcHelperCountryCode,
|
||||||
inputGpcHelperPhone,
|
inputGpcHelperPhone,
|
||||||
selectGpcHelperOtpChannel,
|
selectGpcHelperOtpChannel,
|
||||||
@@ -11482,7 +11734,7 @@ selectPlusPaymentMethod?.addEventListener('change', () => {
|
|||||||
scheduleSettingsAutoSave();
|
scheduleSettingsAutoSave();
|
||||||
});
|
});
|
||||||
input?.addEventListener('change', () => {
|
input?.addEventListener('change', () => {
|
||||||
if (input === selectGpcHelperOtpChannel || input === inputGpcHelperLocalSmsEnabled) {
|
if (input === selectGpcHelperPhoneMode || input === selectGpcHelperOtpChannel || input === inputGpcHelperLocalSmsEnabled) {
|
||||||
updatePlusModeUI();
|
updatePlusModeUI();
|
||||||
}
|
}
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
@@ -13393,6 +13645,14 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
if (message.payload.plusPaymentMethod !== undefined && selectPlusPaymentMethod) {
|
if (message.payload.plusPaymentMethod !== undefined && selectPlusPaymentMethod) {
|
||||||
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(message.payload.plusPaymentMethod);
|
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(message.payload.plusPaymentMethod);
|
||||||
}
|
}
|
||||||
|
if (message.payload.gopayHelperPhoneMode !== undefined && selectGpcHelperPhoneMode) {
|
||||||
|
selectGpcHelperPhoneMode.value = normalizeGpcHelperPhoneModeValue(message.payload.gopayHelperPhoneMode);
|
||||||
|
}
|
||||||
|
if (message.payload.gopayHelperAutoModeEnabled === false && selectGpcHelperPhoneMode?.value === GPC_HELPER_PHONE_MODE_AUTO) {
|
||||||
|
selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL;
|
||||||
|
syncLatestState({ gopayHelperPhoneMode: GPC_HELPER_PHONE_MODE_MANUAL });
|
||||||
|
showToast('当前 API Key 未开通自动模式,已切回手动模式。', 'warn', 2200);
|
||||||
|
}
|
||||||
if (message.payload.gopayHelperOtpChannel !== undefined && selectGpcHelperOtpChannel) {
|
if (message.payload.gopayHelperOtpChannel !== undefined && selectGpcHelperOtpChannel) {
|
||||||
selectGpcHelperOtpChannel.value = normalizeGpcOtpChannelValue(message.payload.gopayHelperOtpChannel);
|
selectGpcHelperOtpChannel.value = normalizeGpcOtpChannelValue(message.payload.gopayHelperOtpChannel);
|
||||||
}
|
}
|
||||||
@@ -13414,6 +13674,8 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
if (
|
if (
|
||||||
message.payload.plusModeEnabled !== undefined
|
message.payload.plusModeEnabled !== undefined
|
||||||
|| message.payload.plusPaymentMethod !== undefined
|
|| message.payload.plusPaymentMethod !== undefined
|
||||||
|
|| message.payload.gopayHelperPhoneMode !== undefined
|
||||||
|
|| message.payload.gopayHelperAutoModeEnabled !== undefined
|
||||||
|| message.payload.gopayHelperOtpChannel !== undefined
|
|| message.payload.gopayHelperOtpChannel !== undefined
|
||||||
|| message.payload.gopayHelperLocalSmsHelperEnabled !== undefined
|
|| message.payload.gopayHelperLocalSmsHelperEnabled !== undefined
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ test('background account history settings are normalized independently from hotm
|
|||||||
extractFunction('normalizeAccountRunHistoryHelperBaseUrl'),
|
extractFunction('normalizeAccountRunHistoryHelperBaseUrl'),
|
||||||
extractFunction('normalizeVerificationResendCount'),
|
extractFunction('normalizeVerificationResendCount'),
|
||||||
extractFunction('normalizePlusPaymentMethod'),
|
extractFunction('normalizePlusPaymentMethod'),
|
||||||
|
extractFunction('normalizeGpcHelperPhoneMode'),
|
||||||
extractFunction('normalizePhoneSmsProvider'),
|
extractFunction('normalizePhoneSmsProvider'),
|
||||||
extractFunction('normalizePhoneSmsProviderOrder'),
|
extractFunction('normalizePhoneSmsProviderOrder'),
|
||||||
extractFunction('normalizeSignupMethod'),
|
extractFunction('normalizeSignupMethod'),
|
||||||
@@ -207,6 +208,12 @@ return {
|
|||||||
);
|
);
|
||||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperApiUrl', ''), 'https://gpc.qlhazycoder.top');
|
assert.equal(api.normalizePersistentSettingValue('gopayHelperApiUrl', ''), 'https://gpc.qlhazycoder.top');
|
||||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperApiKey', ' gpc-123 '), 'gpc-123');
|
assert.equal(api.normalizePersistentSettingValue('gopayHelperApiKey', ' gpc-123 '), 'gpc-123');
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('gopayHelperPhoneMode', 'auto'), 'auto');
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('gopayHelperPhoneMode', 'builtin'), 'auto');
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('gopayHelperPhoneMode', 'unknown'), 'manual');
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('gopayHelperRemainingUses', '998'), 998);
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('gopayHelperAutoModeEnabled', 1), true);
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('gopayHelperApiKeyStatus', ' active '), 'active');
|
||||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperCountryCode', ' 86 '), '+86');
|
assert.equal(api.normalizePersistentSettingValue('gopayHelperCountryCode', ' 86 '), '+86');
|
||||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperPhoneNumber', ' +86 138-0013-8000 '), '+8613800138000');
|
assert.equal(api.normalizePersistentSettingValue('gopayHelperPhoneNumber', ' +86 138-0013-8000 '), '+8613800138000');
|
||||||
assert.equal(api.normalizePersistentSettingValue('gopayHelperPin', ' 12-34-56 '), '123456');
|
assert.equal(api.normalizePersistentSettingValue('gopayHelperPin', ' 12-34-56 '), '123456');
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ function createRouter(overrides = {}) {
|
|||||||
verifyHotmailAccount: async () => {},
|
verifyHotmailAccount: async () => {},
|
||||||
refreshGpcCardBalance: overrides.refreshGpcCardBalance || (async (state, options) => {
|
refreshGpcCardBalance: overrides.refreshGpcCardBalance || (async (state, options) => {
|
||||||
events.balanceRefreshes.push({ state, options });
|
events.balanceRefreshes.push({ state, options });
|
||||||
return { balance: '余额 3' };
|
return { balance: '余额 3', remainingUses: 3, autoModeEnabled: true, apiKeyStatus: 'active' };
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -556,7 +556,7 @@ test('message router refreshes GPC balance through explicit sidepanel message',
|
|||||||
},
|
},
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
assert.deepStrictEqual(response, { ok: true, balance: '余额 3' });
|
assert.deepStrictEqual(response, { ok: true, balance: '余额 3', remainingUses: 3, autoModeEnabled: true, apiKeyStatus: 'active' });
|
||||||
assert.equal(events.balanceRefreshes.length, 1);
|
assert.equal(events.balanceRefreshes.length, 1);
|
||||||
assert.equal(events.balanceRefreshes[0].state.gopayHelperApiUrl, 'http://localhost:18473/');
|
assert.equal(events.balanceRefreshes[0].state.gopayHelperApiUrl, 'http://localhost:18473/');
|
||||||
assert.equal(events.balanceRefreshes[0].state.gopayHelperApiKey, 'payload_api_key');
|
assert.equal(events.balanceRefreshes[0].state.gopayHelperApiKey, 'payload_api_key');
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ test('GoPay utils normalize manual OTP input', () => {
|
|||||||
assert.equal(api.normalizeGpcOtpChannel('sms'), 'sms');
|
assert.equal(api.normalizeGpcOtpChannel('sms'), 'sms');
|
||||||
assert.equal(api.normalizeGpcOtpChannel('wa'), 'whatsapp');
|
assert.equal(api.normalizeGpcOtpChannel('wa'), 'whatsapp');
|
||||||
assert.equal(api.normalizeGpcOtpChannel('unknown'), 'whatsapp');
|
assert.equal(api.normalizeGpcOtpChannel('unknown'), 'whatsapp');
|
||||||
|
assert.equal(api.normalizeGpcHelperPhoneMode('auto'), 'auto');
|
||||||
|
assert.equal(api.normalizeGpcHelperPhoneMode('builtin'), 'auto');
|
||||||
|
assert.equal(api.normalizeGpcHelperPhoneMode('manual'), 'manual');
|
||||||
|
assert.equal(api.normalizeGpcHelperPhoneMode('unknown'), 'manual');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GoPay utils keeps GPC helper payment method distinct', () => {
|
test('GoPay utils keeps GPC helper payment method distinct', () => {
|
||||||
@@ -81,10 +85,21 @@ test('GoPay utils formats balance and maps linked-account errors', () => {
|
|||||||
api.formatGpcBalancePayload({
|
api.formatGpcBalancePayload({
|
||||||
code: 200,
|
code: 200,
|
||||||
message: 'ok',
|
message: 'ok',
|
||||||
data: { remaining_uses: 0, total_uses: 3, used_uses: 3, status: 'active' },
|
data: { remaining_uses: 0, total_uses: 3, used_uses: 3, status: 'active', auto_mode_enabled: false },
|
||||||
}),
|
}),
|
||||||
'余额 0/3,已用 3,状态 active'
|
'余额 0/3,已用 3,状态 active,自动模式 未开通'
|
||||||
);
|
);
|
||||||
|
assert.equal(
|
||||||
|
api.formatGpcBalancePayload({
|
||||||
|
code: 200,
|
||||||
|
message: 'ok',
|
||||||
|
data: { remaining_uses: 998, total_uses: 1000, used_uses: 2, status: 'active', auto_mode_enabled: true },
|
||||||
|
}),
|
||||||
|
'余额 998/1000,已用 2,状态 active,自动模式 已开通'
|
||||||
|
);
|
||||||
|
assert.equal(api.getGpcBalanceRemainingUses({ data: { remaining_uses: 998 } }), 998);
|
||||||
|
assert.equal(api.isGpcAutoModeEnabled({ data: { auto_mode_enabled: true } }), true);
|
||||||
|
assert.equal(api.isGpcAutoModeEnabled({ data: { auto_mode_enabled: false } }), false);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
api.unwrapGpcResponse({ code: 200, message: 'ok', data: { task_id: 'task_1' } }),
|
api.unwrapGpcResponse({ code: 200, message: 'ok', data: { task_id: 'task_1' } }),
|
||||||
{ task_id: 'task_1' }
|
{ task_id: 'task_1' }
|
||||||
|
|||||||
@@ -903,11 +903,119 @@ test('GPC billing polls queue task, submits WhatsApp OTP then PIN, and waits unt
|
|||||||
assert.equal(pinCall.options.headers['X-API-Key'], 'gpc_billing_123');
|
assert.equal(pinCall.options.headers['X-API-Key'], 'gpc_billing_123');
|
||||||
assert.ok(fetchCalls.findIndex((call) => call.url.endsWith('/api/gp/tasks/task_123/pin')) < fetchCalls.length - 1);
|
assert.ok(fetchCalls.findIndex((call) => call.url.endsWith('/api/gp/tasks/task_123/pin')) < fetchCalls.length - 1);
|
||||||
assert.equal(events.states.some((state) => state.gopayHelperTaskId === 'task_123' && state.gopayHelperTaskStatus === 'completed'), true);
|
assert.equal(events.states.some((state) => state.gopayHelperTaskId === 'task_123' && state.gopayHelperTaskStatus === 'completed'), true);
|
||||||
|
assert.equal(events.logs.some((entry) => entry.message === '步骤 7:GPC 任务状态:等待 WhatsApp OTP'), true);
|
||||||
|
assert.equal(events.logs.some((entry) => /whatsapp_otp_wait/.test(entry.message)), false);
|
||||||
assert.equal(events.completed[0].step, 7);
|
assert.equal(events.completed[0].step, 7);
|
||||||
assert.equal(events.completed[0].payload.plusCheckoutSource, 'gpc-helper');
|
assert.equal(events.completed[0].payload.plusCheckoutSource, 'gpc-helper');
|
||||||
assert.ok(events.sleeps.includes(3000));
|
assert.ok(events.sleeps.includes(3000));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('GPC billing auto mode only polls until completed without OTP or PIN submission', async () => {
|
||||||
|
const fetchCalls = [];
|
||||||
|
let pollCount = 0;
|
||||||
|
const { events, executor } = createExecutorHarness({
|
||||||
|
frames: [],
|
||||||
|
stateByFrame: {},
|
||||||
|
fetchImpl: async (url, options = {}) => {
|
||||||
|
fetchCalls.push({ url, options });
|
||||||
|
if (url === 'https://gpc.qlhazycoder.top/api/gp/tasks/task_auto') {
|
||||||
|
pollCount += 1;
|
||||||
|
if (pollCount === 1) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => createGpcTaskResponse({ task_id: 'task_auto', phone_mode: 'auto', status: 'queued', status_text: '排队中', api_waiting_for: '' }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pollCount === 2) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => createGpcTaskResponse({ task_id: 'task_auto', phone_mode: 'auto', status: 'active', status_text: '处理中', remote_stage: 'auto_otp_wait', api_waiting_for: 'auto_otp' }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => createGpcTaskResponse({ task_id: 'task_auto', phone_mode: 'auto', status: 'completed', status_text: '充值完成', remote_stage: 'completed', api_waiting_for: '' }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error(`unexpected url: ${url}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executePlusCheckoutBilling({
|
||||||
|
plusPaymentMethod: 'gpc-helper',
|
||||||
|
plusCheckoutSource: 'gpc-helper',
|
||||||
|
gopayHelperTaskId: 'task_auto',
|
||||||
|
gopayHelperPhoneMode: 'auto',
|
||||||
|
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||||
|
gopayHelperApiKey: 'gpc_auto',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(fetchCalls.length, 3);
|
||||||
|
assert.equal(fetchCalls.some((call) => /\/api\/gp\/tasks\/task_auto\/(otp|pin)$/.test(call.url)), false);
|
||||||
|
assert.equal(events.logs.some((entry) => entry.message === '步骤 7:GPC 任务状态:等待自动 OTP'), true);
|
||||||
|
assert.equal(events.logs.some((entry) => /auto_otp_wait/.test(entry.message)), false);
|
||||||
|
assert.equal(events.states.some((state) => state.plusManualConfirmationMethod === 'gopay-otp'), false);
|
||||||
|
assert.equal(events.states.some((state) => state.gopayHelperTaskId === 'task_auto' && state.gopayHelperPhoneMode === 'auto' && state.gopayHelperTaskStatus === 'completed'), true);
|
||||||
|
assert.equal(events.completed[0].step, 7);
|
||||||
|
assert.equal(events.completed[0].payload.plusCheckoutSource, 'gpc-helper');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GPC billing logs checkout order stage in Chinese', async () => {
|
||||||
|
let pollCount = 0;
|
||||||
|
const { events, executor } = createExecutorHarness({
|
||||||
|
frames: [],
|
||||||
|
stateByFrame: {},
|
||||||
|
fetchImpl: async (url) => {
|
||||||
|
if (url === 'https://gpc.qlhazycoder.top/api/gp/tasks/task_stage') {
|
||||||
|
pollCount += 1;
|
||||||
|
if (pollCount === 1) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => createGpcTaskResponse({
|
||||||
|
task_id: 'task_stage',
|
||||||
|
phone_mode: 'auto',
|
||||||
|
status: 'active',
|
||||||
|
status_text: '处理中',
|
||||||
|
remote_stage: 'checkout_order_start',
|
||||||
|
api_waiting_for: '',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => createGpcTaskResponse({
|
||||||
|
task_id: 'task_stage',
|
||||||
|
phone_mode: 'auto',
|
||||||
|
status: 'completed',
|
||||||
|
status_text: '充值完成',
|
||||||
|
remote_stage: 'completed',
|
||||||
|
api_waiting_for: '',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error(`unexpected url: ${url}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executePlusCheckoutBilling({
|
||||||
|
plusPaymentMethod: 'gpc-helper',
|
||||||
|
plusCheckoutSource: 'gpc-helper',
|
||||||
|
gopayHelperTaskId: 'task_stage',
|
||||||
|
gopayHelperPhoneMode: 'auto',
|
||||||
|
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||||
|
gopayHelperApiKey: 'gpc_auto',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(events.logs.some((entry) => entry.message === '步骤 7:GPC 任务状态:创建订单'), true);
|
||||||
|
assert.equal(events.logs.some((entry) => /checkout_order_start/.test(entry.message)), false);
|
||||||
|
});
|
||||||
|
|
||||||
test('GPC billing reads SMS OTP from local helper for sms_otp_wait', async () => {
|
test('GPC billing reads SMS OTP from local helper for sms_otp_wait', async () => {
|
||||||
const fetchCalls = [];
|
const fetchCalls = [];
|
||||||
let pollCount = 0;
|
let pollCount = 0;
|
||||||
|
|||||||
@@ -3,9 +3,42 @@ const assert = require('node:assert/strict');
|
|||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
|
|
||||||
const source = fs.readFileSync('background/steps/create-plus-checkout.js', 'utf8');
|
const source = fs.readFileSync('background/steps/create-plus-checkout.js', 'utf8');
|
||||||
|
const gopayUtilsSource = fs.readFileSync('gopay-utils.js', 'utf8');
|
||||||
const globalScope = {};
|
const globalScope = {};
|
||||||
|
new Function('self', `${gopayUtilsSource};`)(globalScope);
|
||||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundPlusCheckoutCreate;`)(globalScope);
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundPlusCheckoutCreate;`)(globalScope);
|
||||||
|
|
||||||
|
function createGpcBalanceResponse(overrides = {}) {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'ok',
|
||||||
|
data: {
|
||||||
|
api_key: 'gpc_test',
|
||||||
|
status: 'active',
|
||||||
|
auto_mode_enabled: false,
|
||||||
|
total_uses: 1000,
|
||||||
|
remaining_uses: 998,
|
||||||
|
used_uses: 2,
|
||||||
|
...overrides,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createGpcTaskResponse(overrides = {}) {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'ok',
|
||||||
|
data: {
|
||||||
|
task_id: 'task_123',
|
||||||
|
status: 'active',
|
||||||
|
status_text: '处理中',
|
||||||
|
phone_mode: 'manual',
|
||||||
|
remote_stage: 'checkout_start',
|
||||||
|
...overrides,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
test('Plus checkout create does not wait 20 seconds after opening checkout page', async () => {
|
test('Plus checkout create does not wait 20 seconds after opening checkout page', async () => {
|
||||||
const events = [];
|
const events = [];
|
||||||
const executor = api.createPlusCheckoutCreateExecutor({
|
const executor = api.createPlusCheckoutCreateExecutor({
|
||||||
@@ -107,7 +140,7 @@ test('GoPay plus checkout create forwards gopay payment method to the checkout c
|
|||||||
assert.deepStrictEqual(events[0]?.payload, { paymentMethod: 'gopay' });
|
assert.deepStrictEqual(events[0]?.payload, { paymentMethod: 'gopay' });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GPC checkout injects Plus script before reading ChatGPT session token and sends X-API-Key', async () => {
|
test('GPC manual checkout injects Plus script before reading ChatGPT session token and sends X-API-Key', async () => {
|
||||||
const events = [];
|
const events = [];
|
||||||
const fetchCalls = [];
|
const fetchCalls = [];
|
||||||
const executor = api.createPlusCheckoutCreateExecutor({
|
const executor = api.createPlusCheckoutCreateExecutor({
|
||||||
@@ -128,18 +161,9 @@ test('GPC checkout injects Plus script before reading ChatGPT session token and
|
|||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
status: 200,
|
status: 200,
|
||||||
json: async () => ({
|
json: async () => url.endsWith('/api/gp/balance')
|
||||||
code: 200,
|
? createGpcBalanceResponse({ auto_mode_enabled: false, remaining_uses: 998 })
|
||||||
message: 'ok',
|
: createGpcTaskResponse({ otp_channel: 'whatsapp' }),
|
||||||
data: {
|
|
||||||
task_id: 'task_123',
|
|
||||||
status: 'active',
|
|
||||||
status_text: '处理中',
|
|
||||||
phone_mode: 'manual',
|
|
||||||
remote_stage: 'checkout_start',
|
|
||||||
otp_channel: 'whatsapp',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
registerTab: async (source, tabId) => events.push({ type: 'register', source, tabId }),
|
registerTab: async (source, tabId) => events.push({ type: 'register', source, tabId }),
|
||||||
@@ -155,6 +179,7 @@ test('GPC checkout injects Plus script before reading ChatGPT session token and
|
|||||||
await executor.executePlusCheckoutCreate({
|
await executor.executePlusCheckoutCreate({
|
||||||
email: 'Current.Round+GPC@Example.COM',
|
email: 'Current.Round+GPC@Example.COM',
|
||||||
plusPaymentMethod: 'gpc-helper',
|
plusPaymentMethod: 'gpc-helper',
|
||||||
|
gopayHelperPhoneMode: 'manual',
|
||||||
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||||
gopayHelperPhoneNumber: '+8613800138000',
|
gopayHelperPhoneNumber: '+8613800138000',
|
||||||
gopayPhone: '',
|
gopayPhone: '',
|
||||||
@@ -172,9 +197,11 @@ test('GPC checkout injects Plus script before reading ChatGPT session token and
|
|||||||
includeSession: true,
|
includeSession: true,
|
||||||
includeAccessToken: true,
|
includeAccessToken: true,
|
||||||
});
|
});
|
||||||
assert.equal(fetchCalls.length, 1);
|
assert.equal(fetchCalls.length, 2);
|
||||||
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/tasks');
|
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||||
const helperPayload = JSON.parse(fetchCalls[0].options.body);
|
assert.equal(fetchCalls[0].options.headers['X-API-Key'], 'gpc_test_123');
|
||||||
|
assert.equal(fetchCalls[1].url, 'https://gpc.qlhazycoder.top/api/gp/tasks');
|
||||||
|
const helperPayload = JSON.parse(fetchCalls[1].options.body);
|
||||||
assert.deepEqual(helperPayload, {
|
assert.deepEqual(helperPayload, {
|
||||||
access_token: 'session-access-token',
|
access_token: 'session-access-token',
|
||||||
phone_mode: 'manual',
|
phone_mode: 'manual',
|
||||||
@@ -182,7 +209,7 @@ test('GPC checkout injects Plus script before reading ChatGPT session token and
|
|||||||
phone_number: '13800138000',
|
phone_number: '13800138000',
|
||||||
otp_channel: 'whatsapp',
|
otp_channel: 'whatsapp',
|
||||||
});
|
});
|
||||||
assert.equal(fetchCalls[0].options.headers['X-API-Key'], 'gpc_test_123');
|
assert.equal(fetchCalls[1].options.headers['X-API-Key'], 'gpc_test_123');
|
||||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'card_key'), false);
|
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'card_key'), false);
|
||||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'customer_email'), false);
|
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'customer_email'), false);
|
||||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'checkout_ui_mode'), false);
|
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'checkout_ui_mode'), false);
|
||||||
@@ -199,6 +226,165 @@ test('GPC checkout injects Plus script before reading ChatGPT session token and
|
|||||||
assert.equal(events.find((event) => event.type === 'complete')?.payload?.plusCheckoutSource, 'gpc-helper');
|
assert.equal(events.find((event) => event.type === 'complete')?.payload?.plusCheckoutSource, 'gpc-helper');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('GPC auto checkout only sends access token and API Key', async () => {
|
||||||
|
const events = [];
|
||||||
|
const fetchCalls = [];
|
||||||
|
const executor = api.createPlusCheckoutCreateExecutor({
|
||||||
|
addLog: async (message, level = 'info') => events.push({ type: 'log', message, level }),
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
create: async () => {
|
||||||
|
throw new Error('should not open token tab when direct access token exists');
|
||||||
|
},
|
||||||
|
remove: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeStepFromBackground: async (step, payload) => events.push({ type: 'complete', step, payload }),
|
||||||
|
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||||
|
fetch: async (url, options = {}) => {
|
||||||
|
fetchCalls.push({ url, options });
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => url.endsWith('/api/gp/balance')
|
||||||
|
? createGpcBalanceResponse({ auto_mode_enabled: true, remaining_uses: 998 })
|
||||||
|
: createGpcTaskResponse({
|
||||||
|
task_id: 'task_auto',
|
||||||
|
status: 'queued',
|
||||||
|
status_text: '排队中',
|
||||||
|
phone_mode: 'auto',
|
||||||
|
api_waiting_for: '',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
registerTab: async () => {},
|
||||||
|
sendTabMessageUntilStopped: async () => ({}),
|
||||||
|
setState: async (payload) => events.push({ type: 'set-state', payload }),
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
waitForTabCompleteUntilStopped: async () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executePlusCheckoutCreate({
|
||||||
|
plusPaymentMethod: 'gpc-helper',
|
||||||
|
gopayHelperPhoneMode: 'auto',
|
||||||
|
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||||
|
chatgptAccessToken: 'state-access-token',
|
||||||
|
gopayHelperApiKey: 'gpc_auto_123',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(fetchCalls.length, 2);
|
||||||
|
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||||
|
assert.equal(fetchCalls[0].options.headers['X-API-Key'], 'gpc_auto_123');
|
||||||
|
assert.equal(fetchCalls[1].url, 'https://gpc.qlhazycoder.top/api/gp/tasks');
|
||||||
|
const helperPayload = JSON.parse(fetchCalls[1].options.body);
|
||||||
|
assert.deepEqual(helperPayload, {
|
||||||
|
access_token: 'state-access-token',
|
||||||
|
phone_mode: 'auto',
|
||||||
|
});
|
||||||
|
assert.equal(fetchCalls[1].options.headers['X-API-Key'], 'gpc_auto_123');
|
||||||
|
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'country_code'), false);
|
||||||
|
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'phone_number'), false);
|
||||||
|
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'otp_channel'), false);
|
||||||
|
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'pin'), false);
|
||||||
|
const statePayload = events.find((event) => event.type === 'set-state')?.payload || {};
|
||||||
|
assert.equal(statePayload.gopayHelperTaskId, 'task_auto');
|
||||||
|
assert.equal(statePayload.gopayHelperPhoneMode, 'auto');
|
||||||
|
assert.equal(statePayload.gopayHelperTaskStatus, 'queued');
|
||||||
|
assert.equal(events.find((event) => event.type === 'complete')?.step, 6);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GPC auto checkout blocks API Keys without auto mode permission', async () => {
|
||||||
|
const fetchCalls = [];
|
||||||
|
const executor = api.createPlusCheckoutCreateExecutor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
create: async () => {
|
||||||
|
throw new Error('should not open token tab when direct access token exists');
|
||||||
|
},
|
||||||
|
remove: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeStepFromBackground: async () => {},
|
||||||
|
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||||
|
fetch: async (url, options = {}) => {
|
||||||
|
fetchCalls.push({ url, options });
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => createGpcBalanceResponse({ auto_mode_enabled: false, remaining_uses: 998 }),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
registerTab: async () => {},
|
||||||
|
sendTabMessageUntilStopped: async () => ({}),
|
||||||
|
setState: async () => {},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
waitForTabCompleteUntilStopped: async () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => executor.executePlusCheckoutCreate({
|
||||||
|
plusPaymentMethod: 'gpc-helper',
|
||||||
|
gopayHelperPhoneMode: 'auto',
|
||||||
|
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||||
|
chatgptAccessToken: 'state-access-token',
|
||||||
|
gopayHelperApiKey: 'gpc_auto_disabled',
|
||||||
|
}),
|
||||||
|
/未开通自动模式/
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(fetchCalls.length, 1);
|
||||||
|
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GPC checkout blocks exhausted API Keys before creating task', async () => {
|
||||||
|
const fetchCalls = [];
|
||||||
|
const executor = api.createPlusCheckoutCreateExecutor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
create: async () => {
|
||||||
|
throw new Error('should not open token tab when direct access token exists');
|
||||||
|
},
|
||||||
|
remove: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeStepFromBackground: async () => {},
|
||||||
|
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||||
|
fetch: async (url, options = {}) => {
|
||||||
|
fetchCalls.push({ url, options });
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => createGpcBalanceResponse({ auto_mode_enabled: false, remaining_uses: 0 }),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
registerTab: async () => {},
|
||||||
|
sendTabMessageUntilStopped: async () => ({}),
|
||||||
|
setState: async () => {},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
waitForTabCompleteUntilStopped: async () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => executor.executePlusCheckoutCreate({
|
||||||
|
plusPaymentMethod: 'gpc-helper',
|
||||||
|
gopayHelperPhoneMode: 'manual',
|
||||||
|
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||||
|
chatgptAccessToken: 'state-access-token',
|
||||||
|
gopayHelperPhoneNumber: '+8613800138000',
|
||||||
|
gopayHelperCountryCode: '+86',
|
||||||
|
gopayHelperPin: '123456',
|
||||||
|
gopayHelperApiKey: 'gpc_exhausted',
|
||||||
|
}),
|
||||||
|
/剩余次数不足/
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(fetchCalls.length, 1);
|
||||||
|
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||||
|
});
|
||||||
|
|
||||||
test('GPC checkout forwards selected SMS OTP channel', async () => {
|
test('GPC checkout forwards selected SMS OTP channel', async () => {
|
||||||
const fetchCalls = [];
|
const fetchCalls = [];
|
||||||
const executor = api.createPlusCheckoutCreateExecutor({
|
const executor = api.createPlusCheckoutCreateExecutor({
|
||||||
@@ -216,11 +402,9 @@ test('GPC checkout forwards selected SMS OTP channel', async () => {
|
|||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
status: 200,
|
status: 200,
|
||||||
json: async () => ({
|
json: async () => url.endsWith('/api/gp/balance')
|
||||||
code: 200,
|
? createGpcBalanceResponse({ auto_mode_enabled: false, remaining_uses: 998 })
|
||||||
message: 'ok',
|
: createGpcTaskResponse({ task_id: 'task_sms', status: 'active', phone_mode: 'manual', remote_stage: 'checkout_start' }),
|
||||||
data: { task_id: 'task_sms', status: 'active', phone_mode: 'manual', remote_stage: 'checkout_start' },
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
registerTab: async () => {},
|
registerTab: async () => {},
|
||||||
@@ -241,10 +425,13 @@ test('GPC checkout forwards selected SMS OTP channel', async () => {
|
|||||||
gopayHelperOtpChannel: 'sms',
|
gopayHelperOtpChannel: 'sms',
|
||||||
});
|
});
|
||||||
|
|
||||||
const helperPayload = JSON.parse(fetchCalls[0].options.body);
|
assert.equal(fetchCalls.length, 2);
|
||||||
|
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||||
|
assert.equal(fetchCalls[0].options.headers['X-API-Key'], 'gpc_sms');
|
||||||
|
const helperPayload = JSON.parse(fetchCalls[1].options.body);
|
||||||
assert.equal(helperPayload.phone_mode, 'manual');
|
assert.equal(helperPayload.phone_mode, 'manual');
|
||||||
assert.equal(helperPayload.otp_channel, 'sms');
|
assert.equal(helperPayload.otp_channel, 'sms');
|
||||||
assert.equal(fetchCalls[0].options.headers['X-API-Key'], 'gpc_sms');
|
assert.equal(fetchCalls[1].options.headers['X-API-Key'], 'gpc_sms');
|
||||||
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'card_key'), false);
|
assert.equal(Object.prototype.hasOwnProperty.call(helperPayload, 'card_key'), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -264,6 +451,13 @@ test('GPC checkout surfaces unified queue API errors', async () => {
|
|||||||
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
ensureContentScriptReadyOnTabUntilStopped: async () => {},
|
||||||
fetch: async (url, options = {}) => {
|
fetch: async (url, options = {}) => {
|
||||||
fetchCalls.push({ url, options });
|
fetchCalls.push({ url, options });
|
||||||
|
if (url.endsWith('/api/gp/balance')) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => createGpcBalanceResponse({ auto_mode_enabled: false, remaining_uses: 998 }),
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
status: 400,
|
status: 400,
|
||||||
@@ -295,9 +489,11 @@ test('GPC checkout surfaces unified queue API errors', async () => {
|
|||||||
/创建 GPC 订单失败:access_token 无效/
|
/创建 GPC 订单失败:access_token 无效/
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(fetchCalls.length, 1);
|
assert.equal(fetchCalls.length, 2);
|
||||||
assert.equal(Object.prototype.hasOwnProperty.call(JSON.parse(fetchCalls[0].options.body), 'card_key'), false);
|
assert.equal(fetchCalls[0].url, 'https://gpc.qlhazycoder.top/api/gp/balance');
|
||||||
assert.equal(fetchCalls[0].options.headers['X-API-Key'], 'gpc_paid_456');
|
assert.equal(fetchCalls[1].url, 'https://gpc.qlhazycoder.top/api/gp/tasks');
|
||||||
|
assert.equal(Object.prototype.hasOwnProperty.call(JSON.parse(fetchCalls[1].options.body), 'card_key'), false);
|
||||||
|
assert.equal(fetchCalls[1].options.headers['X-API-Key'], 'gpc_paid_456');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GPC checkout does not fall back to browser GoPay phone fields', async () => {
|
test('GPC checkout does not fall back to browser GoPay phone fields', async () => {
|
||||||
@@ -326,6 +522,7 @@ test('GPC checkout does not fall back to browser GoPay phone fields', async () =
|
|||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
() => executor.executePlusCheckoutCreate({
|
() => executor.executePlusCheckoutCreate({
|
||||||
plusPaymentMethod: 'gpc-helper',
|
plusPaymentMethod: 'gpc-helper',
|
||||||
|
gopayHelperPhoneMode: 'manual',
|
||||||
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
gopayHelperApiUrl: 'https://gpc.qlhazycoder.top/',
|
||||||
chatgptAccessToken: 'state-access-token',
|
chatgptAccessToken: 'state-access-token',
|
||||||
email: 'helper-phone-test@example.com',
|
email: 'helper-phone-test@example.com',
|
||||||
|
|||||||
@@ -113,6 +113,9 @@ async function refreshContributionContentHint() {
|
|||||||
events.push({ type: 'refresh' });
|
events.push({ type: 'refresh' });
|
||||||
${refreshImpl ? 'return (' + refreshImpl + ')();' : 'return null;'}
|
${refreshImpl ? 'return (' + refreshImpl + ')();' : 'return null;'}
|
||||||
}
|
}
|
||||||
|
async function ensureGpcApiKeyReadyForStart() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
${bundle}
|
${bundle}
|
||||||
return {
|
return {
|
||||||
startAutoRunFromCurrentSettings,
|
startAutoRunFromCurrentSettings,
|
||||||
|
|||||||
@@ -132,6 +132,10 @@ test('sidepanel Plus UI hides PayPal account selector while GoPay is selected',
|
|||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('normalizePlusPaymentMethod'),
|
extractFunction('normalizePlusPaymentMethod'),
|
||||||
extractFunction('getSelectedPlusPaymentMethod'),
|
extractFunction('getSelectedPlusPaymentMethod'),
|
||||||
|
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||||
|
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||||
|
extractFunction('hasGpcAutoModePermissionField'),
|
||||||
|
extractFunction('isGpcAutoModePermissionDenied'),
|
||||||
extractFunction('normalizeGpcOtpChannelValue'),
|
extractFunction('normalizeGpcOtpChannelValue'),
|
||||||
extractFunction('updatePlusModeUI'),
|
extractFunction('updatePlusModeUI'),
|
||||||
].join('\n');
|
].join('\n');
|
||||||
@@ -141,6 +145,8 @@ let latestState = { plusPaymentMethod: 'gopay' };
|
|||||||
let currentPlusPaymentMethod = 'paypal';
|
let currentPlusPaymentMethod = 'paypal';
|
||||||
const inputPlusModeEnabled = { checked: true };
|
const inputPlusModeEnabled = { checked: true };
|
||||||
const selectPlusPaymentMethod = { value: 'gopay', style: { display: 'none' } };
|
const selectPlusPaymentMethod = { value: 'gopay', style: { display: 'none' } };
|
||||||
|
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||||
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
const rowPayPalAccount = { style: { display: '' } };
|
const rowPayPalAccount = { style: { display: '' } };
|
||||||
${bundle}
|
${bundle}
|
||||||
return { updatePlusModeUI, selectPlusPaymentMethod, rowPayPalAccount };
|
return { updatePlusModeUI, selectPlusPaymentMethod, rowPayPalAccount };
|
||||||
@@ -209,21 +215,29 @@ test('sidepanel Plus UI shows GPC fields and purchase button only for GPC', () =
|
|||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('normalizePlusPaymentMethod'),
|
extractFunction('normalizePlusPaymentMethod'),
|
||||||
extractFunction('getSelectedPlusPaymentMethod'),
|
extractFunction('getSelectedPlusPaymentMethod'),
|
||||||
|
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||||
|
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||||
|
extractFunction('hasGpcAutoModePermissionField'),
|
||||||
|
extractFunction('isGpcAutoModePermissionDenied'),
|
||||||
extractFunction('normalizeGpcOtpChannelValue'),
|
extractFunction('normalizeGpcOtpChannelValue'),
|
||||||
extractFunction('updatePlusModeUI'),
|
extractFunction('updatePlusModeUI'),
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
let latestState = { plusPaymentMethod: 'gpc-helper' };
|
let latestState = { plusPaymentMethod: 'gpc-helper', gopayHelperAutoModeEnabled: true };
|
||||||
let currentPlusPaymentMethod = 'paypal';
|
let currentPlusPaymentMethod = 'paypal';
|
||||||
const inputPlusModeEnabled = { checked: true };
|
const inputPlusModeEnabled = { checked: true };
|
||||||
const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } };
|
const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } };
|
||||||
|
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||||
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
const plusPaymentMethodCaption = { textContent: '' };
|
const plusPaymentMethodCaption = { textContent: '' };
|
||||||
const btnGpcCardKeyPurchase = { style: { display: 'none' } };
|
const btnGpcCardKeyPurchase = { style: { display: 'none' } };
|
||||||
const rowPayPalAccount = { style: { display: '' } };
|
const rowPayPalAccount = { style: { display: '' } };
|
||||||
const rowPlusPaymentMethod = { style: { display: 'none' } };
|
const rowPlusPaymentMethod = { style: { display: 'none' } };
|
||||||
const rowGpcHelperApi = { style: { display: 'none' } };
|
const rowGpcHelperApi = { style: { display: 'none' } };
|
||||||
const rowGpcHelperCardKey = { style: { display: 'none' } };
|
const rowGpcHelperCardKey = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperPhoneMode = { style: { display: 'none' } };
|
||||||
|
const selectGpcHelperPhoneMode = { value: 'manual' };
|
||||||
const rowGpcHelperCountryCode = { style: { display: 'none' } };
|
const rowGpcHelperCountryCode = { style: { display: 'none' } };
|
||||||
const rowGpcHelperPhone = { style: { display: 'none' } };
|
const rowGpcHelperPhone = { style: { display: 'none' } };
|
||||||
const rowGpcHelperOtpChannel = { style: { display: 'none' } };
|
const rowGpcHelperOtpChannel = { style: { display: 'none' } };
|
||||||
@@ -240,12 +254,13 @@ ${bundle}
|
|||||||
return {
|
return {
|
||||||
updatePlusModeUI,
|
updatePlusModeUI,
|
||||||
selectPlusPaymentMethod,
|
selectPlusPaymentMethod,
|
||||||
|
selectGpcHelperPhoneMode,
|
||||||
selectGpcHelperOtpChannel,
|
selectGpcHelperOtpChannel,
|
||||||
inputGpcHelperLocalSmsEnabled,
|
inputGpcHelperLocalSmsEnabled,
|
||||||
btnGpcCardKeyPurchase,
|
btnGpcCardKeyPurchase,
|
||||||
rowPayPalAccount,
|
rowPayPalAccount,
|
||||||
plusPaymentMethodCaption,
|
plusPaymentMethodCaption,
|
||||||
rows: { rowGpcHelperApi, rowGpcHelperCardKey, rowGpcHelperCountryCode, rowGpcHelperPhone, rowGpcHelperOtpChannel, rowGpcHelperLocalSmsEnabled, rowGpcHelperLocalSmsUrl, rowGpcHelperPin },
|
rows: { rowGpcHelperApi, rowGpcHelperCardKey, rowGpcHelperPhoneMode, rowGpcHelperCountryCode, rowGpcHelperPhone, rowGpcHelperOtpChannel, rowGpcHelperLocalSmsEnabled, rowGpcHelperLocalSmsUrl, rowGpcHelperPin },
|
||||||
};
|
};
|
||||||
`)();
|
`)();
|
||||||
|
|
||||||
@@ -255,6 +270,7 @@ return {
|
|||||||
assert.equal(api.btnGpcCardKeyPurchase.style.display, '');
|
assert.equal(api.btnGpcCardKeyPurchase.style.display, '');
|
||||||
assert.equal(api.rows.rowGpcHelperApi.style.display, '');
|
assert.equal(api.rows.rowGpcHelperApi.style.display, '');
|
||||||
assert.equal(api.rows.rowGpcHelperCardKey.style.display, '');
|
assert.equal(api.rows.rowGpcHelperCardKey.style.display, '');
|
||||||
|
assert.equal(api.rows.rowGpcHelperPhoneMode.style.display, '');
|
||||||
assert.equal(api.rows.rowGpcHelperPhone.style.display, '');
|
assert.equal(api.rows.rowGpcHelperPhone.style.display, '');
|
||||||
assert.equal(api.rows.rowGpcHelperOtpChannel.style.display, '');
|
assert.equal(api.rows.rowGpcHelperOtpChannel.style.display, '');
|
||||||
assert.equal(api.rows.rowGpcHelperLocalSmsEnabled.style.display, '');
|
assert.equal(api.rows.rowGpcHelperLocalSmsEnabled.style.display, '');
|
||||||
@@ -272,6 +288,15 @@ return {
|
|||||||
assert.equal(api.rows.rowGpcHelperLocalSmsEnabled.style.display, '');
|
assert.equal(api.rows.rowGpcHelperLocalSmsEnabled.style.display, '');
|
||||||
assert.equal(api.rows.rowGpcHelperLocalSmsUrl.style.display, '');
|
assert.equal(api.rows.rowGpcHelperLocalSmsUrl.style.display, '');
|
||||||
|
|
||||||
|
api.selectGpcHelperPhoneMode.value = 'auto';
|
||||||
|
api.updatePlusModeUI();
|
||||||
|
assert.equal(api.rows.rowGpcHelperPhoneMode.style.display, '');
|
||||||
|
assert.equal(api.rows.rowGpcHelperPhone.style.display, 'none');
|
||||||
|
assert.equal(api.rows.rowGpcHelperOtpChannel.style.display, 'none');
|
||||||
|
assert.equal(api.rows.rowGpcHelperLocalSmsEnabled.style.display, 'none');
|
||||||
|
assert.equal(api.rows.rowGpcHelperLocalSmsUrl.style.display, 'none');
|
||||||
|
assert.match(api.plusPaymentMethodCaption.textContent, /自动/);
|
||||||
|
|
||||||
api.selectPlusPaymentMethod.value = 'gopay';
|
api.selectPlusPaymentMethod.value = 'gopay';
|
||||||
api.updatePlusModeUI();
|
api.updatePlusModeUI();
|
||||||
assert.equal(api.btnGpcCardKeyPurchase.style.display, 'none');
|
assert.equal(api.btnGpcCardKeyPurchase.style.display, 'none');
|
||||||
@@ -279,6 +304,103 @@ return {
|
|||||||
assert.equal(api.rowPayPalAccount.style.display, 'none');
|
assert.equal(api.rowPayPalAccount.style.display, 'none');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sidepanel hides GPC auto mode selector when API Key has no auto permission', () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('normalizePlusPaymentMethod'),
|
||||||
|
extractFunction('getSelectedPlusPaymentMethod'),
|
||||||
|
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||||
|
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||||
|
extractFunction('hasGpcAutoModePermissionField'),
|
||||||
|
extractFunction('isGpcAutoModePermissionDenied'),
|
||||||
|
extractFunction('normalizeGpcOtpChannelValue'),
|
||||||
|
extractFunction('updatePlusModeUI'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
let latestState = { plusPaymentMethod: 'gpc-helper', gopayHelperPhoneMode: 'auto', gopayHelperAutoModeEnabled: false, gopayHelperBalancePayload: { auto_mode_enabled: false } };
|
||||||
|
let currentPlusPaymentMethod = 'gpc-helper';
|
||||||
|
const inputPlusModeEnabled = { checked: true };
|
||||||
|
const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } };
|
||||||
|
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||||
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
|
const plusPaymentMethodCaption = { textContent: '' };
|
||||||
|
const btnGpcCardKeyPurchase = { style: { display: 'none' } };
|
||||||
|
const rowPayPalAccount = { style: { display: '' } };
|
||||||
|
const rowPlusPaymentMethod = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperApi = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperCardKey = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperPhoneMode = { style: { display: 'none' } };
|
||||||
|
const selectGpcHelperPhoneMode = { value: 'auto' };
|
||||||
|
const rowGpcHelperCountryCode = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperPhone = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperOtpChannel = { style: { display: 'none' } };
|
||||||
|
const selectGpcHelperOtpChannel = { value: 'whatsapp' };
|
||||||
|
const rowGpcHelperLocalSmsEnabled = { style: { display: 'none' } };
|
||||||
|
const inputGpcHelperLocalSmsEnabled = { checked: false };
|
||||||
|
const rowGpcHelperLocalSmsUrl = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperPin = { style: { display: 'none' } };
|
||||||
|
${bundle}
|
||||||
|
return { updatePlusModeUI, selectGpcHelperPhoneMode, plusPaymentMethodCaption, rows: { rowGpcHelperPhoneMode, rowGpcHelperPhone, rowGpcHelperOtpChannel, rowGpcHelperPin } };
|
||||||
|
`)();
|
||||||
|
|
||||||
|
api.updatePlusModeUI();
|
||||||
|
|
||||||
|
assert.equal(api.rows.rowGpcHelperPhoneMode.style.display, 'none');
|
||||||
|
assert.equal(api.selectGpcHelperPhoneMode.value, 'manual');
|
||||||
|
assert.equal(api.rows.rowGpcHelperPhone.style.display, '');
|
||||||
|
assert.equal(api.rows.rowGpcHelperOtpChannel.style.display, '');
|
||||||
|
assert.equal(api.rows.rowGpcHelperPin.style.display, '');
|
||||||
|
assert.match(api.plusPaymentMethodCaption.textContent, /手动/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sidepanel keeps selected GPC auto mode before permission has been queried', () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('normalizePlusPaymentMethod'),
|
||||||
|
extractFunction('getSelectedPlusPaymentMethod'),
|
||||||
|
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||||
|
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||||
|
extractFunction('hasGpcAutoModePermissionField'),
|
||||||
|
extractFunction('isGpcAutoModePermissionDenied'),
|
||||||
|
extractFunction('normalizeGpcOtpChannelValue'),
|
||||||
|
extractFunction('updatePlusModeUI'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
let latestState = { plusPaymentMethod: 'gpc-helper', gopayHelperPhoneMode: 'auto', gopayHelperAutoModeEnabled: false, gopayHelperBalancePayload: null };
|
||||||
|
let currentPlusPaymentMethod = 'gpc-helper';
|
||||||
|
const inputPlusModeEnabled = { checked: true };
|
||||||
|
const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } };
|
||||||
|
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||||
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
|
const plusPaymentMethodCaption = { textContent: '' };
|
||||||
|
const rowPayPalAccount = { style: { display: '' } };
|
||||||
|
const rowPlusPaymentMethod = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperApi = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperCardKey = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperPhoneMode = { style: { display: 'none' } };
|
||||||
|
const selectGpcHelperPhoneMode = { value: 'auto' };
|
||||||
|
const rowGpcHelperCountryCode = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperPhone = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperOtpChannel = { style: { display: 'none' } };
|
||||||
|
const selectGpcHelperOtpChannel = { value: 'whatsapp' };
|
||||||
|
const rowGpcHelperLocalSmsEnabled = { style: { display: 'none' } };
|
||||||
|
const inputGpcHelperLocalSmsEnabled = { checked: false };
|
||||||
|
const rowGpcHelperLocalSmsUrl = { style: { display: 'none' } };
|
||||||
|
const rowGpcHelperPin = { style: { display: 'none' } };
|
||||||
|
${bundle}
|
||||||
|
return { updatePlusModeUI, selectGpcHelperPhoneMode, plusPaymentMethodCaption, rows: { rowGpcHelperPhoneMode, rowGpcHelperPhone, rowGpcHelperOtpChannel, rowGpcHelperPin } };
|
||||||
|
`)();
|
||||||
|
|
||||||
|
api.updatePlusModeUI();
|
||||||
|
|
||||||
|
assert.equal(api.rows.rowGpcHelperPhoneMode.style.display, '');
|
||||||
|
assert.equal(api.selectGpcHelperPhoneMode.value, 'auto');
|
||||||
|
assert.equal(api.rows.rowGpcHelperPhone.style.display, 'none');
|
||||||
|
assert.equal(api.rows.rowGpcHelperOtpChannel.style.display, 'none');
|
||||||
|
assert.equal(api.rows.rowGpcHelperPin.style.display, 'none');
|
||||||
|
assert.match(api.plusPaymentMethodCaption.textContent, /自动/);
|
||||||
|
});
|
||||||
|
|
||||||
test('sidepanel resolves pending GoPay manual confirmation from DATA_UPDATED state', async () => {
|
test('sidepanel resolves pending GoPay manual confirmation from DATA_UPDATED state', async () => {
|
||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('openPlusManualConfirmationDialog'),
|
extractFunction('openPlusManualConfirmationDialog'),
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), [1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13]);
|
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), [1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13]);
|
||||||
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), 13);
|
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), 13);
|
||||||
assert.equal(gpcSteps[5].title, '创建 GPC 订单');
|
assert.equal(gpcSteps[5].title, '创建 GPC 订单');
|
||||||
assert.equal(gpcSteps[6].title, 'GPC OTP/PIN 验证');
|
assert.equal(gpcSteps[6].title, '等待 GPC 任务完成');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('sidepanel html loads shared step definitions before sidepanel bootstrap', () => {
|
test('sidepanel html loads shared step definitions before sidepanel bootstrap', () => {
|
||||||
@@ -142,6 +142,9 @@ test('sidepanel html exposes Plus mode, PayPal, and GoPay settings', () => {
|
|||||||
assert.match(html, />转换 API Key</);
|
assert.match(html, />转换 API Key</);
|
||||||
assert.match(html, /GPC API Key/);
|
assert.match(html, /GPC API Key/);
|
||||||
assert.match(html, /id="input-gpc-helper-card-key"/);
|
assert.match(html, /id="input-gpc-helper-card-key"/);
|
||||||
|
assert.match(html, /GPC 模式/);
|
||||||
|
assert.match(html, /id="select-gpc-helper-phone-mode"/);
|
||||||
|
assert.match(html, /<option value="auto">自动模式<\/option>/);
|
||||||
assert.match(html, /id="btn-gpc-helper-balance"/);
|
assert.match(html, /id="btn-gpc-helper-balance"/);
|
||||||
assert.match(html, /id="input-gpc-helper-phone"/);
|
assert.match(html, /id="input-gpc-helper-phone"/);
|
||||||
assert.match(html, /id="select-gpc-helper-otp-channel"/);
|
assert.match(html, /id="select-gpc-helper-otp-channel"/);
|
||||||
|
|||||||
+3
-3
@@ -183,7 +183,7 @@
|
|||||||
- Codex2API 配置
|
- Codex2API 配置
|
||||||
- IP 代理持久配置:`ipProxyEnabled`、服务商、模式、API 地址、服务商配置快照、账号列表、固定 Host / Port / Protocol / Username / Password、地区参数、session 与自动切换阈值
|
- IP 代理持久配置:`ipProxyEnabled`、服务商、模式、API 地址、服务商配置快照、账号列表、固定 Host / Port / Protocol / Username / Password、地区参数、session 与自动切换阈值
|
||||||
- Plus 模式开关 `plusModeEnabled`
|
- Plus 模式开关 `plusModeEnabled`
|
||||||
- Plus 支付方式 `plusPaymentMethod`,GoPay 配置 `gopayPhone / gopayPin`,GPC helper 配置 `gopayHelperApiUrl / gopayHelperApiKey / gopayHelperPhoneNumber / gopayHelperOtpChannel / gopayHelperLocalSmsHelperEnabled / gopayHelperLocalSmsHelperUrl / gopayHelperPin`;其中 GPC API 地址固定归一为 `https://gpc.qlhazycoder.top`
|
- Plus 支付方式 `plusPaymentMethod`,GoPay 配置 `gopayPhone / gopayPin`,GPC helper 配置 `gopayHelperApiUrl / gopayHelperApiKey / gopayHelperPhoneMode / gopayHelperPhoneNumber / gopayHelperOtpChannel / gopayHelperLocalSmsHelperEnabled / gopayHelperLocalSmsHelperUrl / gopayHelperPin`;其中 GPC API 地址固定归一为 `https://gpc.qlhazycoder.top`
|
||||||
- PayPal 账号池配置 `paypalAccounts / currentPayPalAccountId`,以及供后台步骤兼容读取的 `paypalEmail / paypalPassword`
|
- PayPal 账号池配置 `paypalAccounts / currentPayPalAccountId`,以及供后台步骤兼容读取的 `paypalEmail / paypalPassword`
|
||||||
- 邮箱 provider 配置
|
- 邮箱 provider 配置
|
||||||
- Cloud Mail / SkyMail API 配置:`cloudMailBaseUrl / cloudMailAdminEmail / cloudMailAdminPassword / cloudMailToken / cloudMailReceiveMailbox / cloudMailDomain / cloudMailDomains`
|
- Cloud Mail / SkyMail API 配置:`cloudMailBaseUrl / cloudMailAdminEmail / cloudMailAdminPassword / cloudMailToken / cloudMailReceiveMailbox / cloudMailDomain / cloudMailDomains`
|
||||||
@@ -551,8 +551,8 @@ Plus 模式通过 `plusModeEnabled` 开启,目标是在普通注册资料完
|
|||||||
Plus 模式可见步骤:
|
Plus 模式可见步骤:
|
||||||
|
|
||||||
1. 第 1~5 步:沿用普通注册入口、邮箱、密码、注册验证码、资料填写链路。
|
1. 第 1~5 步:沿用普通注册入口、邮箱、密码、注册验证码、资料填写链路。
|
||||||
2. 第 6 步 `创建 Plus Checkout`:PayPal / GoPay 打开已登录 ChatGPT 页面,通过 `/api/auth/session` 读取 accessToken,再请求 `https://chatgpt.com/backend-api/payments/checkout` 创建 `chatgptplusplan` 的 checkout session。PayPal 使用 `DE / EUR` 与 `https://chatgpt.com/checkout/openai_ie/{checkout_session_id}`;GoPay 使用 `ID / IDR` 与 `https://chatgpt.com/checkout/openai_llc/{checkout_session_id}`;GPC helper 模式改为把 accessToken、手机号、国家区号、`otp_channel` 等提交到 `gopayHelperApiUrl` 的 `/api/gp/tasks`,并通过 `gopayHelperApiKey` 发送 `X-API-Key` 认证,创建后保存 `task_id`;当前默认 GPC API 地址为 `https://gpc.qlhazycoder.top`。
|
2. 第 6 步 `创建 Plus Checkout`:PayPal / GoPay 打开已登录 ChatGPT 页面,通过 `/api/auth/session` 读取 accessToken,再请求 `https://chatgpt.com/backend-api/payments/checkout` 创建 `chatgptplusplan` 的 checkout session。PayPal 使用 `DE / EUR` 与 `https://chatgpt.com/checkout/openai_ie/{checkout_session_id}`;GoPay 使用 `ID / IDR` 与 `https://chatgpt.com/checkout/openai_llc/{checkout_session_id}`;GPC helper 模式改为把 accessToken 和 `gopayHelperPhoneMode` 提交到 `gopayHelperApiUrl` 的 `/api/gp/tasks`,并通过 `gopayHelperApiKey` 发送 `X-API-Key` 认证;自动模式只传 `phone_mode=auto`,手动模式额外传手机号、国家区号和 `otp_channel`,创建后保存 `task_id`;当前默认 GPC API 地址为 `https://gpc.qlhazycoder.top`。
|
||||||
3. 第 7 步 `填写账单并提交订阅`:PayPal / GoPay 仍走 checkout 页面与 Stripe iframe 自动化;GPC helper 模式不再操作 checkout iframe,而是轮询 `/api/gp/tasks/{task_id}`,根据远端 `api_waiting_for` 依次向 `/otp` 和 `/pin` 提交验证码与 PIN。若侧栏启用本地 OTP helper,后台会轮询 `gopayHelperLocalSmsHelperUrl` 的 `/latest-otp?phone=...&consume=1` 接口,按当前 GPC 手机号读取并消费当前 OTP 通道的验证码;未开启本地 helper 时才弹出手动 OTP 输入框。仓库内置的 `scripts/gpc_sms_helper_macos.py` 是 macOS Messages/本地通知兼容读取实现,其他通道需要提供兼容的本地 `/latest-otp` 或 `/otp` 接口。
|
3. 第 7 步 `填写账单并提交订阅`:PayPal / GoPay 仍走 checkout 页面与 Stripe iframe 自动化;GPC helper 模式不再操作 checkout iframe,而是轮询 `/api/gp/tasks/{task_id}`。自动模式只等待远端 `completed` 后继续后续步骤;手动模式根据远端 `api_waiting_for` 依次向 `/otp` 和 `/pin` 提交验证码与 PIN。若侧栏启用本地 OTP helper,后台会轮询 `gopayHelperLocalSmsHelperUrl` 的 `/latest-otp?phone=...&consume=1` 接口,按当前 GPC 手机号读取并消费当前 OTP 通道的验证码;未开启本地 helper 时才弹出手动 OTP 输入框。仓库内置的 `scripts/gpc_sms_helper_macos.py` 是 macOS Messages/本地通知兼容读取实现,其他通道需要提供兼容的本地 `/latest-otp` 或 `/otp` 接口。
|
||||||
4. 仅 PayPal / GoPay 会继续显示第 8 步:该步按当前支付方式显示为 `PayPal 登录与授权` 或 `GoPay 手机验证与授权`,底层 step key 仍为 `paypal-approve`。
|
4. 仅 PayPal / GoPay 会继续显示第 8 步:该步按当前支付方式显示为 `PayPal 登录与授权` 或 `GoPay 手机验证与授权`,底层 step key 仍为 `paypal-approve`。
|
||||||
5. 第 9 步 `订阅回跳确认` 仅在 PayPal / GoPay 模式下等待授权后回跳到 ChatGPT / OpenAI 页面,页面加载完成后固定等待 1 秒。GPC helper 模式在第 7 步任务完成后会直接进入 Plus 可见第 10 步 OAuth 登录。
|
5. 第 9 步 `订阅回跳确认` 仅在 PayPal / GoPay 模式下等待授权后回跳到 ChatGPT / OpenAI 页面,页面加载完成后固定等待 1 秒。GPC helper 模式在第 7 步任务完成后会直接进入 Plus 可见第 10 步 OAuth 登录。
|
||||||
6. 第 10 步:复用原 Step 7 OAuth 登录执行器,但状态和日志按 Plus 可见第 10 步记录。
|
6. 第 10 步:复用原 Step 7 OAuth 登录执行器,但状态和日志按 Plus 可见第 10 步记录。
|
||||||
|
|||||||
Reference in New Issue
Block a user